Merge pull request #143 from MagicSolutions/fix/admin-urs
Use separate URLs per model in administration to do the sortingmaster
commit
e9b03a3a4f
|
|
@ -13,18 +13,21 @@ except ImportError:
|
|||
from django.contrib.admin import ModelAdmin, TabularInline, StackedInline
|
||||
from django.contrib.admin.options import InlineModelAdmin
|
||||
|
||||
if VERSION >= (1, 8):
|
||||
from django.contrib.auth import get_permission_codename
|
||||
try:
|
||||
from django.contrib.contenttypes.admin import (GenericStackedInline,
|
||||
GenericTabularInline)
|
||||
else:
|
||||
GenericTabularInline)
|
||||
except:
|
||||
# Django < 1.7
|
||||
from django.contrib.contenttypes.generic import (GenericStackedInline,
|
||||
GenericTabularInline)
|
||||
GenericTabularInline)
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.template.defaultfilters import capfirst
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from adminsortable.fields import SortableForeignKey
|
||||
from adminsortable.models import SortableMixin
|
||||
|
|
@ -98,18 +101,33 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
|
|||
|
||||
def get_urls(self):
|
||||
urls = super(SortableAdmin, self).get_urls()
|
||||
opts = self.model._meta
|
||||
try:
|
||||
info = opts.app_label, opts.model_name
|
||||
except AttributeError:
|
||||
# Django < 1.7
|
||||
info = opts.app_label, opts.modul_name
|
||||
|
||||
# this ajax view changes the order
|
||||
admin_do_sorting_url = url(r'^sorting/do-sorting/(?P<model_type_id>\d+)/$',
|
||||
# this ajax view changes the order of instances of self.model
|
||||
admin_do_sorting_url = url(
|
||||
r'^sort/do-sorting/$',
|
||||
self.admin_site.admin_view(self.do_sorting_view),
|
||||
name='admin_do_sorting')
|
||||
name='%s_%s_do_sorting' % info)
|
||||
|
||||
# this ajax view changes the order of instances of inline models
|
||||
admin_do_inline_sorting_url = url(
|
||||
r'^sort/do-sorting/(?P<model_type_id>\d+)/$',
|
||||
self.admin_site.admin_view(self.do_sorting_view),
|
||||
name='%s_%s_do_sorting' % info)
|
||||
|
||||
# this view displays the sortable objects
|
||||
admin_sort_url = url(r'^sort/$',
|
||||
admin_sort_url = url(
|
||||
r'^sort/$',
|
||||
self.admin_site.admin_view(self.sort_view),
|
||||
name='admin_sort')
|
||||
name='%s_%s_sort' % info)
|
||||
|
||||
urls = [
|
||||
admin_do_inline_sorting_url,
|
||||
admin_do_sorting_url,
|
||||
admin_sort_url
|
||||
] + urls
|
||||
|
|
@ -120,15 +138,10 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
|
|||
Custom admin view that displays the objects as a list whose sort
|
||||
order can be changed via drag-and-drop.
|
||||
"""
|
||||
if not self.has_change_permission(request):
|
||||
raise PermissionDenied
|
||||
|
||||
opts = self.model._meta
|
||||
if VERSION >= (1, 8):
|
||||
codename = get_permission_codename('change', opts)
|
||||
has_perm = request.user.has_perm('{0}.{1}'.format(opts.app_label,
|
||||
codename))
|
||||
else:
|
||||
has_perm = request.user.has_perm('{0}.{1}'.format(opts.app_label,
|
||||
opts.get_change_permission()))
|
||||
|
||||
jquery_lib_path = 'admin/js/jquery.js' if VERSION < (1, 9) \
|
||||
else 'admin/js/vendor/jquery/jquery.js'
|
||||
|
|
@ -218,7 +231,7 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
|
|||
capfirst(verbose_name_plural)),
|
||||
'opts': opts,
|
||||
'app_label': opts.app_label,
|
||||
'has_perm': has_perm,
|
||||
'has_perm': True,
|
||||
'objects': objects,
|
||||
'group_expression': sortable_by_expression,
|
||||
'sortable_by_class': sortable_by_class,
|
||||
|
|
@ -254,19 +267,31 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
|
|||
return super(SortableAdmin, self).change_view(request, object_id,
|
||||
form_url='', extra_context=extra_context)
|
||||
|
||||
@method_decorator(require_POST)
|
||||
def do_sorting_view(self, request, model_type_id=None):
|
||||
"""
|
||||
This view sets the ordering of the objects for the model type
|
||||
and primary keys passed in. It must be an Ajax POST.
|
||||
"""
|
||||
if not self.has_change_permission(request):
|
||||
raise PermissionDenied
|
||||
|
||||
response = {'objects_sorted': False}
|
||||
|
||||
if request.is_ajax() and request.method == 'POST':
|
||||
if request.is_ajax():
|
||||
try:
|
||||
if model_type_id is None:
|
||||
klass = self.model
|
||||
else:
|
||||
klass = get_object_or_404(ContentType,
|
||||
id=model_type_id).model_class()
|
||||
if klass not in (inline.model for inline in self.inlines):
|
||||
raise Http404(
|
||||
'There is no inline model with type id: {0}'.format(
|
||||
model_type_id))
|
||||
|
||||
indexes = list(map(str,
|
||||
request.POST.get('indexes', []).split(',')))
|
||||
klass = ContentType.objects.get(
|
||||
id=model_type_id).model_class()
|
||||
objects_dict = dict([(str(obj.pk), obj) for obj in
|
||||
klass.objects.filter(pk__in=indexes)])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% load i18n admin_modify adminsortable_tags %}
|
||||
{% load i18n admin_modify adminsortable_tags admin_urls %}
|
||||
{% load static from staticfiles %}
|
||||
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
|
||||
<h2>{{ inline_admin_formset.opts.verbose_name_plural|title }} {% if inline_admin_formset.formset.initial_form_count > 1 %} - {% trans "drag and drop to change order" %}{% endif %}</h2>
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
{% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
|
||||
{{ inline_admin_form.fk_field.field }}
|
||||
{% if inline_admin_form.original %}
|
||||
<input type="hidden" name="admin_sorting_url" value="{% url 'admin:admin_do_sorting' inline_admin_form.original.model_type_id %}" />
|
||||
<input type="hidden" name="admin_sorting_url" value="{% url opts|admin_urlname:'do_sorting' inline_admin_form.original.model_type_id %}" />
|
||||
{% endif %}
|
||||
</div>{% endfor %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
{% include "admin/includes/fieldset.html" %}
|
||||
{% endfor %}
|
||||
{% if inline_admin_form.original %}
|
||||
<input type="hidden" name="admin_sorting_url" value="{% url 'admin:admin_do_sorting' inline_admin_form.original.model_type_id %}" />
|
||||
<input type="hidden" name="admin_sorting_url" value="{% url opts|admin_urlname:'do_sorting' inline_admin_form.original.model_type_id %}" />
|
||||
{% endif %}
|
||||
{% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
|
||||
{{ inline_admin_form.fk_field.field }}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% load i18n admin_modify adminsortable_tags %}
|
||||
{% load i18n admin_modify adminsortable_tags admin_urls %}
|
||||
{% load static from staticfiles %}
|
||||
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
|
||||
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
{% endfor %}
|
||||
{% endspaceless %}
|
||||
{% if inline_admin_form.original %}
|
||||
<input type="hidden" name="admin_sorting_url" value="{% url 'admin:admin_do_sorting' inline_admin_form.original.model_type_id %}" />
|
||||
<input type="hidden" name="admin_sorting_url" value="{% url opts|admin_urlname:'do_sorting' inline_admin_form.original.model_type_id %}" />
|
||||
{% endif %}
|
||||
</td>
|
||||
{% for fieldset in inline_admin_form %}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
{% endfor %}
|
||||
{% endspaceless %}
|
||||
{% if inline_admin_form.original %}
|
||||
<input type="hidden" name="admin_sorting_url" value="{% url 'admin:admin_do_sorting' inline_admin_form.original.model_type_id %}" />
|
||||
<input type="hidden" name="admin_sorting_url" value="{% url opts|admin_urlname:'do_sorting' inline_admin_form.original.model_type_id %}" />
|
||||
{% endif %}
|
||||
</td>
|
||||
{% for fieldset in inline_admin_form %}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{% load adminsortable_tags %}
|
||||
{% load adminsortable_tags admin_urls %}
|
||||
|
||||
<form>
|
||||
<input name="pk" type="hidden" value="{{ object.pk }}" />
|
||||
<a href="{% url 'admin:admin_do_sorting' object.model_type_id %}" class="admin_sorting_url"><i class="fa fa-{% if forloop.first %}sort-desc{% elif forloop.last %}sort-asc{% else %}sort{% endif %}"></i> {{ object }}</a>
|
||||
<a href="{% url opts|admin_urlname:'do_sorting' %}" class="admin_sorting_url"><i class="fa fa-{% if forloop.first %}sort-desc{% elif forloop.last %}sort-asc{% else %}sort{% endif %}"></i> {{ object }}</a>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -32,10 +32,16 @@ class SortableTestCase(TestCase):
|
|||
self.client = Client()
|
||||
self.user_raw_password = 'admin'
|
||||
self.user = User.objects.create_user('admin', 'admin@admin.com',
|
||||
self.user_raw_password)
|
||||
self.user_raw_password)
|
||||
self.user.is_staff = True
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
self.staff_raw_password = 'staff'
|
||||
self.staff = User.objects.create_user('staff', 'staff@staff.com',
|
||||
self.staff_raw_password)
|
||||
self.staff.is_staff = True
|
||||
self.user.is_superuser = False
|
||||
self.staff.save()
|
||||
|
||||
# create people
|
||||
Person.objects.create(first_name='Bob', last_name='Smith',
|
||||
|
|
@ -64,6 +70,9 @@ class SortableTestCase(TestCase):
|
|||
def test_new_user_is_staff(self):
|
||||
self.assertEqual(self.user.is_staff, True, 'User is not staff')
|
||||
|
||||
def test_new_staff_is_staff(self):
|
||||
self.assertEqual(self.staff.is_staff, True, 'Staff User is not staff')
|
||||
|
||||
def test_is_not_sortable(self):
|
||||
"""
|
||||
A model should only become sortable if it has more than
|
||||
|
|
@ -100,8 +109,11 @@ class SortableTestCase(TestCase):
|
|||
return category1, category2, category3
|
||||
|
||||
def get_sorting_url(self):
|
||||
return '/admin/app/category/sorting/do-sorting/{0}/'.format(
|
||||
Category.model_type_id())
|
||||
return '/admin/app/category/sort/do-sorting/'
|
||||
|
||||
def get_inline_sorting_url(self, model):
|
||||
return '/admin/app/project/sort/do-sorting/{0}/'.format(
|
||||
model.model_type_id())
|
||||
|
||||
def get_category_indexes(self, *categories):
|
||||
return {'indexes': ','.join([str(c.id) for c in categories])}
|
||||
|
|
@ -120,6 +132,38 @@ class SortableTestCase(TestCase):
|
|||
self.assertTrue('adminsortable/change_list.html' in template_names,
|
||||
'adminsortable/change_list.html was not rendered')
|
||||
|
||||
def test_adminsortable_change_list_sorting_fails_if_not_post(self):
|
||||
logged_in = self.client.login(username=self.user.username,
|
||||
password=self.user_raw_password)
|
||||
self.assertTrue(logged_in, 'User is not logged in')
|
||||
|
||||
category1, category2, category3 = self.make_test_categories()
|
||||
# make a normal GET
|
||||
response = self.client.get(
|
||||
self.get_sorting_url(),
|
||||
data=self.get_category_indexes(category1, category2, category3))
|
||||
|
||||
self.assertEqual(
|
||||
response.status_code,
|
||||
httplib.METHOD_NOT_ALLOWED,
|
||||
'Objects should not have been sorted. A POST method is required.')
|
||||
|
||||
def test_adminsortable_change_list_sorting_fails_permission_denied(self):
|
||||
logged_in = self.client.login(username=self.staff.username,
|
||||
password=self.staff_raw_password)
|
||||
self.assertTrue(logged_in, 'User is not logged in')
|
||||
|
||||
category1, category2, category3 = self.make_test_categories()
|
||||
# make a normal POST
|
||||
response = self.client.post(
|
||||
self.get_sorting_url(),
|
||||
data=self.get_category_indexes(category1, category2, category3))
|
||||
|
||||
self.assertEqual(
|
||||
response.status_code,
|
||||
httplib.FORBIDDEN,
|
||||
'Objects should not have been sorted. User is not allowed.')
|
||||
|
||||
def test_adminsortable_change_list_sorting_fails_if_not_ajax(self):
|
||||
logged_in = self.client.login(username=self.user.username,
|
||||
password=self.user_raw_password)
|
||||
|
|
@ -203,3 +247,81 @@ class SortableTestCase(TestCase):
|
|||
response = self.client.get('/admin/app/project/sort/')
|
||||
self.assertEquals(response.status_code, httplib.OK,
|
||||
'Unable to reach sort view.')
|
||||
|
||||
def test_adminsortable_change_list_view_permission_denied(self):
|
||||
category1 = self.create_category(title='Category 3')
|
||||
Project.objects.create(category=category1, description="foo")
|
||||
|
||||
self.client.login(username=self.staff.username,
|
||||
password=self.staff_raw_password)
|
||||
response = self.client.get('/admin/app/project/sort/')
|
||||
self.assertEquals(response.status_code, httplib.FORBIDDEN,
|
||||
'Sort view must be forbidden.')
|
||||
|
||||
def test_adminsortable_inline_changelist_not_found(self):
|
||||
self.client.login(username=self.user.username,
|
||||
password=self.user_raw_password)
|
||||
|
||||
project = Project.objects.create(
|
||||
category=self.create_category(),
|
||||
description='Test project'
|
||||
)
|
||||
note1 = project.note_set.create(text='note 1')
|
||||
note2 = project.note_set.create(text='note 2')
|
||||
|
||||
response = self.client.post(
|
||||
self.get_inline_sorting_url(Category),
|
||||
data=self.get_category_indexes(note2, note1),
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
|
||||
self.assertEqual(
|
||||
response.status_code,
|
||||
httplib.NOT_FOUND,
|
||||
'Categories must not be sortable trough ProjectAdmin')
|
||||
|
||||
def test_adminsortable_inline_changelist_success(self):
|
||||
self.client.login(username=self.user.username,
|
||||
password=self.user_raw_password)
|
||||
|
||||
project = Project.objects.create(
|
||||
category=self.create_category(),
|
||||
description='Test project'
|
||||
)
|
||||
note1 = project.note_set.create(text='note 1')
|
||||
note2 = project.note_set.create(text='note 2')
|
||||
note3 = project.note_set.create(text='note 3')
|
||||
|
||||
response = self.client.post(
|
||||
self.get_inline_sorting_url(project.note_set.model),
|
||||
data=self.get_category_indexes(note3, note2, note1),
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
|
||||
self.assertEqual(
|
||||
response.status_code,
|
||||
httplib.OK,
|
||||
'Note inline must be sortable in ProjectAdmin')
|
||||
content = json.loads(response.content.decode(encoding='UTF-8'),
|
||||
'latin-1')
|
||||
self.assertTrue(content.get('objects_sorted'),
|
||||
'Objects should have been sorted.')
|
||||
|
||||
notes = list(project.note_set.all().values('id', 'order', 'text'))
|
||||
expected_notes = [
|
||||
{
|
||||
'id': note3.pk,
|
||||
'order': 1,
|
||||
'text': note3.text,
|
||||
},
|
||||
{
|
||||
'id': note2.pk,
|
||||
'order': 2,
|
||||
'text': note2.text,
|
||||
},
|
||||
{
|
||||
'id': note1.pk,
|
||||
'order': 3,
|
||||
'text': note1.text,
|
||||
}
|
||||
]
|
||||
self.assertEqual(notes, expected_notes)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue