mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-04-26 17:44:45 +00:00
Handle actions by POSTing to ModelListView
This commit is contained in:
parent
79e6dfad83
commit
ade92956d0
5 changed files with 125 additions and 32 deletions
|
|
@ -13,11 +13,13 @@ from django.db.models import get_models, signals
|
|||
|
||||
from djadmin2 import apiviews
|
||||
from djadmin2 import views
|
||||
from djadmin2 import actions
|
||||
|
||||
MODEL_ADMIN_ATTRS = (
|
||||
'list_display', 'list_display_links', 'list_filter',
|
||||
'admin', 'has_permission', 'has_add_permission',
|
||||
'has_edit_permission', 'has_delete_permission',
|
||||
'get_actions',
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -126,6 +128,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
|
||||
|
|
@ -243,6 +248,20 @@ 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 ImmutableAdmin(object):
|
||||
"""
|
||||
|
|
|
|||
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();
|
||||
});
|
||||
});
|
||||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from django.conf import settings
|
|||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.forms.models import modelform_factory
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.views import generic
|
||||
|
||||
from braces.views import AccessMixin
|
||||
|
|
@ -109,12 +110,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