Restructured sample project for Django 2.
Refactored database and changed "app" to "samples" so name didn't conflict with "AppConfig". Replaced deprecated assignment_tag with simple_tag. Updated unit tests.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from adminsortable.admin import (SortableAdmin, SortableTabularInline,
|
||||
SortableStackedInline, SortableGenericStackedInline,
|
||||
NonSortableParentAdmin)
|
||||
from adminsortable.utils import get_is_sortable
|
||||
from .models import (Category, Widget, Project, Credit, Note, GenericNote,
|
||||
Component, Person, NonSortableCategory, SortableCategoryWidget,
|
||||
SortableNonInlineCategory, NonSortableCredit, NonSortableNote,
|
||||
CustomWidget, CustomWidgetComponent, BackwardCompatibleWidget)
|
||||
|
||||
|
||||
admin.site.register(Category, SortableAdmin)
|
||||
|
||||
|
||||
class ComponentInline(SortableStackedInline):
|
||||
# fieldsets = (
|
||||
# ('foo', {
|
||||
# 'classes': ('collapse',),
|
||||
# 'fields': ('title',)
|
||||
# }),
|
||||
# ('', {
|
||||
# 'classes': ('collapse',),
|
||||
# 'fields': ('widget',)
|
||||
# }),
|
||||
# )
|
||||
model = Component
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(ComponentInline, self).get_queryset(
|
||||
request).exclude(title__icontains='2')
|
||||
if get_is_sortable(qs):
|
||||
self.model.is_sortable = True
|
||||
else:
|
||||
self.model.is_sortable = False
|
||||
return qs
|
||||
|
||||
|
||||
class WidgetAdmin(SortableAdmin):
|
||||
def get_queryset(self, request):
|
||||
"""
|
||||
A simple example demonstrating that adminsortable works even in
|
||||
situations where you need to filter the queryset in admin. Here,
|
||||
we are just filtering out `widget` instances with an pk higher
|
||||
than 3
|
||||
"""
|
||||
qs = super(WidgetAdmin, self).get_queryset(request)
|
||||
return qs.filter(id__lte=3)
|
||||
|
||||
inlines = [ComponentInline]
|
||||
|
||||
admin.site.register(Widget, WidgetAdmin)
|
||||
|
||||
|
||||
class CreditInline(SortableTabularInline):
|
||||
model = Credit
|
||||
extra = 1
|
||||
|
||||
|
||||
class NoteInline(SortableStackedInline):
|
||||
model = Note
|
||||
extra = 2
|
||||
|
||||
|
||||
class GenericNoteInline(SortableGenericStackedInline):
|
||||
model = GenericNote
|
||||
extra = 0
|
||||
|
||||
|
||||
class NonSortableCreditInline(admin.TabularInline):
|
||||
model = NonSortableCredit
|
||||
extra = 1
|
||||
|
||||
|
||||
class NonSortableNoteInline(admin.StackedInline):
|
||||
model = NonSortableNote
|
||||
extra = 0
|
||||
|
||||
|
||||
class ProjectAdmin(SortableAdmin):
|
||||
inlines = [
|
||||
CreditInline, NoteInline, GenericNoteInline,
|
||||
NonSortableCreditInline, NonSortableNoteInline
|
||||
]
|
||||
list_display = ['__str__', 'category']
|
||||
|
||||
admin.site.register(Project, ProjectAdmin)
|
||||
|
||||
|
||||
class PersonAdmin(SortableAdmin):
|
||||
list_display = ['__str__', 'is_board_member']
|
||||
|
||||
admin.site.register(Person, PersonAdmin)
|
||||
|
||||
|
||||
class SortableCategoryWidgetInline(SortableStackedInline):
|
||||
model = SortableCategoryWidget
|
||||
extra = 0
|
||||
|
||||
|
||||
class NonSortableCategoryAdmin(NonSortableParentAdmin):
|
||||
inlines = [SortableCategoryWidgetInline]
|
||||
|
||||
admin.site.register(NonSortableCategory, NonSortableCategoryAdmin)
|
||||
|
||||
|
||||
class CustomWidgetComponentInline(SortableStackedInline):
|
||||
model = CustomWidgetComponent
|
||||
extra = 0
|
||||
|
||||
|
||||
class CustomWidgetAdmin(SortableAdmin):
|
||||
inlines = [CustomWidgetComponentInline]
|
||||
|
||||
|
||||
admin.site.register(SortableNonInlineCategory, SortableAdmin)
|
||||
admin.site.register(CustomWidget, CustomWidgetAdmin)
|
||||
admin.site.register(BackwardCompatibleWidget, SortableAdmin)
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SamplesConfig(AppConfig):
|
||||
name = 'samples'
|
||||
@@ -0,0 +1,251 @@
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
|
||||
from adminsortable.fields import SortableForeignKey
|
||||
from adminsortable.models import Sortable, SortableMixin
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SimpleModel(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
title = models.CharField(max_length=50)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
# A model that is sortable
|
||||
class Category(SimpleModel, SortableMixin):
|
||||
class Meta:
|
||||
verbose_name_plural = 'Categories'
|
||||
ordering = ['order']
|
||||
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
|
||||
# A model with an override of its queryset for admin
|
||||
@python_2_unicode_compatible
|
||||
class Widget(SimpleModel, SortableMixin):
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
order = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
|
||||
# A model that is sortable relative to a foreign key that is also sortable
|
||||
# uses SortableForeignKey field. Works with versions 1.3+
|
||||
class Project(SimpleModel, SortableMixin):
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
|
||||
category = SortableForeignKey(Category, on_delete=models.CASCADE)
|
||||
description = models.TextField()
|
||||
|
||||
order = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
|
||||
# Registered as a tabular inline on `Project`
|
||||
@python_2_unicode_compatible
|
||||
class Credit(SortableMixin):
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
||||
first_name = models.CharField(max_length=30, help_text="Given name")
|
||||
last_name = models.CharField(max_length=30, help_text="Family name")
|
||||
|
||||
order = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
def __str__(self):
|
||||
return '{0} {1}'.format(self.first_name, self.last_name)
|
||||
|
||||
|
||||
# Registered as a stacked inline on `Project`
|
||||
@python_2_unicode_compatible
|
||||
class Note(SortableMixin):
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
||||
text = models.CharField(max_length=100)
|
||||
|
||||
order = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.text
|
||||
|
||||
|
||||
# Registered as a tabular inline on `Project` which can't be sorted
|
||||
@python_2_unicode_compatible
|
||||
class NonSortableCredit(models.Model):
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
||||
first_name = models.CharField(max_length=30, help_text="Given name")
|
||||
last_name = models.CharField(max_length=30, help_text="Family name")
|
||||
|
||||
def __str__(self):
|
||||
return '{0} {1}'.format(self.first_name, self.last_name)
|
||||
|
||||
|
||||
# Registered as a stacked inline on `Project` which can't be sorted
|
||||
@python_2_unicode_compatible
|
||||
class NonSortableNote(models.Model):
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
||||
text = models.CharField(max_length=100)
|
||||
|
||||
def __str__(self):
|
||||
return self.text
|
||||
|
||||
|
||||
# A generic bound model
|
||||
@python_2_unicode_compatible
|
||||
class GenericNote(SimpleModel, SortableMixin):
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE,
|
||||
verbose_name=u"Content type", related_name="generic_notes")
|
||||
object_id = models.PositiveIntegerField(u"Content id")
|
||||
content_object = GenericForeignKey(ct_field='content_type',
|
||||
fk_field='object_id')
|
||||
|
||||
order = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
|
||||
def __str__(self):
|
||||
return u'{0}: {1}'.format(self.title, self.content_object)
|
||||
|
||||
|
||||
# An model registered as an inline that has a custom queryset
|
||||
@python_2_unicode_compatible
|
||||
class Component(SimpleModel, SortableMixin):
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
|
||||
widget = SortableForeignKey(Widget, on_delete=models.CASCADE)
|
||||
|
||||
order = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Person(SortableMixin):
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
verbose_name_plural = 'People'
|
||||
|
||||
first_name = models.CharField(max_length=50)
|
||||
last_name = models.CharField(max_length=50)
|
||||
is_board_member = models.BooleanField('Board Member', default=False)
|
||||
|
||||
order = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
# Sorting Filters allow you to set up sub-sets of objects that need
|
||||
# to have independent sorting. They are listed in order, from left
|
||||
# to right in the sorting change list. You can use any standard
|
||||
# Django ORM filter method.
|
||||
sorting_filters = (
|
||||
('Board Members', {'is_board_member': True}),
|
||||
('Non-Board Members', {'is_board_member': False}),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return '{0} {1}'.format(self.first_name, self.last_name)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class NonSortableCategory(SimpleModel):
|
||||
class Meta(SimpleModel.Meta):
|
||||
verbose_name = 'Non-Sortable Category'
|
||||
verbose_name_plural = 'Non-Sortable Categories'
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SortableCategoryWidget(SimpleModel, SortableMixin):
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
verbose_name = 'Sortable Category Widget'
|
||||
verbose_name_plural = 'Sortable Category Widgets'
|
||||
|
||||
non_sortable_category = SortableForeignKey(
|
||||
NonSortableCategory, on_delete=models.CASCADE)
|
||||
|
||||
order = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SortableNonInlineCategory(SimpleModel, SortableMixin):
|
||||
"""Example of a model that is sortable, but has a SortableForeignKey
|
||||
that is *not* sortable, and is also not defined as an inline of the
|
||||
SortableForeignKey field."""
|
||||
|
||||
non_sortable_category = SortableForeignKey(
|
||||
NonSortableCategory, on_delete=models.CASCADE)
|
||||
|
||||
order = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
verbose_name = 'Sortable Non-Inline Category'
|
||||
verbose_name_plural = 'Sortable Non-Inline Categories'
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CustomWidget(SortableMixin, SimpleModel):
|
||||
|
||||
# custom field for ordering
|
||||
custom_order_field = models.PositiveIntegerField(default=0, db_index=True,
|
||||
editable=False)
|
||||
|
||||
class Meta:
|
||||
ordering = ['custom_order_field']
|
||||
verbose_name = 'Custom Widget'
|
||||
verbose_name_plural = 'Custom Widgets'
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CustomWidgetComponent(SortableMixin, SimpleModel):
|
||||
|
||||
custom_widget = models.ForeignKey(CustomWidget, on_delete=models.CASCADE)
|
||||
|
||||
# custom field for ordering
|
||||
widget_order = models.PositiveIntegerField(default=0, db_index=True,
|
||||
editable=False)
|
||||
|
||||
class Meta:
|
||||
ordering = ['widget_order']
|
||||
verbose_name = 'Custom Widget Component'
|
||||
verbose_name_plural = 'Custom Widget Components'
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class BackwardCompatibleWidget(Sortable, SimpleModel):
|
||||
|
||||
class Meta(Sortable.Meta):
|
||||
verbose_name = 'Backward Compatible Widget'
|
||||
verbose_name_plural = 'Backward Compatible Widgets'
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
@@ -0,0 +1,318 @@
|
||||
try:
|
||||
import httplib # Python 2
|
||||
except ImportError:
|
||||
import http.client as httplib # Python 3
|
||||
|
||||
import json
|
||||
import uuid
|
||||
|
||||
import django
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
|
||||
from adminsortable.models import SortableMixin
|
||||
from adminsortable.utils import get_is_sortable
|
||||
from .models import Category, Person, Project
|
||||
|
||||
|
||||
class TestSortableModel(SortableMixin):
|
||||
title = models.CharField(max_length=100)
|
||||
|
||||
order = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class TestNonAutoFieldModel(SortableMixin):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
order = models.PositiveIntegerField(editable=False, db_index=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['order']
|
||||
|
||||
|
||||
class SortableTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.user_raw_password = 'admin'
|
||||
self.user = User.objects.create_user('admin', 'admin@admin.com',
|
||||
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',
|
||||
is_board_member=True)
|
||||
Person.objects.create(first_name='Sally', last_name='Sue',
|
||||
is_board_member=False)
|
||||
Person.objects.create(first_name='Mike', last_name='Wilson',
|
||||
is_board_member=True)
|
||||
Person.objects.create(first_name='Robert', last_name='Roberts',
|
||||
is_board_member=True)
|
||||
|
||||
self.people = Person.objects.all()
|
||||
self.first_person = self.people[0]
|
||||
self.second_person = self.people[1]
|
||||
self.third_person = self.people[2]
|
||||
self.fourth_person = self.people[3]
|
||||
|
||||
def create_category(self, title='Category 1'):
|
||||
category = Category.objects.create(title=title)
|
||||
return category
|
||||
|
||||
def test_new_user_is_authenticated(self):
|
||||
if django.VERSION < (1, 10):
|
||||
self.assertEqual(self.user.is_authenticated(), True,
|
||||
'User is not authenticated')
|
||||
else:
|
||||
self.assertEqual(self.user.is_authenticated, True,
|
||||
'User is not authenticated')
|
||||
|
||||
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
|
||||
record to sort.
|
||||
"""
|
||||
self.create_category()
|
||||
self.assertFalse(get_is_sortable(Category.objects.all()),
|
||||
'Category only has one record. It should not be sortable.')
|
||||
|
||||
def test_is_sortable(self):
|
||||
self.create_category()
|
||||
self.create_category(title='Category 2')
|
||||
self.assertTrue(get_is_sortable(Category.objects.all()),
|
||||
'Category has more than one record. It should be sortable.')
|
||||
|
||||
def test_save_order_incremented(self):
|
||||
category1 = self.create_category()
|
||||
self.assertEqual(category1.order, 1, 'Category 1 order should be 1.')
|
||||
|
||||
category2 = self.create_category(title='Category 2')
|
||||
self.assertEqual(category2.order, 2, 'Category 2 order should be 2.')
|
||||
|
||||
def test_adminsortable_change_list_view(self):
|
||||
self.client.login(username=self.user.username,
|
||||
password=self.user_raw_password)
|
||||
response = self.client.get('/admin/samples/category/sort/')
|
||||
self.assertEqual(response.status_code, httplib.OK,
|
||||
'Unable to reach sort view.')
|
||||
|
||||
def make_test_categories(self):
|
||||
category1 = self.create_category()
|
||||
category2 = self.create_category(title='Category 2')
|
||||
category3 = self.create_category(title='Category 3')
|
||||
return category1, category2, category3
|
||||
|
||||
def get_sorting_url(self, model):
|
||||
return '/admin/samples/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])}
|
||||
|
||||
def test_adminsortable_changelist_templates(self):
|
||||
logged_in = self.client.login(username=self.user.username,
|
||||
password=self.user_raw_password)
|
||||
self.assertTrue(logged_in, 'User is not logged in')
|
||||
|
||||
response = self.client.get('/admin/samples/category/sort/')
|
||||
self.assertEqual(response.status_code, httplib.OK,
|
||||
'Admin sort request failed.')
|
||||
|
||||
# assert adminsortable change list templates are used
|
||||
template_names = [t.name for t in response.templates]
|
||||
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(Category),
|
||||
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(Category),
|
||||
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)
|
||||
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(Category),
|
||||
data=self.get_category_indexes(category1, category2, category3))
|
||||
content = json.loads(response.content.decode(encoding='UTF-8'))
|
||||
self.assertFalse(content.get('objects_sorted'),
|
||||
'Objects should not have been sorted. An ajax post is required.')
|
||||
|
||||
def test_adminsortable_change_list_sorting_successful(self):
|
||||
logged_in = self.client.login(username=self.user.username,
|
||||
password=self.user_raw_password)
|
||||
self.assertTrue(logged_in, 'User is not logged in')
|
||||
|
||||
# make categories
|
||||
category1, category2, category3 = self.make_test_categories()
|
||||
|
||||
# make an Ajax POST
|
||||
response = self.client.post(self.get_sorting_url(Category),
|
||||
data=self.get_category_indexes(category3, category2, category1),
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
content = json.loads(response.content.decode(encoding='UTF-8'))
|
||||
self.assertTrue(content.get('objects_sorted'),
|
||||
'Objects should have been sorted.')
|
||||
|
||||
# assert order is correct
|
||||
categories = Category.objects.all()
|
||||
cat1 = categories[0]
|
||||
cat2 = categories[1]
|
||||
cat3 = categories[2]
|
||||
|
||||
self.assertEqual(cat1.order, 1,
|
||||
'First category returned should have order == 1')
|
||||
self.assertEqual(cat1.pk, 3,
|
||||
'Category ID 3 should have been first in queryset')
|
||||
|
||||
self.assertEqual(cat2.order, 2,
|
||||
'Second category returned should have order == 2')
|
||||
self.assertEqual(cat2.pk, 2,
|
||||
'Category ID 2 should have been second in queryset')
|
||||
|
||||
self.assertEqual(cat3.order, 3,
|
||||
'Third category returned should have order == 3')
|
||||
self.assertEqual(cat3.pk, 1,
|
||||
'Category ID 1 should have been third in queryset')
|
||||
|
||||
def test_get_next(self):
|
||||
for index, person in enumerate(self.people):
|
||||
next_index = index + 1
|
||||
next_person = self.people[index].get_next()
|
||||
|
||||
if next_index < len(self.people):
|
||||
self.assertEqual(next_person, self.people[next_index],
|
||||
'Next person should be "{0}"'.format(
|
||||
self.people[next_index]))
|
||||
|
||||
def test_get_previous(self):
|
||||
for person in self.people:
|
||||
previous_person = person.get_previous()
|
||||
|
||||
# get_previous() returns `None` if there isn't a previous object
|
||||
if previous_person:
|
||||
self.assertEqual(previous_person,
|
||||
self.people.get(order=person.order - 1),
|
||||
'Previous person for "{0}" (order: {1}) should be "{2}"'
|
||||
'(order: {3})'.format(person, person.order,
|
||||
previous_person, previous_person.order))
|
||||
|
||||
def test_adminsortable_change_list_view_loads_with_sortable_fk(self):
|
||||
category1 = self.create_category(title='Category 3')
|
||||
Project.objects.create(category=category1, description="foo")
|
||||
|
||||
self.client.login(username=self.user.username,
|
||||
password=self.user_raw_password)
|
||||
response = self.client.get('/admin/samples/project/sort/')
|
||||
self.assertEqual(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/samples/project/sort/')
|
||||
self.assertEqual(response.status_code, httplib.FORBIDDEN,
|
||||
'Sort view must be forbidden.')
|
||||
|
||||
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_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'))
|
||||
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)
|
||||
|
||||
def test_save_non_auto_field_model(self):
|
||||
model = TestNonAutoFieldModel()
|
||||
model.save()
|
||||
Reference in New Issue
Block a user