diff --git a/.travis.yml b/.travis.yml index db076ec..1c9c45a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python python: - - "2.6" - "2.7" - "3.4" @@ -9,7 +8,6 @@ env: - DJANGO_VERSION=1.5.10 SAMPLE_PROJECT=sample_project - DJANGO_VERSION=1.6.7 SAMPLE_PROJECT=sample_project - DJANGO_VERSION=1.7 SAMPLE_PROJECT=sample_project - - DJANGO_VERSION=1.7 SAMPLE_PROJECT=python3_sample_project branches: only: @@ -17,18 +15,6 @@ branches: matrix: exclude: - - - python: "2.6" - env: DJANGO_VERSION=1.5.10 SAMPLE_PROJECT=sample_project - - - - python: "2.6" - env: DJANGO_VERSION=1.7 SAMPLE_PROJECT=sample_project - - - - python: "2.6" - env: DJANGO_VERSION=1.7 SAMPLE_PROJECT=python3_sample_project - - python: "2.7" env: DJANGO_VERSION=1.5.10 SAMPLE_PROJECT=sample_project diff --git a/README.md b/README.md index 985926b..88f62b7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://travis-ci.org/iambrandontaylor/django-admin-sortable.svg?branch=master)](https://travis-ci.org/iambrandontaylor/django-admin-sortable) -Current version: 1.7.7 +Current version: 1.7.8 This project makes it easy to add drag-and-drop ordering to any model in Django admin. Inlines for a sortable model may also be made sortable, @@ -409,8 +409,9 @@ ordering on top of that just seemed a little much in my opinion. django-admin-sortable is currently used in production. -### What's new in 1.7.7? +### What's new in 1.7.8? - Limited support for self-referential SortableForeignKeys +- Improved support for sortable models that have a foreign key to a non-sortable parent ### Deprecation warnings diff --git a/adminsortable/__init__.py b/adminsortable/__init__.py index 59bb9fa..d9ba222 100644 --- a/adminsortable/__init__.py +++ b/adminsortable/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 7, 7) # following PEP 386 +VERSION = (1, 7, 8) # following PEP 386 DEV_N = None diff --git a/adminsortable/admin.py b/adminsortable/admin.py index a4f498f..f7dc505 100644 --- a/adminsortable/admin.py +++ b/adminsortable/admin.py @@ -279,7 +279,11 @@ class SortableInlineBase(SortableAdminBase, InlineModelAdmin): ' and SortableStackedInline must inherit from Sortable') def get_queryset(self, request): - qs = super(SortableInlineBase, self).get_queryset(request) + if VERSION < (1, 6): + qs = super(SortableInlineBase, self).queryset(request) + else: + qs = super(SortableInlineBase, self).get_queryset(request) + if get_is_sortable(qs): self.model.is_sortable = True else: @@ -292,7 +296,7 @@ class SortableInlineBase(SortableAdminBase, InlineModelAdmin): class SortableTabularInline(TabularInline, SortableInlineBase): """Custom template that enables sorting for tabular inlines""" - if VERSION <= (1, 5): + if VERSION < (1, 6): template = 'adminsortable/edit_inline/tabular-1.5.x.html' else: template = 'adminsortable/edit_inline/tabular.html' @@ -300,7 +304,7 @@ class SortableTabularInline(TabularInline, SortableInlineBase): class SortableStackedInline(StackedInline, SortableInlineBase): """Custom template that enables sorting for stacked inlines""" - if VERSION <= (1, 5): + if VERSION < (1, 6): template = 'adminsortable/edit_inline/stacked-1.5.x.html' else: template = 'adminsortable/edit_inline/stacked.html' @@ -308,7 +312,7 @@ class SortableStackedInline(StackedInline, SortableInlineBase): class SortableGenericTabularInline(GenericTabularInline, SortableInlineBase): """Custom template that enables sorting for tabular inlines""" - if VERSION <= (1, 5): + if VERSION < (1, 6): template = 'adminsortable/edit_inline/tabular-1.5.x.html' else: template = 'adminsortable/edit_inline/tabular.html' @@ -316,7 +320,7 @@ class SortableGenericTabularInline(GenericTabularInline, SortableInlineBase): class SortableGenericStackedInline(GenericStackedInline, SortableInlineBase): """Custom template that enables sorting for stacked inlines""" - if VERSION <= (1, 5): + if VERSION < (1, 6): template = 'adminsortable/edit_inline/stacked-1.5.x.html' else: template = 'adminsortable/edit_inline/stacked.html' diff --git a/adminsortable/models.py b/adminsortable/models.py index 2e9d907..d17a815 100644 --- a/adminsortable/models.py +++ b/adminsortable/models.py @@ -80,7 +80,7 @@ class Sortable(models.Model): {self.sortable_foreign_key.name: sfk_obj.id}) try: - obj = self._meta.model.objects.filter(**filters)[:1][0] + obj = self.__class__.objects.filter(**filters)[:1][0] except IndexError: obj = None diff --git a/adminsortable/utils.py b/adminsortable/utils.py index 539e4eb..3452351 100644 --- a/adminsortable/utils.py +++ b/adminsortable/utils.py @@ -1,9 +1,13 @@ from .models import Sortable, SortableForeignKey +def check_inheritance(obj): + return issubclass(type(obj), Sortable) + + def get_is_sortable(objects): if objects: - if issubclass(type(objects[0]), Sortable): + if check_inheritance(objects[0]): if objects.count() > 1: return True return False @@ -11,7 +15,8 @@ def get_is_sortable(objects): def is_self_referential(cls): cls_type = type(cls) - sortable_subclass = issubclass(cls_type, Sortable) + sortable_subclass = check_inheritance(cls_type) + # sortable_subclass = issubclass(cls_type, Sortable) sortable_foreign_key_subclass = issubclass(cls_type, SortableForeignKey) if sortable_foreign_key_subclass and not sortable_subclass: return True @@ -20,10 +25,10 @@ def is_self_referential(cls): def check_model_is_sortable(cls): if cls: - if is_self_referential(cls): - objects = cls.model.objects - else: - objects = cls.objects - + if check_inheritance(cls): + if is_self_referential(cls): + objects = cls.model.objects + else: + objects = cls.objects return get_is_sortable(objects.all()) return False diff --git a/python3_sample_project/app/__init__.py b/python3_sample_project/app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python3_sample_project/app/admin.py b/python3_sample_project/app/admin.py deleted file mode 100644 index 580bf5a..0000000 --- a/python3_sample_project/app/admin.py +++ /dev/null @@ -1,84 +0,0 @@ -from django.contrib import admin - -from adminsortable.admin import (SortableAdmin, SortableTabularInline, - SortableStackedInline, SortableGenericStackedInline, - NonSortableParentAdmin) -from adminsortable.utils import get_is_sortable -from app.models import (Category, Widget, Project, Credit, Note, GenericNote, - Component, Person, NonSortableCategory, SortableCategoryWidget, - SortableNonInlineCategory, SelfReferentialCategory) - - -admin.site.register(Category, SortableAdmin) - - -class ComponentInline(SortableStackedInline): - model = Component - - def queryset(self, request): - qs = super(ComponentInline, self).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 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).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 = 0 - - -class GenericNoteInline(SortableGenericStackedInline): - model = GenericNote - extra = 0 - - -class ProjectAdmin(SortableAdmin): - inlines = [CreditInline, NoteInline, GenericNoteInline] - 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) - -admin.site.register(SortableNonInlineCategory, SortableAdmin) - -admin.site.register(SelfReferentialCategory, SortableAdmin) diff --git a/python3_sample_project/app/fixtures/data.json b/python3_sample_project/app/fixtures/data.json deleted file mode 100644 index 25f6d7d..0000000 --- a/python3_sample_project/app/fixtures/data.json +++ /dev/null @@ -1,52 +0,0 @@ -[ -{ - "fields": { - "is_board_member": true, - "order": 1, - "first_name": "Brandon", - "last_name": "Taylor" - }, - "pk": 1, - "model": "app.person" -}, -{ - "fields": { - "is_board_member": true, - "order": 2, - "first_name": "Kerri", - "last_name": "Taylor" - }, - "pk": 2, - "model": "app.person" -}, -{ - "fields": { - "is_board_member": false, - "order": 3, - "first_name": "Sarah", - "last_name": "Taylor" - }, - "pk": 3, - "model": "app.person" -}, -{ - "fields": { - "is_board_member": false, - "order": 4, - "first_name": "Renna", - "last_name": "Taylor" - }, - "pk": 4, - "model": "app.person" -}, -{ - "fields": { - "is_board_member": false, - "order": 5, - "first_name": "Jake", - "last_name": "Taylor" - }, - "pk": 5, - "model": "app.person" -} -] diff --git a/python3_sample_project/app/migrations/0001_initial.py b/python3_sample_project/app/migrations/0001_initial.py deleted file mode 100644 index ab0527a..0000000 --- a/python3_sample_project/app/migrations/0001_initial.py +++ /dev/null @@ -1,146 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import adminsortable.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('contenttypes', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Category', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('order', models.PositiveIntegerField(editable=False, default=1, db_index=True)), - ('title', models.CharField(max_length=50)), - ], - options={ - 'ordering': ['order'], - 'verbose_name_plural': 'Categories', - 'abstract': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Component', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('order', models.PositiveIntegerField(editable=False, default=1, db_index=True)), - ('title', models.CharField(max_length=50)), - ], - options={ - 'ordering': ['order'], - 'abstract': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Credit', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('order', models.PositiveIntegerField(editable=False, default=1, db_index=True)), - ('first_name', models.CharField(max_length=30)), - ('last_name', models.CharField(max_length=30)), - ], - options={ - 'ordering': ['order'], - 'abstract': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='GenericNote', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('order', models.PositiveIntegerField(editable=False, default=1, db_index=True)), - ('title', models.CharField(max_length=50)), - ('object_id', models.PositiveIntegerField(verbose_name='Content id')), - ('content_type', models.ForeignKey(related_name='generic_notes', verbose_name='Content type', to='contenttypes.ContentType')), - ], - options={ - 'ordering': ['order'], - 'abstract': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Note', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('order', models.PositiveIntegerField(editable=False, default=1, db_index=True)), - ('text', models.CharField(max_length=100)), - ], - options={ - 'ordering': ['order'], - 'abstract': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Person', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('order', models.PositiveIntegerField(editable=False, default=1, db_index=True)), - ('first_name', models.CharField(max_length=50)), - ('last_name', models.CharField(max_length=50)), - ('is_board_member', models.BooleanField(verbose_name='Board Member', default=False)), - ], - options={ - 'ordering': ['order'], - 'verbose_name_plural': 'People', - 'abstract': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Project', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('order', models.PositiveIntegerField(editable=False, default=1, db_index=True)), - ('title', models.CharField(max_length=50)), - ('description', models.TextField()), - ('category', adminsortable.fields.SortableForeignKey(to='app.Category')), - ], - options={ - 'ordering': ['order'], - 'abstract': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Widget', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('order', models.PositiveIntegerField(editable=False, default=1, db_index=True)), - ('title', models.CharField(max_length=50)), - ], - options={ - 'ordering': ['order'], - 'abstract': False, - }, - bases=(models.Model,), - ), - migrations.AddField( - model_name='note', - name='project', - field=models.ForeignKey(to='app.Project'), - preserve_default=True, - ), - migrations.AddField( - model_name='credit', - name='project', - field=models.ForeignKey(to='app.Project'), - preserve_default=True, - ), - migrations.AddField( - model_name='component', - name='widget', - field=adminsortable.fields.SortableForeignKey(to='app.Widget'), - preserve_default=True, - ), - ] diff --git a/python3_sample_project/app/migrations/0002_nonsortablecategory_sortablecategorywidget.py b/python3_sample_project/app/migrations/0002_nonsortablecategory_sortablecategorywidget.py deleted file mode 100644 index 53db365..0000000 --- a/python3_sample_project/app/migrations/0002_nonsortablecategory_sortablecategorywidget.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import adminsortable.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='NonSortableCategory', - fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), - ('title', models.CharField(max_length=50)), - ], - options={ - 'verbose_name': 'Non-Sortable Category', - 'verbose_name_plural': 'Non-Sortable Categories', - 'abstract': False, - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='SortableCategoryWidget', - fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), - ('order', models.PositiveIntegerField(db_index=True, editable=False, default=1)), - ('title', models.CharField(max_length=50)), - ('non_sortable_category', adminsortable.fields.SortableForeignKey(to='app.NonSortableCategory')), - ], - options={ - 'ordering': ['order'], - 'verbose_name': 'Sortable Category Widget', - 'abstract': False, - 'verbose_name_plural': 'Sortable Category Widgets', - }, - bases=(models.Model,), - ), - ] diff --git a/python3_sample_project/app/migrations/0003_selfreferentialcategory.py b/python3_sample_project/app/migrations/0003_selfreferentialcategory.py deleted file mode 100644 index bd9a878..0000000 --- a/python3_sample_project/app/migrations/0003_selfreferentialcategory.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import adminsortable.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0002_nonsortablecategory_sortablecategorywidget'), - ] - - operations = [ - migrations.CreateModel( - name='SelfReferentialCategory', - fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), - ('order', models.PositiveIntegerField(db_index=True, editable=False, default=1)), - ('title', models.CharField(max_length=50)), - ('child', adminsortable.fields.SortableForeignKey(to='app.SelfReferentialCategory')), - ], - options={ - 'abstract': False, - }, - bases=(models.Model,), - ), - ] diff --git a/python3_sample_project/app/migrations/0004_auto_20141029_1612.py b/python3_sample_project/app/migrations/0004_auto_20141029_1612.py deleted file mode 100644 index 1f7e76f..0000000 --- a/python3_sample_project/app/migrations/0004_auto_20141029_1612.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import adminsortable.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0003_selfreferentialcategory'), - ] - - operations = [ - migrations.AlterModelOptions( - name='selfreferentialcategory', - options={'verbose_name_plural': 'Sortable Referential Categories', 'verbose_name': 'Sortable Referential Category'}, - ), - migrations.AlterField( - model_name='selfreferentialcategory', - name='child', - field=adminsortable.fields.SortableForeignKey(to='app.SelfReferentialCategory', null=True, blank=True), - ), - ] diff --git a/python3_sample_project/app/migrations/__init__.py b/python3_sample_project/app/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python3_sample_project/app/models.py b/python3_sample_project/app/models.py deleted file mode 100644 index 34127b1..0000000 --- a/python3_sample_project/app/models.py +++ /dev/null @@ -1,169 +0,0 @@ -from django.contrib.contenttypes import generic -from django.contrib.contenttypes.models import ContentType -from django.db import models - -from adminsortable.fields import SortableForeignKey -from adminsortable.models import Sortable - - -class SimpleModel(models.Model): - class Meta: - abstract = True - - title = models.CharField(max_length=50) - - def __unicode__(self): - return self.title - - -# A model that is sortable -class Category(SimpleModel, Sortable): - class Meta(Sortable.Meta): - """ - Classes that inherit from Sortable must define an inner - Meta class that inherits from Sortable.Meta or ordering - won't work as expected - """ - verbose_name_plural = 'Categories' - - def __str__(self): - return self.title - - -# A model with an override of its queryset for admin -class Widget(SimpleModel, Sortable): - class Meta(Sortable.Meta): - pass - - def __str__(self): - return self.title - - -# 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, Sortable): - class Meta(Sortable.Meta): - pass - - category = SortableForeignKey(Category) - description = models.TextField() - - -# Registered as a tabular inline on `Project` -class Credit(Sortable): - class Meta(Sortable.Meta): - pass - - project = models.ForeignKey(Project) - first_name = models.CharField(max_length=30) - last_name = models.CharField(max_length=30) - - def __str__(self): - return '{0} {1}'.format(self.first_name, self.last_name) - - -# Registered as a stacked inline on `Project` -class Note(Sortable): - class Meta(Sortable.Meta): - pass - - project = models.ForeignKey(Project) - text = models.CharField(max_length=100) - - def __str__(self): - return self.text - - -# A generic bound model -class GenericNote(SimpleModel, Sortable): - content_type = models.ForeignKey(ContentType, - verbose_name=u"Content type", related_name="generic_notes") - object_id = models.PositiveIntegerField(u"Content id") - content_object = generic.GenericForeignKey(ct_field='content_type', - fk_field='object_id') - - class Meta(Sortable.Meta): - pass - - def __str__(self): - return u'{0}: {1}'.format(self.title, self.content_object) - - -# An model registered as an inline that has a custom queryset -class Component(SimpleModel, Sortable): - class Meta(Sortable.Meta): - pass - - widget = SortableForeignKey(Widget) - - def __str__(self): - return self.title - - -class Person(Sortable): - class Meta(Sortable.Meta): - 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) - - # 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) - - -class NonSortableCategory(SimpleModel): - class Meta(SimpleModel.Meta): - verbose_name = 'Non-Sortable Category' - verbose_name_plural = 'Non-Sortable Categories' - - def __str__(self): - return self.title - - -class SortableCategoryWidget(SimpleModel, Sortable): - class Meta(Sortable.Meta): - verbose_name = 'Sortable Category Widget' - verbose_name_plural = 'Sortable Category Widgets' - - non_sortable_category = SortableForeignKey(NonSortableCategory) - - def __str__(self): - return self.title - - -class SortableNonInlineCategory(SimpleModel, Sortable): - """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.""" - - class Meta(Sortable.Meta): - verbose_name = 'Sortable Non-Inline Category' - verbose_name_plural = 'Sortable Non-Inline Categories' - - non_sortable_category = SortableForeignKey(NonSortableCategory) - - def __str__(self): - return self.title - - -class SelfReferentialCategory(SimpleModel, Sortable): - class Meta(Sortable.Meta): - verbose_name = 'Sortable Referential Category' - verbose_name_plural = 'Sortable Referential Categories' - - child = SortableForeignKey('self', blank=True, null=True) - - def __str__(self): - if self.child: - return '{} - {}'.format(self.child, self.title) - return self.title diff --git a/python3_sample_project/app/tests.py b/python3_sample_project/app/tests.py deleted file mode 100644 index 66e1a4b..0000000 --- a/python3_sample_project/app/tests.py +++ /dev/null @@ -1,158 +0,0 @@ -import http.client -import json - -from django.contrib.auth.models import User -from django.test import TestCase -from django.test.client import Client, RequestFactory - -from adminsortable.utils import get_is_sortable -from app.models import Category, Person - - -class SortableTestCase(TestCase): - fixtures = ['data.json'] - - def setUp(self): - self.client = Client() - self.factory = RequestFactory() - 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() - - people = Person.objects.all() - self.first_person = people[0] - self.second_person = people[1] - - def create_category(self, title='Category 1'): - category = Category.objects.create(title=title) - return category - - def test_new_user_is_authenticated(self): - 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_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/app/category/sort/') - self.assertEquals(response.status_code, http.client.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): - return '/admin/app/category/sorting/do-sorting/{0}/'.format( - Category.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/app/category/sort/') - self.assertEqual(response.status_code, http.client.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_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(), - data=self.get_category_indexes(category1, category2, category3)) - content = json.loads(response.content.decode(encoding='UTF-8'), - 'latin-1') - 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(), - data=self.get_category_indexes(category3, category2, category1), - HTTP_X_REQUESTED_WITH='XMLHttpRequest') - content = json.loads(response.content.decode(encoding='UTF-8'), - 'latin-1') - 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): - result = self.first_person.get_next() - - self.assertEqual(self.second_person, result, 'Next person should ' - 'be "{}"'.format(self.second_person)) - - def test_get_previous(self): - result = self.second_person.get_previous() - - self.assertEqual(self.first_person, result, 'Previous person should ' - 'be "{}"'.format(self.first_person)) diff --git a/python3_sample_project/database/python3-test-project.sqlite3 b/python3_sample_project/database/python3-test-project.sqlite3 deleted file mode 100644 index 95395d0..0000000 Binary files a/python3_sample_project/database/python3-test-project.sqlite3 and /dev/null differ diff --git a/python3_sample_project/manage.py b/python3_sample_project/manage.py deleted file mode 100644 index 08821aa..0000000 --- a/python3_sample_project/manage.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -# Adds the adminsortable package from the cloned repository instead of -# site_packages -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", - "python3_sample_project.settings") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff --git a/python3_sample_project/python3_sample_project/__init__.py b/python3_sample_project/python3_sample_project/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python3_sample_project/python3_sample_project/settings.py b/python3_sample_project/python3_sample_project/settings.py deleted file mode 100644 index 5d729fe..0000000 --- a/python3_sample_project/python3_sample_project/settings.py +++ /dev/null @@ -1,99 +0,0 @@ -""" -Django settings for python3_sample_project project. - -For more information on this file, see -https://docs.djangoproject.com/en/1.7/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.7/ref/settings/ -""" - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -from .utils import map_path - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'k^uy@(9tieoj3d%o=09ph$b&gu+5q@9h$f(6l7@h2ak*0@y9%w' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -TEMPLATE_DEBUG = True - -ALLOWED_HOSTS = [] - - -# Application definition - -INSTALLED_APPS = ( - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - - 'adminsortable', - 'app', -) - -MIDDLEWARE_CLASSES = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) - -ROOT_URLCONF = 'python3_sample_project.urls' - -WSGI_APPLICATION = 'python3_sample_project.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/1.7/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': map_path('database/python3-test-project.sqlite3'), - }, - 'test': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': map_path('database/test-python3-test-project.sqlite3'), - } -} - -# Internationalization -# https://docs.djangoproject.com/en/1.7/topics/i18n/ - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.7/howto/static-files/ - -STATIC_URL = '/static/' - -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -) - -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -) diff --git a/python3_sample_project/python3_sample_project/urls.py b/python3_sample_project/python3_sample_project/urls.py deleted file mode 100644 index c4b2ed2..0000000 --- a/python3_sample_project/python3_sample_project/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.conf.urls import patterns, include, url -from django.contrib import admin - -urlpatterns = patterns('', - # Examples: - # url(r'^$', 'python3_sample_project.views.home', name='home'), - # url(r'^blog/', include('blog.urls')), - - url(r'^admin/', include(admin.site.urls)), -) diff --git a/python3_sample_project/python3_sample_project/utils.py b/python3_sample_project/python3_sample_project/utils.py deleted file mode 100644 index 4178ad9..0000000 --- a/python3_sample_project/python3_sample_project/utils.py +++ /dev/null @@ -1,6 +0,0 @@ -import os - - -def map_path(directory_name): - return os.path.join(os.path.dirname(__file__), - '../' + directory_name).replace('\\', '/') diff --git a/python3_sample_project/python3_sample_project/wsgi.py b/python3_sample_project/python3_sample_project/wsgi.py deleted file mode 100644 index 1256fa7..0000000 --- a/python3_sample_project/python3_sample_project/wsgi.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -WSGI config for python3_sample_project project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ -""" - -import os -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "python3_sample_project.settings") - -from django.core.wsgi import get_wsgi_application -application = get_wsgi_application() diff --git a/sample_project/app/models.py b/sample_project/app/models.py index bf8b563..7fcc520 100644 --- a/sample_project/app/models.py +++ b/sample_project/app/models.py @@ -181,5 +181,10 @@ class SortableNonInlineCategory(SimpleModel, Sortable): verbose_name = 'Sortable Non-Inline Category' verbose_name_plural = 'Sortable Non-Inline Categories' +<<<<<<< HEAD +======= + non_sortable_category = SortableForeignKey(NonSortableCategory) + +>>>>>>> e83e31cb22c681d4ddd954e993a9c2a129486ad4 def __str__(self): return self.title diff --git a/sample_project/app/tests.py b/sample_project/app/tests.py index 5c5b1e2..9871a43 100644 --- a/sample_project/app/tests.py +++ b/sample_project/app/tests.py @@ -1,4 +1,8 @@ -import httplib +try: + import httplib +except ImportError: + import http.client as httplib + import json from django.contrib.auth.models import User @@ -118,7 +122,8 @@ class SortableTestCase(TestCase): #make a normal POST response = self.client.post(self.get_sorting_url(), data=self.get_category_indexes(category1, category2, category3)) - content = json.loads(response.content) + content = json.loads(response.content.decode(encoding='UTF-8'), + 'latin-1') self.assertFalse(content.get('objects_sorted'), 'Objects should not have been sorted. An ajax post is required.') @@ -134,7 +139,8 @@ class SortableTestCase(TestCase): response = self.client.post(self.get_sorting_url(), data=self.get_category_indexes(category3, category2, category1), HTTP_X_REQUESTED_WITH='XMLHttpRequest') - content = json.loads(response.content) + content = json.loads(response.content.decode(encoding='UTF-8'), + 'latin-1') self.assertTrue(content.get('objects_sorted'), 'Objects should have been sorted.')