merge conflict resolutions

This commit is contained in:
Daniel Greenfeld 2013-05-23 13:01:27 +02:00
commit a62d3aceff
9 changed files with 218 additions and 34 deletions

60
djadmin2/actions.py Normal file
View 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"

View file

@ -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")

View file

@ -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):
"""

View 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();
});
});

View file

@ -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">

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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"

View file

@ -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):