mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-03-16 22:20:24 +00:00
merge conflict resolutions
This commit is contained in:
commit
a62d3aceff
9 changed files with 218 additions and 34 deletions
60
djadmin2/actions.py
Normal file
60
djadmin2/actions.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.text import capfirst
|
||||
|
||||
from django.contrib import messages
|
||||
|
||||
def get_description(action):
|
||||
if hasattr(action, 'description'):
|
||||
return action.description
|
||||
else:
|
||||
return capfirst(action.__name__.replace('_', ' '))
|
||||
|
||||
def delete_selected(request, queryset):
|
||||
# We check whether the user has permission to delete the objects in the
|
||||
# queryset.
|
||||
#
|
||||
# TODO: This duplicates some of the permission-checking functionality in
|
||||
# BaseAdmin2. Investigate how to DRY this out.
|
||||
#
|
||||
# TODO: Check that user has permission to delete all related obejcts. See
|
||||
# `get_deleted_objects` in contrib.admin.util for how this is currently
|
||||
# done. (Hint: I think we can do better.)
|
||||
|
||||
model = queryset.model
|
||||
opts = model._meta
|
||||
permission_name = '%s.delete.%s' \
|
||||
% (opts.app_label, opts.object_name.lower())
|
||||
has_permission = request.user.has_perm(permission_name)
|
||||
|
||||
if len(queryset) == 1:
|
||||
objects_name = opts.verbose_name
|
||||
else:
|
||||
objects_name = opts.verbose_name_plural
|
||||
|
||||
if request.POST.get('confirmed'):
|
||||
# The user has confirmed that they want to delete the objects.
|
||||
if has_permission:
|
||||
queryset.delete()
|
||||
message = "Successfully deleted %d %s" % \
|
||||
(len(queryset), objects_name)
|
||||
messages.add_message(request, messages.INFO, message)
|
||||
return None
|
||||
else:
|
||||
raise PermissionDenied
|
||||
else:
|
||||
# The user has not confirmed that they want to delete the objects, so
|
||||
# render a template asking for their confirmation.
|
||||
if has_permission:
|
||||
template = 'admin2/bootstrap/delete_selected_confirmation.html'
|
||||
context = {
|
||||
'queryset': queryset,
|
||||
'objects_name': objects_name
|
||||
}
|
||||
return TemplateResponse(request, template, context)
|
||||
else:
|
||||
message = "Permission to delete %s denied" % objects_name
|
||||
messages.add_message(request, messages.INFO, message)
|
||||
return None
|
||||
|
||||
delete_selected.description = "Delete selected items"
|
||||
|
|
@ -3,7 +3,7 @@ from django.conf import settings
|
|||
MODEL_ADMIN_ATTRS = (
|
||||
'list_display', 'list_display_links', 'list_filter',
|
||||
'admin', 'has_permission', 'has_add_permission',
|
||||
'has_edit_permission', 'has_delete_permission',
|
||||
'has_edit_permission', 'has_delete_permission', 'get_actions'
|
||||
)
|
||||
|
||||
ADMIN2_THEME_DIRECTORY = getattr(settings, "ADMIN2_THEME_DIRECTORY", "admin2/bootstrap")
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import extra_views
|
|||
from djadmin2 import apiviews
|
||||
from djadmin2 import constants
|
||||
from djadmin2 import views
|
||||
from djadmin2 import actions
|
||||
|
||||
|
||||
class BaseAdmin2(object):
|
||||
|
|
@ -125,6 +126,9 @@ class ModelAdmin2(BaseAdmin2):
|
|||
api_list_view = apiviews.ListCreateAPIView
|
||||
api_detail_view = apiviews.RetrieveUpdateDestroyAPIView
|
||||
|
||||
# Actions
|
||||
actions = [actions.delete_selected]
|
||||
|
||||
def __init__(self, model, admin, **kwargs):
|
||||
self.model = model
|
||||
self.admin = admin
|
||||
|
|
@ -244,6 +248,19 @@ class ModelAdmin2(BaseAdmin2):
|
|||
def api_urls(self):
|
||||
return self.get_api_urls(), None, None
|
||||
|
||||
def get_actions(self):
|
||||
actions_dict = {}
|
||||
|
||||
for cls in type(self).mro()[::-1]:
|
||||
class_actions = getattr(cls, 'actions', [])
|
||||
for action in class_actions:
|
||||
actions_dict[action.__name__] = {
|
||||
'name': action.__name__,
|
||||
'description': actions.get_description(action),
|
||||
'func': action
|
||||
}
|
||||
return actions_dict
|
||||
|
||||
|
||||
class Admin2Inline(extra_views.InlineFormSet):
|
||||
"""
|
||||
|
|
|
|||
26
djadmin2/static/themes/bootstrap/js/actions.js
Normal file
26
djadmin2/static/themes/bootstrap/js/actions.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
$(function() {
|
||||
var element = $("#model-list");
|
||||
var selectAllCheckbox = element.find('.model-select-all');
|
||||
var selectCheckboxen = element.find('.model-select');
|
||||
var selectedCount = element.find('#selected-count');
|
||||
|
||||
var updateSelectedCount = function() {
|
||||
var count = 0;
|
||||
for (var ix = 0; ix < selectCheckboxen.length; ix++) {
|
||||
if ($(selectCheckboxen[ix]).prop('checked')) {
|
||||
count++;
|
||||
};
|
||||
};
|
||||
selectAllCheckbox.prop('checked', count == selectCheckboxen.length);
|
||||
selectedCount.text(count);
|
||||
};
|
||||
|
||||
selectAllCheckbox.click(function(e) {
|
||||
selectCheckboxen.prop('checked', this.checked);
|
||||
updateSelectedCount();
|
||||
});
|
||||
|
||||
selectCheckboxen.click(function(e) {
|
||||
updateSelectedCount();
|
||||
});
|
||||
});
|
||||
|
|
@ -33,7 +33,6 @@
|
|||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
</strong>
|
||||
{% if user.has_usable_password %}
|
||||
<li><a tabindex="-1" href="TODO">{% trans "Change password" %}</a></li>
|
||||
{% endif %}
|
||||
|
|
@ -45,6 +44,14 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{% if messages %}
|
||||
<ul class="messages">
|
||||
{% for message in messages %}
|
||||
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="span10">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
{% extends "admin2/bootstrap/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p>Are you sure you want to delete the selected {{ objects_name }}?</p>
|
||||
|
||||
<ul>
|
||||
{% for item in queryset %}
|
||||
<li>{{ item }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="confirmed" value="yes" />
|
||||
<input type="hidden" name="action" value="delete_selected" />
|
||||
{% for item in queryset %}
|
||||
<input type="hidden" name="selected_model_id" value="{{ item.id }}" />
|
||||
{% endfor %}
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
|
||||
{% endblock content %}
|
||||
|
|
@ -5,46 +5,57 @@
|
|||
|
||||
{% block page_title %}Select {{ model }} to change{% endblock %}
|
||||
|
||||
{% block extrajs %}
|
||||
<script src="/static/themes/bootstrap/js/actions.js"></script>
|
||||
{% endblock extrajs %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="span12">
|
||||
<div class="space-below">
|
||||
<div class="btn-group">
|
||||
<button class="btn dropdown-toggle" data-toggle="dropdown">
|
||||
Actions
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a tabindex="-1" href="#">Delete selected {{ model }}{{ object_list|pluralize }}</a></li>
|
||||
</ul>
|
||||
<div id="model-list" class="row">
|
||||
<form id="model-list-form" class="form-inline" method="post">
|
||||
<div class="span12">
|
||||
<div class="navbar">
|
||||
{% csrf_token %}
|
||||
<select name="action">
|
||||
{% for action in actions %}
|
||||
<option value="{{ action.name }}">{{ action.description }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="Submit" class="btn">Go</button>
|
||||
<span id="selected-count">0</span> of {{ object_list|length }} selected
|
||||
<div class="pull-right">
|
||||
{# if has_add_permission #}
|
||||
<a href="{% url view|admin2_urlname:'create' %}" class="btn"><i class="icon-plus"></i> Add {{ model }}</a>
|
||||
{# endif #}
|
||||
</div>
|
||||
</div>
|
||||
<small class="muted">TODO of {{ object_list|length }} selected</small>
|
||||
<div class="pull-right">
|
||||
{# if has_add_permission #}
|
||||
<a href="{% url view|admin2_urlname:'create' %}" class="btn"><i class="icon-plus"></i> Add {{ model }}</a>
|
||||
{# endif #}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-bordered table-striped">
|
||||
<thead>
|
||||
<th class="checkbox-column"><input type="checkbox"></th>
|
||||
<th>{{ model|title}}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for obj in object_list %}
|
||||
<table class="table table-bordered table-striped">
|
||||
<thead>
|
||||
<th><input type="checkbox" class="model-select-all"></th>
|
||||
<th>{{ model|title}}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for obj in object_list %}
|
||||
<tr>
|
||||
<td><input type="checkbox"></td>
|
||||
<td><input type="checkbox" class="model-select" name="selected_model_id" value="{{obj.id}}"></td>
|
||||
<td>
|
||||
<a href="{% url view|admin2_urlname:'update' pk=obj.pk %}">{{ obj }}</a>
|
||||
{{ obj }} <a href="{% url view|admin2_urlname:'detail' pk=obj.pk %}">detail</a>
|
||||
{# if has_edit_permission #}
|
||||
<a href="{% url view|admin2_urlname:'update' pk=obj.pk %}">edit</a>
|
||||
{# endif #}
|
||||
{# if has_delete_permission #}
|
||||
<a href="{% url view|admin2_urlname:'delete' pk=obj.pk %}">delete</a>
|
||||
{# endif #}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>{{ object_list|length }} {{ model }}{{ object_list|pluralize }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ object_list|length }} {{ model }}{{ object_list|pluralize }}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import os
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.views import generic
|
||||
|
||||
import extra_views
|
||||
|
|
@ -23,12 +27,32 @@ class ModelListView(Admin2Mixin, generic.ListView):
|
|||
default_template_name = "model_list.html"
|
||||
permission_type = 'view'
|
||||
|
||||
def post(self, request):
|
||||
# This is where we handle actions
|
||||
action_name = request.POST['action']
|
||||
action_func = self.get_actions()[action_name]['func']
|
||||
selected_model_ids = request.POST.getlist('selected_model_id')
|
||||
queryset = self.model.objects.filter(pk__in=selected_model_ids)
|
||||
response = action_func(request, queryset)
|
||||
if response is None:
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
else:
|
||||
return response
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ModelListView, self).get_context_data(**kwargs)
|
||||
context['model'] = self.get_model()._meta.verbose_name
|
||||
context['model_pluralized'] = self.get_model()._meta.verbose_name_plural
|
||||
context['actions'] = self.get_actions().values()
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
view_name = 'admin2:{}_{}_index'.format(self.app_label, self.model_name)
|
||||
return reverse(view_name)
|
||||
|
||||
def get_actions(self):
|
||||
return self.model_admin.get_actions()
|
||||
|
||||
|
||||
class ModelDetailView(AdminModel2Mixin, generic.DetailView):
|
||||
default_template_name = "model_detail.html"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,22 @@ class PostListTest(BaseIntegrationTest):
|
|||
response = self.client.get(reverse("admin2:blog_post_index"))
|
||||
self.assertContains(response, post.title)
|
||||
|
||||
def test_actions_displayed(self):
|
||||
response = self.client.get(reverse("admin2:blog_post_index"))
|
||||
self.assertInHTML('<option value="delete_selected">Delete selected items</option>', response.content)
|
||||
|
||||
def test_delete_selected_post(self):
|
||||
post = Post.objects.create(title="a_post_title", body="body")
|
||||
params = {'action': 'delete_selected', 'selected_model_id': str(post.id)}
|
||||
response = self.client.post(reverse("admin2:blog_post_index"), params)
|
||||
self.assertInHTML('<p>Are you sure you want to delete the selected post?</p>', response.content)
|
||||
|
||||
def test_delete_selected_post_confirmation(self):
|
||||
post = Post.objects.create(title="a_post_title", body="body")
|
||||
params = {'action': 'delete_selected', 'selected_model_id': str(post.id), 'confirmed': 'yes'}
|
||||
response = self.client.post(reverse("admin2:blog_post_index"), params)
|
||||
self.assertRedirects(response, reverse("admin2:blog_post_index"))
|
||||
|
||||
|
||||
class PostDetailViewTest(BaseIntegrationTest):
|
||||
def test_view_ok(self):
|
||||
|
|
|
|||
Loading…
Reference in a new issue