From cc5fc81da94faadea7ae0d5e1a117d0a9fcceb1c Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 14:13:12 +0100 Subject: [PATCH 01/20] Add tox.ini to test and collect combined coverage data of multiple Python and Django versions --- tox.ini | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tox.ini diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..2f8f44f --- /dev/null +++ b/tox.ini @@ -0,0 +1,36 @@ +[tox] +envlist = django{1.8,1.9,1.10,1.11}-{py27,py34,py35},coverage + +[testenv] +deps = + coverage + django1.8: Django>=1.8,<1.9 + django1.9: Django>=1.9,<1.10 + django1.10: Django>=1.10,<1.11 + django1.11: Django>=1.11a1,<1.12 +whitelist_externals = cd +setenv = + PYTHONPATH = {toxinidir}/sample_project + PYTHONWARNINGS = module + PYTHONDONTWRITEBYTECODE = 1 +commands = + coverage run -p sample_project/manage.py test app + +[testenv:coverage] +deps = coverage +skip_install = true +commands = + coverage combine + coverage report + coverage html + +[coverage:run] +branch = True +parallel = True +source = + adminsortable + sample_project + +[coverage:report] +exclude_lines = + if __name__ == .__main__.: From e0a85c554b88c9cf2b7b45014bc9e85358bb5bb3 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 14:14:47 +0100 Subject: [PATCH 02/20] Remove django.conf.urls import fallback --- adminsortable/admin.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/adminsortable/admin.py b/adminsortable/admin.py index ae24dd1..9bf089a 100644 --- a/adminsortable/admin.py +++ b/adminsortable/admin.py @@ -3,13 +3,7 @@ import json from django import VERSION from django.conf import settings - -try: - from django.conf.urls import url -except ImportError: - # Django < 1.4 - from django.conf.urls.defaults import url - +from django.conf.urls import url from django.contrib.admin import ModelAdmin, TabularInline, StackedInline from django.contrib.admin.options import InlineModelAdmin From 74e0c924558baa9f19d59b94cf89168cfc606085 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 14:22:27 +0100 Subject: [PATCH 03/20] Remove contenttypes.generic import fallbacks --- adminsortable/admin.py | 11 ++--------- sample_project/app/models.py | 8 +------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/adminsortable/admin.py b/adminsortable/admin.py index 9bf089a..d233f58 100644 --- a/adminsortable/admin.py +++ b/adminsortable/admin.py @@ -6,15 +6,8 @@ from django.conf import settings from django.conf.urls import url from django.contrib.admin import ModelAdmin, TabularInline, StackedInline from django.contrib.admin.options import InlineModelAdmin - -try: - from django.contrib.contenttypes.admin import (GenericStackedInline, - GenericTabularInline) -except: - # Django < 1.7 - from django.contrib.contenttypes.generic import (GenericStackedInline, - GenericTabularInline) - +from django.contrib.contenttypes.admin import (GenericStackedInline, + GenericTabularInline) from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.http import HttpResponse, Http404 diff --git a/sample_project/app/models.py b/sample_project/app/models.py index 6f1186f..81e6606 100644 --- a/sample_project/app/models.py +++ b/sample_project/app/models.py @@ -1,10 +1,4 @@ -from django import VERSION - -if VERSION < (1, 9): - from django.contrib.contenttypes.generic import GenericForeignKey -else: - from django.contrib.contenttypes.fields import GenericForeignKey - +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 f17db2293878832ac001654582d79aa96b339087 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 14:38:12 +0100 Subject: [PATCH 04/20] Remove inline admin fallback templates --- adminsortable/admin.py | 8 -- .../edit_inline/stacked-1.5.x.html | 92 ------------ .../edit_inline/tabular-1.5.x.html | 136 ------------------ 3 files changed, 236 deletions(-) delete mode 100644 adminsortable/templates/adminsortable/edit_inline/stacked-1.5.x.html delete mode 100644 adminsortable/templates/adminsortable/edit_inline/tabular-1.5.x.html diff --git a/adminsortable/admin.py b/adminsortable/admin.py index d233f58..7f55ba6 100644 --- a/adminsortable/admin.py +++ b/adminsortable/admin.py @@ -334,8 +334,6 @@ class SortableTabularInline(TabularInline, SortableInlineBase): """Custom template that enables sorting for tabular inlines""" if VERSION >= (1, 10): template = 'adminsortable/edit_inline/tabular-1.10.x.html' - elif VERSION < (1, 6): - template = 'adminsortable/edit_inline/tabular-1.5.x.html' else: template = 'adminsortable/edit_inline/tabular.html' @@ -344,8 +342,6 @@ class SortableStackedInline(StackedInline, SortableInlineBase): """Custom template that enables sorting for stacked inlines""" if VERSION >= (1, 10): template = 'adminsortable/edit_inline/stacked-1.10.x.html' - elif VERSION < (1, 6): - template = 'adminsortable/edit_inline/stacked-1.5.x.html' else: template = 'adminsortable/edit_inline/stacked.html' @@ -354,8 +350,6 @@ class SortableGenericTabularInline(GenericTabularInline, SortableInlineBase): """Custom template that enables sorting for tabular inlines""" if VERSION >= (1, 10): template = 'adminsortable/edit_inline/tabular-1.10.x.html' - elif VERSION < (1, 6): - template = 'adminsortable/edit_inline/tabular-1.5.x.html' else: template = 'adminsortable/edit_inline/tabular.html' @@ -364,7 +358,5 @@ class SortableGenericStackedInline(GenericStackedInline, SortableInlineBase): """Custom template that enables sorting for stacked inlines""" if VERSION >= (1, 10): template = 'adminsortable/edit_inline/stacked-1.10.x.html' - elif VERSION < (1, 6): - template = 'adminsortable/edit_inline/stacked-1.5.x.html' else: template = 'adminsortable/edit_inline/stacked.html' diff --git a/adminsortable/templates/adminsortable/edit_inline/stacked-1.5.x.html b/adminsortable/templates/adminsortable/edit_inline/stacked-1.5.x.html deleted file mode 100644 index c34182d..0000000 --- a/adminsortable/templates/adminsortable/edit_inline/stacked-1.5.x.html +++ /dev/null @@ -1,92 +0,0 @@ -{% load i18n admin_modify adminsortable_tags admin_urls %} -{% load static from staticfiles %} -
-

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

-{{ inline_admin_formset.formset.management_form }} -{{ inline_admin_formset.formset.non_form_errors }} - -{% for inline_admin_form in inline_admin_formset %}
-

- {% if inline_admin_form.original %} - {% with initial_forms_count=inline_admin_formset.formset.management_form.INITIAL_FORMS.value %} - - {% endwith %} - {% endif %} - {{ inline_admin_formset.opts.verbose_name|title }}: {% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %} - {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %} - {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}{% endif %} -

- {% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %} - {% for fieldset in inline_admin_form %} - {% include "admin/includes/fieldset.html" %} - {% endfor %} - {% 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 %} - - {% endif %} -
{% endfor %} -
- - diff --git a/adminsortable/templates/adminsortable/edit_inline/tabular-1.5.x.html b/adminsortable/templates/adminsortable/edit_inline/tabular-1.5.x.html deleted file mode 100644 index bdc8348..0000000 --- a/adminsortable/templates/adminsortable/edit_inline/tabular-1.5.x.html +++ /dev/null @@ -1,136 +0,0 @@ -{% load i18n admin_modify adminsortable_tags admin_urls %} -{% load static from staticfiles %} -
- -
- - From ac507e448425fd5a8449d47a0063fb4403258de6 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 14:44:51 +0100 Subject: [PATCH 05/20] Remove get_queryset fallback --- adminsortable/admin.py | 23 +++-------------------- sample_project/app/admin.py | 8 ++++---- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/adminsortable/admin.py b/adminsortable/admin.py index 7f55ba6..cd868b7 100644 --- a/adminsortable/admin.py +++ b/adminsortable/admin.py @@ -38,13 +38,7 @@ class SortableAdminBase(object): its sort order can be changed. This view adds a link to the object_tools block to take people to the view to change the sorting. """ - - try: - qs_method = getattr(self, 'get_queryset', self.queryset) - except AttributeError: - qs_method = self.get_queryset - - if get_is_sortable(qs_method(request)): + if get_is_sortable(self.get_queryset(request)): self.change_list_template = \ self.sortable_change_list_with_sort_link_template self.is_sortable = True @@ -137,11 +131,7 @@ class SortableAdmin(SortableAdminBase, ModelAdmin): pass # Apply any sort filters to create a subset of sortable objects - try: - qs_method = getattr(self, 'get_queryset', self.queryset) - except AttributeError: - qs_method = self.get_queryset - objects = qs_method(request).filter(**filters) + objects = self.get_queryset(request).filter(**filters) # Determine if we need to regroup objects relative to a # foreign key specified on the model class that is extending Sortable. @@ -315,20 +305,13 @@ class SortableInlineBase(SortableAdminBase, InlineModelAdmin): ' (or Sortable for legacy implementations)') def get_queryset(self, request): - if VERSION < (1, 6): - qs = super(SortableInlineBase, self).queryset(request) - else: - qs = super(SortableInlineBase, self).get_queryset(request) - + qs = super(SortableInlineBase, self).get_queryset(request) if get_is_sortable(qs): self.model.is_sortable = True else: self.model.is_sortable = False return qs - if VERSION < (1, 6): - queryset = get_queryset - class SortableTabularInline(TabularInline, SortableInlineBase): """Custom template that enables sorting for tabular inlines""" diff --git a/sample_project/app/admin.py b/sample_project/app/admin.py index cba42f8..9da0c19 100644 --- a/sample_project/app/admin.py +++ b/sample_project/app/admin.py @@ -26,8 +26,8 @@ class ComponentInline(SortableStackedInline): # ) model = Component - def queryset(self, request): - qs = super(ComponentInline, self).queryset( + 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 @@ -37,14 +37,14 @@ class ComponentInline(SortableStackedInline): class WidgetAdmin(SortableAdmin): - def queryset(self, request): + 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).queryset(request) + qs = super(WidgetAdmin, self).get_queryset(request) return qs.filter(id__lte=3) inlines = [ComponentInline] From 356d88dfde42b1da67297213e1250ef47dbb4690 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 15:09:17 +0100 Subject: [PATCH 06/20] Remove ordering field lookup fallback --- adminsortable/admin.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/adminsortable/admin.py b/adminsortable/admin.py index cd868b7..4594467 100644 --- a/adminsortable/admin.py +++ b/adminsortable/admin.py @@ -183,9 +183,6 @@ class SortableAdmin(SortableAdminBase, ModelAdmin): try: order_field_name = opts.model._meta.ordering[0] - except (AttributeError, IndexError): - # for Django 1.5.x - order_field_name = opts.ordering[0] except (AttributeError, IndexError): order_field_name = 'order' From 8b88dfa9cdd714264f7836e84d79951b70e22835 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 15:13:13 +0100 Subject: [PATCH 07/20] Remove pointless fallback code (it just repeats the previously failed statement) --- adminsortable/admin.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/adminsortable/admin.py b/adminsortable/admin.py index 4594467..dd12f8c 100644 --- a/adminsortable/admin.py +++ b/adminsortable/admin.py @@ -82,12 +82,7 @@ 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.model_name + info = self.model._meta.app_label, self.model._meta.model_name # this ajax view changes the order of instances of the model type admin_do_sorting_url = url( From b0df1ff70123dd80138deb4bcbe1a7ba2647b87e Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 15:19:26 +0100 Subject: [PATCH 08/20] Remove south_field_triple method as South does not support Django >= 1.7 --- adminsortable/fields.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/adminsortable/fields.py b/adminsortable/fields.py index 380f555..3376f12 100644 --- a/adminsortable/fields.py +++ b/adminsortable/fields.py @@ -7,14 +7,4 @@ class SortableForeignKey(ForeignKey): This field replaces previous functionality where `sortable_by` was defined as a model property that specified another model class. """ - - def south_field_triple(self): - try: - from south.modelsinspector import introspector - cls_name = '{0}.{1}'.format( - self.__class__.__module__, - self.__class__.__name__) - args, kwargs = introspector(self) - return cls_name, args, kwargs - except ImportError: - pass + pass From 209a280ce8567502f6b1a3da7eea075c3d3122bf Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 15:25:21 +0100 Subject: [PATCH 09/20] Remove (seemingly broken) TemplateSyntaxError import fallback --- .../templatetags/django_template_additions.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/adminsortable/templatetags/django_template_additions.py b/adminsortable/templatetags/django_template_additions.py index c05f349..1866b68 100644 --- a/adminsortable/templatetags/django_template_additions.py +++ b/adminsortable/templatetags/django_template_additions.py @@ -2,11 +2,6 @@ from itertools import groupby import django from django import template -try: - from django import TemplateSyntaxError -except ImportError: - #support for django 1.3 - from django.template.base import TemplateSyntaxError register = template.Library() @@ -64,14 +59,15 @@ def dynamic_regroup(parser, token): """ firstbits = token.contents.split(None, 3) if len(firstbits) != 4: - raise TemplateSyntaxError("'regroup' tag takes five arguments") + raise template.TemplateSyntaxError("'regroup' tag takes five arguments") target = parser.compile_filter(firstbits[1]) if firstbits[2] != 'by': - raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") + raise template.TemplateSyntaxError( + "second argument to 'regroup' tag must be 'by'") lastbits_reversed = firstbits[3][::-1].split(None, 2) if lastbits_reversed[1][::-1] != 'as': - raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" - " be 'as'") + raise template.TemplateSyntaxError( + "next-to-last argument to 'regroup' tag must be 'as'") expression = lastbits_reversed[2][::-1] var_name = lastbits_reversed[0][::-1] From 9997644929f15a875cdc7410ed5d70069357ec36 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 15:33:48 +0100 Subject: [PATCH 10/20] Run uuid field tests unconditional --- sample_project/app/tests.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/sample_project/app/tests.py b/sample_project/app/tests.py index e573ebb..ab23c0f 100644 --- a/sample_project/app/tests.py +++ b/sample_project/app/tests.py @@ -3,12 +3,8 @@ try: except ImportError: import http.client as httplib -from django import VERSION - -if VERSION > (1, 8): - import uuid - import json +import uuid from django import VERSION from django.contrib.auth.models import User @@ -33,13 +29,12 @@ class TestSortableModel(SortableMixin): return self.title -if VERSION > (1, 8): - class TestNonAutoFieldModel(SortableMixin): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - order = models.PositiveIntegerField(editable=False, db_index=True) +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 Meta: + ordering = ['order'] class SortableTestCase(TestCase): From 28fe5d14608c0b3bb2de813bc1d32c6610dd92e8 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 15:37:37 +0100 Subject: [PATCH 11/20] Run non auto field model test unconditional --- sample_project/app/tests.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sample_project/app/tests.py b/sample_project/app/tests.py index ab23c0f..b640a90 100644 --- a/sample_project/app/tests.py +++ b/sample_project/app/tests.py @@ -6,7 +6,6 @@ except ImportError: import json import uuid -from django import VERSION from django.contrib.auth.models import User from django.db import models from django.test import TestCase @@ -312,8 +311,5 @@ class SortableTestCase(TestCase): self.assertEqual(notes, expected_notes) def test_save_non_auto_field_model(self): - if VERSION > (1, 8): - model = TestNonAutoFieldModel() - model.save() - else: - pass + model = TestNonAutoFieldModel() + model.save() From 7262cb656415aea2d754de2a5346231bd62a4a7c Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 15:39:59 +0100 Subject: [PATCH 12/20] Remove unused utils module --- sample_project/sample_project/utils.py | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 sample_project/sample_project/utils.py diff --git a/sample_project/sample_project/utils.py b/sample_project/sample_project/utils.py deleted file mode 100644 index 4178ad9..0000000 --- a/sample_project/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('\\', '/') From 9d3c958365379dd4f1b3cc1b9470ac69b83cc615 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 15:43:17 +0100 Subject: [PATCH 13/20] Remove deprecated template settings --- sample_project/sample_project/settings.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/sample_project/sample_project/settings.py b/sample_project/sample_project/settings.py index ceb7009..9fabb27 100644 --- a/sample_project/sample_project/settings.py +++ b/sample_project/sample_project/settings.py @@ -8,7 +8,6 @@ def map_path(directory_name): DEBUG = True -TEMPLATE_DEBUG = DEBUG ADMINS = ( # ('Your Name', 'your_email@example.com'), @@ -91,12 +90,6 @@ STATICFILES_FINDERS = ( # Make this unique, and don't share it with anybody. SECRET_KEY = '8**a!c8$1x)p@j2pj0yq!*v+dzp24g*$918ws#x@k+gf%0%rct' -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -) - MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -112,14 +105,12 @@ ROOT_URLCONF = 'sample_project.urls' # Python dotted path to the WSGI application used by Django's runserver. WSGI_APPLICATION = 'sample_project.wsgi.application' -TEMPLATE_DIRS = ( - map_path('templates'), -) - TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': TEMPLATE_DIRS, + 'DIRS': [ + map_path('templates') + ], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ From b8bfbe0c9a414a30107fb86c2cd850319e7d1458 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 15:48:10 +0100 Subject: [PATCH 14/20] Explicitly set on_delete=models.CASCADE on ForeignKey as it will be required in Django 2.0 --- sample_project/app/models.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/sample_project/app/models.py b/sample_project/app/models.py index 81e6606..4036064 100644 --- a/sample_project/app/models.py +++ b/sample_project/app/models.py @@ -45,7 +45,7 @@ class Project(SimpleModel, SortableMixin): class Meta: ordering = ['order'] - category = SortableForeignKey(Category) + category = SortableForeignKey(Category, on_delete=models.CASCADE) description = models.TextField() order = models.PositiveIntegerField(default=0, editable=False) @@ -57,7 +57,7 @@ class Credit(SortableMixin): class Meta: ordering = ['order'] - project = models.ForeignKey(Project) + 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") @@ -73,7 +73,7 @@ class Note(SortableMixin): class Meta: ordering = ['order'] - project = models.ForeignKey(Project) + project = models.ForeignKey(Project, on_delete=models.CASCADE) text = models.CharField(max_length=100) order = models.PositiveIntegerField(default=0, editable=False) @@ -85,7 +85,7 @@ class Note(SortableMixin): # Registered as a tabular inline on `Project` which can't be sorted @python_2_unicode_compatible class NonSortableCredit(models.Model): - project = models.ForeignKey(Project) + 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") @@ -96,7 +96,7 @@ class NonSortableCredit(models.Model): # Registered as a stacked inline on `Project` which can't be sorted @python_2_unicode_compatible class NonSortableNote(models.Model): - project = models.ForeignKey(Project) + project = models.ForeignKey(Project, on_delete=models.CASCADE) text = models.CharField(max_length=100) def __str__(self): @@ -106,7 +106,7 @@ class NonSortableNote(models.Model): # A generic bound model @python_2_unicode_compatible class GenericNote(SimpleModel, SortableMixin): - content_type = models.ForeignKey(ContentType, + 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', @@ -127,7 +127,7 @@ class Component(SimpleModel, SortableMixin): class Meta: ordering = ['order'] - widget = SortableForeignKey(Widget) + widget = SortableForeignKey(Widget, on_delete=models.CASCADE) order = models.PositiveIntegerField(default=0, editable=False) @@ -177,7 +177,8 @@ class SortableCategoryWidget(SimpleModel, SortableMixin): verbose_name = 'Sortable Category Widget' verbose_name_plural = 'Sortable Category Widgets' - non_sortable_category = SortableForeignKey(NonSortableCategory) + non_sortable_category = SortableForeignKey( + NonSortableCategory, on_delete=models.CASCADE) order = models.PositiveIntegerField(default=0, editable=False) @@ -191,7 +192,8 @@ class SortableNonInlineCategory(SimpleModel, SortableMixin): that is *not* sortable, and is also not defined as an inline of the SortableForeignKey field.""" - non_sortable_category = SortableForeignKey(NonSortableCategory) + non_sortable_category = SortableForeignKey( + NonSortableCategory, on_delete=models.CASCADE) order = models.PositiveIntegerField(default=0, editable=False) @@ -223,7 +225,7 @@ class CustomWidget(SortableMixin, SimpleModel): @python_2_unicode_compatible class CustomWidgetComponent(SortableMixin, SimpleModel): - custom_widget = models.ForeignKey(CustomWidget) + custom_widget = models.ForeignKey(CustomWidget, on_delete=models.CASCADE) # custom field for ordering widget_order = models.PositiveIntegerField(default=0, db_index=True, From defaf45e9a7729da94b63a85e3d9a0f290c7e21c Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 15:57:28 +0100 Subject: [PATCH 15/20] Make it clear why the httplib import is guarded by a try except and has a fallback --- sample_project/app/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sample_project/app/tests.py b/sample_project/app/tests.py index b640a90..87d74c1 100644 --- a/sample_project/app/tests.py +++ b/sample_project/app/tests.py @@ -1,7 +1,7 @@ try: - import httplib + import httplib # Python 2 except ImportError: - import http.client as httplib + import http.client as httplib # Python 3 import json import uuid From e5adadaefcd5d30a69d343f7404bb21410ae8414 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 15:58:01 +0100 Subject: [PATCH 16/20] Use the newer MIDDLEWARE setting but fall back to MIDDLEWARE_CLASSES on Django < 1.10 --- sample_project/sample_project/settings.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sample_project/sample_project/settings.py b/sample_project/sample_project/settings.py index 9fabb27..7327e75 100644 --- a/sample_project/sample_project/settings.py +++ b/sample_project/sample_project/settings.py @@ -1,6 +1,8 @@ # Django settings for test_project project. import os +import django + def map_path(directory_name): return os.path.join(os.path.dirname(__file__), @@ -90,15 +92,19 @@ STATICFILES_FINDERS = ( # Make this unique, and don't share it with anybody. SECRET_KEY = '8**a!c8$1x)p@j2pj0yq!*v+dzp24g*$918ws#x@k+gf%0%rct' -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = [ 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', # Uncomment the next line for simple clickjacking protection: 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) +] + +if django.VERSION < (1, 10): + MIDDLEWARE_CLASSES = MIDDLEWARE ROOT_URLCONF = 'sample_project.urls' From a5eca5ea4dc9b17c73229c40daca363b8a89619b Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 15:58:20 +0100 Subject: [PATCH 17/20] assertEquals is deprecated in Python 3 --- sample_project/app/tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sample_project/app/tests.py b/sample_project/app/tests.py index 87d74c1..3dd3b7c 100644 --- a/sample_project/app/tests.py +++ b/sample_project/app/tests.py @@ -108,7 +108,7 @@ class SortableTestCase(TestCase): 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, httplib.OK, + self.assertEqual(response.status_code, httplib.OK, 'Unable to reach sort view.') def make_test_categories(self): @@ -251,7 +251,7 @@ class SortableTestCase(TestCase): self.client.login(username=self.user.username, password=self.user_raw_password) response = self.client.get('/admin/app/project/sort/') - self.assertEquals(response.status_code, httplib.OK, + self.assertEqual(response.status_code, httplib.OK, 'Unable to reach sort view.') def test_adminsortable_change_list_view_permission_denied(self): @@ -261,8 +261,8 @@ class SortableTestCase(TestCase): 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.') + 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, From aa9c4a60631cfe3994e2fa909b1e891dc8dbe260 Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 16:03:43 +0100 Subject: [PATCH 18/20] User.is_authenticated is a property from Django 1.10 and onward --- sample_project/app/tests.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sample_project/app/tests.py b/sample_project/app/tests.py index 3dd3b7c..0278331 100644 --- a/sample_project/app/tests.py +++ b/sample_project/app/tests.py @@ -6,6 +6,8 @@ except ImportError: import json import uuid +import django + from django.contrib.auth.models import User from django.db import models from django.test import TestCase @@ -73,8 +75,12 @@ class SortableTestCase(TestCase): return category def test_new_user_is_authenticated(self): - self.assertEqual(self.user.is_authenticated(), True, - 'User is not authenticated') + 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') From de6a768cebf1af523e75544fdf2abadc1586c9cb Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 16:07:13 +0100 Subject: [PATCH 19/20] =?UTF-8?q?No=20need=20to=20use=20include=20for=20ad?= =?UTF-8?q?min.site.urls=20(it=E2=80=99s=20deprecated)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sample_project/sample_project/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample_project/sample_project/urls.py b/sample_project/sample_project/urls.py index 5abb2fc..3e2dcfe 100644 --- a/sample_project/sample_project/urls.py +++ b/sample_project/sample_project/urls.py @@ -14,5 +14,5 @@ urlpatterns = [ url(r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), ] From 49a7c41896b7fffc4f9c6d0be64614c96f49387a Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Thu, 16 Mar 2017 16:13:34 +0100 Subject: [PATCH 20/20] Use remote_field.model in favor of rel.to when possible --- adminsortable/admin.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/adminsortable/admin.py b/adminsortable/admin.py index dd12f8c..539d2b1 100644 --- a/adminsortable/admin.py +++ b/adminsortable/admin.py @@ -141,7 +141,11 @@ class SortableAdmin(SortableAdminBase, ModelAdmin): for field in self.model._meta.fields: if isinstance(field, SortableForeignKey): - sortable_by_fk = field.rel.to + try: + sortable_by_fk = field.remote_field.model + except AttributeError: + # Django < 1.9 + sortable_by_fk = field.rel.to sortable_by_field_name = field.name.lower() sortable_by_class_is_sortable = sortable_by_fk.objects.count() >= 2