From f125a6995f1050dbfb7e0f54cedfad9187523154 Mon Sep 17 00:00:00 2001 From: Mark Walker Date: Sun, 11 Apr 2021 00:17:01 +0100 Subject: [PATCH 1/5] Extend test suite to django 3.2 --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index 91cb03c..5f72661 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ env: - DJANGO="django<2.3" - DJANGO="django<3.1" - DJANGO="django<3.2" + - DJANGO="django<3.3" - DJANGO='https://github.com/django/django/archive/master.tar.gz' install: @@ -37,6 +38,8 @@ matrix: env: DJANGO="django<3.1" - python: 2.7 env: DJANGO="django<3.2" + - python: 2.7 + env: DJANGO="django<3.3" - python: 2.7 env: DJANGO='https://github.com/django/django/archive/master.tar.gz' - python: 3.4 @@ -59,5 +62,11 @@ matrix: env: DJANGO="django<2.0" - python: 3.8 env: DJANGO="django<2.1" + - python: 3.8 + env: DJANGO="django<3.1" + - python: 3.8 + env: DJANGO="django<3.2" + - python: 3.8 + env: DJANGO="django<3.3" allow_failures: - env: DJANGO='https://github.com/django/django/archive/master.tar.gz' From 54c0c60c7c0b0b1ad4ea7d065d5d61b2b0667a63 Mon Sep 17 00:00:00 2001 From: Mark Walker Date: Sun, 11 Apr 2021 23:44:06 +0100 Subject: [PATCH 2/5] Extend package to allow django 3.2 --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a6e87d9..83693be 100644 --- a/setup.py +++ b/setup.py @@ -3,10 +3,12 @@ from setuptools import setup, find_packages from adminsortable2 import __version__ + def readfile(filename): with io.open(filename, encoding='utf-8') as fd: return fd.read() + DESCRIPTION = 'Generic drag-and-drop sorting for the List, the Stacked- and the Tabular-Inlines Views in the Django Admin' CLASSIFIERS = [ @@ -27,6 +29,7 @@ def readfile(filename): 'Framework :: Django :: 2.2', 'Framework :: Django :: 3.0', 'Framework :: Django :: 3.1', + 'Framework :: Django :: 3.2', ] @@ -44,7 +47,7 @@ def readfile(filename): platforms=['OS Independent'], classifiers=CLASSIFIERS, install_requires=[ - 'Django>=1.8,<3.2', + 'Django>=1.8,<3.3', ], packages=find_packages(exclude=['example', 'docs']), include_package_data=True, From 8f8ea968782690ac2bd2aef302d3f6c0dd0fd3c2 Mon Sep 17 00:00:00 2001 From: Mark Walker Date: Thu, 15 Apr 2021 14:05:49 +0100 Subject: [PATCH 3/5] Remove deprecated `url()` in favour of `path()` and `re_path()` --- .gitignore | 2 ++ adminsortable2/admin.py | 8 +++++--- example/testapp/urls.py | 4 ++-- parler_example/urls.py | 6 +++--- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 818880a..811e497 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ .DS_Store .tmp* .tox +.venv +env local_settings.py build docs/_build diff --git a/adminsortable2/admin.py b/adminsortable2/admin.py index 6ac7c9a..25540cc 100644 --- a/adminsortable2/admin.py +++ b/adminsortable2/admin.py @@ -12,7 +12,7 @@ else: from django.utils.translation import ugettext_lazy as _ from django.utils.safestring import mark_safe -from django.conf.urls import url +from django.urls import path from django.contrib import admin, messages from django.core.exceptions import ImproperlyConfigured from django.core.paginator import EmptyPage @@ -145,9 +145,11 @@ def _get_update_url_name(self): def get_urls(self): my_urls = [ - url(r'^adminsortable2_update/$', + path( + 'adminsortable2_update/', self.admin_site.admin_view(self.update_order), - name=self._get_update_url_name()), + name=self._get_update_url_name() + ), ] return my_urls + super().get_urls() diff --git a/example/testapp/urls.py b/example/testapp/urls.py index d69c573..1fd335a 100644 --- a/example/testapp/urls.py +++ b/example/testapp/urls.py @@ -1,8 +1,8 @@ -from django.conf.urls import url +from django.urls import path from django.contrib import admin admin.autodiscover() urlpatterns = [ - url(r'^admin/', admin.site.urls), + path(r'^admin/', admin.site.urls), ] diff --git a/parler_example/urls.py b/parler_example/urls.py index 28af331..fa98fbf 100644 --- a/parler_example/urls.py +++ b/parler_example/urls.py @@ -1,10 +1,10 @@ -from django.conf.urls import url from django.contrib import admin +from django.urls import path, re_path from django.views.generic import RedirectView admin.autodiscover() urlpatterns = [ - url(r'^admin/', admin.site.urls), - url(r'^.*$', RedirectView.as_view(url='/admin/')), + path('admin/', admin.site.urls), + re_path(r'^.*$', RedirectView.as_view(url='/admin/')), ] From b8f90f3075cb12ccba40eacf3c307201420ccc93 Mon Sep 17 00:00:00 2001 From: Mark Walker Date: Mon, 19 Apr 2021 23:21:10 +0100 Subject: [PATCH 4/5] Release prep & flake8 ran --- .gitignore | 1 + adminsortable2/__init__.py | 2 +- adminsortable2/admin.py | 275 ++++++++++++------ adminsortable2/management/commands/reorder.py | 15 +- docs/source/changelog.rst | 2 +- 5 files changed, 196 insertions(+), 99 deletions(-) diff --git a/.gitignore b/.gitignore index 811e497..79bda0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea *.log *.pot *.pyc diff --git a/adminsortable2/__init__.py b/adminsortable2/__init__.py index a62e53d..7e49527 100644 --- a/adminsortable2/__init__.py +++ b/adminsortable2/__init__.py @@ -1 +1 @@ -__version__ = '0.7.8' +__version__ = '1.0' diff --git a/adminsortable2/admin.py b/adminsortable2/admin.py index 25540cc..70cb052 100644 --- a/adminsortable2/admin.py +++ b/adminsortable2/admin.py @@ -4,40 +4,32 @@ from types import MethodType from django import forms -from django.contrib.admin.views.main import ORDER_VAR -# Remove check when support for python < 3 is dropped. -import sys -if sys.version_info[0] >= 3: - from django.utils.translation import gettext_lazy as _ -else: - from django.utils.translation import ugettext_lazy as _ -from django.utils.safestring import mark_safe -from django.urls import path from django.contrib import admin, messages +from django.contrib.admin.views.main import ORDER_VAR +from django.contrib.contenttypes.admin import ( + GenericStackedInline, GenericTabularInline +) +from django.contrib.contenttypes.forms import ( + BaseGenericInlineFormSet, +) +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ImproperlyConfigured from django.core.paginator import EmptyPage from django.core.serializers.json import DjangoJSONEncoder -try: - from django.urls import reverse -except ImportError: # Django<1.11 - from django.core.urlresolvers import reverse from django.db import router, transaction from django.db.models.aggregates import Max from django.db.models.expressions import F from django.db.models.functions import Coalesce from django.db.models.signals import post_save, pre_save -from django.forms.models import BaseInlineFormSet from django.forms import widgets +from django.forms.models import BaseInlineFormSet from django.http import ( HttpResponse, HttpResponseBadRequest, - HttpResponseNotAllowed, HttpResponseForbidden) -from django.contrib.contenttypes.models import ContentType -from django.contrib.contenttypes.forms import ( - BaseGenericInlineFormSet, -) -from django.contrib.contenttypes.admin import ( - GenericStackedInline, GenericTabularInline + HttpResponseNotAllowed, HttpResponseForbidden ) +from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ +from django.urls import path, reverse __all__ = ['SortableAdminMixin', 'SortableInlineAdminMixin'] @@ -55,8 +47,10 @@ def _get_default_ordering(model, model_admin): # then try with the model ordering none, prefix, field_name = model._meta.ordering[0].rpartition('-') except (AttributeError, IndexError): - msg = "Model {0}.{1} requires a list or tuple 'ordering' in its Meta class" - raise ImproperlyConfigured(msg.format(model.__module__, model.__name__)) + raise ImproperlyConfigured( + f"Model {model.__module__}.{model.__name__} requires a list or " + f"tuple 'ordering' in its Meta class" + ) else: return prefix, field_name @@ -66,14 +60,16 @@ class MovePageActionForm(admin.helpers.ActionForm): required=False, initial=1, widget=widgets.NumberInput(attrs={'id': 'changelist-form-step'}), - label=False) + label=False + ) page = forms.IntegerField( required=False, widget=widgets.NumberInput(attrs={'id': 'changelist-form-page'}), - label=False) + label=False + ) -class SortableAdminBase(object): +class SortableAdminBase: @property def media(self): css = {'all': ['adminsortable2/css/sortable.css']} @@ -98,13 +94,17 @@ def change_list_template(self): opts = self.model._meta app_label = opts.app_label return [ - os.path.join('adminsortable2', app_label, opts.model_name, 'change_list.html'), + os.path.join( + 'adminsortable2', app_label, opts.model_name, + 'change_list.html' + ), os.path.join('adminsortable2', app_label, 'change_list.html'), 'adminsortable2/change_list.html' ] def __init__(self, model, admin_site): - self.default_order_direction, self.default_order_field = _get_default_ordering(model, self) + self.default_order_direction, self.default_order_field = \ + _get_default_ordering(model, self) super().__init__(model, admin_site) self.enable_sorting = False self.order_by = None @@ -112,7 +112,8 @@ def __init__(self, model, admin_site): self.exclude = [self.default_order_field] elif not self.exclude or self.default_order_field != self.exclude[0]: self.exclude = [self.default_order_field] + list(self.exclude) - if isinstance(self.list_display_links, (list, tuple)) and len(self.list_display_links) == 0: + if isinstance(self.list_display_links, (list, tuple)) and \ + len(self.list_display_links) == 0: self.list_display_links = [self.list_display[0]] self._add_reorder_method() self.list_display = list(self.list_display) @@ -120,28 +121,39 @@ def __init__(self, model, admin_site): # Insert the magic field into the same position as the first occurrence # of the default_order_field, or, if not present, at the start try: - self.default_order_field_index = self.list_display.index(self.default_order_field) + self.default_order_field_index = self.list_display.index( + self.default_order_field + ) except ValueError: self.default_order_field_index = 0 self.list_display.insert(self.default_order_field_index, '_reorder') # Remove *all* occurrences of the field from `list_display` if self.list_display and self.default_order_field in self.list_display: - self.list_display = [f for f in self.list_display if f != self.default_order_field] + self.list_display = [ + f for f in self.list_display if f != self.default_order_field + ] # Remove *all* occurrences of the field from `list_display_links` - if self.list_display_links and self.default_order_field in self.list_display_links: - self.list_display_links = [f for f in self.list_display_links if f != self.default_order_field] + if self.list_display_links and \ + self.default_order_field in self.list_display_links: + self.list_display_links = [ + f for f in self.list_display_links if + f != self.default_order_field + ] # Remove *all* occurrences of the field from `ordering` if self.ordering and self.default_order_field in self.ordering: - self.ordering = [f for f in self.ordering if f != self.default_order_field] + self.ordering = [ + f for f in self.ordering if f != self.default_order_field + ] rev_field = '-' + self.default_order_field if self.ordering and rev_field in self.ordering: self.ordering = [f for f in self.ordering if f != rev_field] def _get_update_url_name(self): - return '%s_%s_sortable_update' % (self.model._meta.app_label, self.model._meta.model_name) + return f'{self.model._meta.app_label}_{self.model._meta.model_name}' \ + f'_sortable_update' def get_urls(self): my_urls = [ @@ -155,35 +167,43 @@ def get_urls(self): def get_actions(self, request): actions = super().get_actions(request) - paginator = self.get_paginator(request, self.get_queryset(request), self.list_per_page) - if len(paginator.page_range) > 1 and 'all' not in request.GET and self.enable_sorting: + paginator = self.get_paginator( + request, self.get_queryset(request), self.list_per_page + ) + if len(paginator.page_range) > 1 and \ + 'all' not in request.GET and self.enable_sorting: # add actions for moving items to other pages move_actions = ['move_to_exact_page'] cur_page = int(request.GET.get('p', 0)) + 1 - if len(paginator.page_range) > 2 and cur_page > paginator.page_range[1]: + if len(paginator.page_range) > 2 and \ + cur_page > paginator.page_range[1]: move_actions.append('move_to_first_page') if cur_page > paginator.page_range[0]: move_actions.append('move_to_back_page') if cur_page < paginator.page_range[-1]: move_actions.append('move_to_forward_page') - if len(paginator.page_range) > 2 and cur_page < paginator.page_range[-2]: + if len(paginator.page_range) > 2 and \ + cur_page < paginator.page_range[-2]: move_actions.append('move_to_last_page') for fname in move_actions: actions.update({fname: self.get_action(fname)}) return actions def get_changelist(self, request, **kwargs): - first_order_direction, first_order_field_index = self._get_first_ordering(request) + first_order_direction, first_order_field_index = \ + self._get_first_ordering(request) if first_order_field_index == self.default_order_field_index: self.enable_sorting = True - self.order_by = "{}{}".format(first_order_direction, self.default_order_field) + self.order_by = \ + f"{first_order_direction}{self.default_order_field}" else: self.enable_sorting = False return super().get_changelist(request, **kwargs) def _get_first_ordering(self, request): """ - Must be consistent with `django.contrib.admin.views.main.ChangeList.get_ordering`. + Must be consistent with + `django.contrib.admin.views.main.ChangeList.get_ordering`. """ order_var = request.GET.get(ORDER_VAR) if order_var is None: @@ -216,19 +236,24 @@ def media(self): def _add_reorder_method(self): """ - Adds a bound method, named '_reorder' to the current instance of this class, with attributes - allow_tags, short_description and admin_order_field. - This can only be done using a function, since it is not possible to add dynamic attributes - to bound methods. + Adds a bound method, named '_reorder' to the current instance of + this class, with attributes allow_tags, short_description and + admin_order_field. + This can only be done using a function, since it is not possible + to add dynamic attributes to bound methods. """ def func(this, item): html = '' if this.enable_sorting: - html = '
 
'.format(getattr(item, this.default_order_field), item.pk) + html = '
' \ + ' 
'.format( + getattr(item, this.default_order_field), item.pk + ) return mark_safe(html) setattr(func, 'allow_tags', True) - # if the field used for ordering has a verbose name use it, otherwise default to "Sort" + # if the field used for ordering has a verbose name use it, + # otherwise default to "Sort" for order_field in self.model._meta.fields: if order_field.name == self.default_order_field: short_description = getattr(order_field, 'verbose_name', None) @@ -246,15 +271,23 @@ def update_order(self, request): if request.method != 'POST': return HttpResponseNotAllowed('Must be a POST request') if not self.has_change_permission(request): - return HttpResponseForbidden('Missing permissions to perform this request') + return HttpResponseForbidden( + 'Missing permissions to perform this request' + ) startorder = int(request.POST.get('startorder')) endorder = int(request.POST.get('endorder', 0)) moved_items = list(self._move_item(request, startorder, endorder)) - return HttpResponse(json.dumps(moved_items, cls=DjangoJSONEncoder), content_type='application/json;charset=UTF-8') + return HttpResponse( + json.dumps(moved_items, cls=DjangoJSONEncoder), + content_type='application/json;charset=UTF-8' + ) def save_model(self, request, obj, form, change): if not change: - setattr(obj, self.default_order_field, self.get_max_order(request, obj) + 1) + setattr( + obj, self.default_order_field, + self.get_max_order(request, obj) + 1 + ) super().save_model(request, obj, form, change) def move_to_exact_page(self, request, queryset): @@ -267,7 +300,9 @@ def move_to_back_page(self, request, queryset): def move_to_forward_page(self, request, queryset): self._bulk_move(request, queryset, self.FORWARD) - move_to_forward_page.short_description = _('Move selected ... pages forward') + move_to_forward_page.short_description = _( + 'Move selected ... pages forward' + ) def move_to_first_page(self, request, queryset): self._bulk_move(request, queryset, self.FIRST) @@ -281,21 +316,21 @@ def _move_item(self, request, startorder, endorder): extra_model_filters = self.get_extra_model_filters(request) return self.move_item(startorder, endorder, extra_model_filters) - def move_item(self, startorder, endorder, extra_model_filters = None): + def move_item(self, startorder, endorder, extra_model_filters=None): model = self.model rank_field = self.default_order_field - if endorder < startorder: # Drag up + if endorder < startorder: # Drag up move_filter = { - '{0}__gte'.format(rank_field): endorder, - '{0}__lte'.format(rank_field): startorder - 1, + f'{rank_field}__gte': endorder, + f'{rank_field}__lte': startorder - 1, } move_delta = +1 - order_by = '-{0}'.format(rank_field) - elif endorder > startorder: # Drag down + order_by = f'-{rank_field}' + elif endorder > startorder: # Drag down move_filter = { - '{0}__gte'.format(rank_field): startorder + 1, - '{0}__lte'.format(rank_field): endorder, + f'{rank_field}__gte': startorder + 1, + f'{rank_field}__lte': endorder, } move_delta = -1 order_by = rank_field @@ -311,17 +346,24 @@ def move_item(self, startorder, endorder, extra_model_filters = None): try: obj = model.objects.get(**obj_filters) except model.MultipleObjectsReturned: - msg = "Detected non-unique values in field '{0}' used for sorting this model.\nConsider to run \n"\ - " python manage.py reorder {1}\n"\ - "to adjust this inconsistency." + # noinspection PyProtectedMember - raise model.MultipleObjectsReturned(msg.format(rank_field, model._meta.label)) + raise model.MultipleObjectsReturned( + "Detected non-unique values in field '{rank_field}' used " + "for sorting this model.\nConsider to run \n" + " python manage.py reorder {model._meta.label}\n" + "to adjust this inconsistency." + ) move_qs = model.objects.filter(**move_filter).order_by(order_by) move_objs = list(move_qs) for instance in move_objs: - setattr(instance, rank_field, getattr(instance, rank_field) + move_delta) - # Do not run `instance.save()`, because it will be updated later in bulk by `move_qs.update`. + setattr( + instance, rank_field, + getattr(instance, rank_field) + move_delta + ) + # Do not run `instance.save()`, because it will be updated + # later in bulk by `move_qs.update`. pre_save.send( model, instance=instance, @@ -343,16 +385,22 @@ def move_item(self, startorder, endorder, extra_model_filters = None): setattr(obj, rank_field, endorder) obj.save(update_fields=[rank_field]) - return [{'pk': instance.pk, 'order': getattr(instance, rank_field)} for instance in chain(move_objs, [obj])] + return [{ + 'pk': instance.pk, + 'order': getattr(instance, rank_field) + } for instance in chain(move_objs, [obj])] - def get_extra_model_filters(self, request): + @staticmethod + def get_extra_model_filters(request): """ Returns additional fields to filter sortable objects """ return {} def get_max_order(self, request, obj=None): - return self.model.objects.aggregate(max_order=Coalesce(Max(self.default_order_field), 0))['max_order'] + return self.model.objects.aggregate( + max_order=Coalesce(Max(self.default_order_field), 0) + )['max_order'] def _bulk_move(self, request, queryset, method): if not self.enable_sorting: @@ -378,7 +426,8 @@ def _bulk_move(self, request, queryset, method): raise Exception('Invalid method') if target_page_number == current_page_number: - # If you want the selected items to be moved to the start of the current page, then just do not return here. + # If you want the selected items to be moved to the start + # of the current page, then just do not return here. return try: @@ -390,13 +439,22 @@ def _bulk_move(self, request, queryset, method): queryset_size = queryset.count() page_size = page.end_index() - page.start_index() + 1 if queryset_size > page_size: - msg = _("The target page size is {}. It is too small for {} items.").format(page_size, queryset_size) + msg = _( + f"The target page size is {page_size}. " + f"It is too small for {queryset_size} items." + ) self.message_user(request, msg, level=messages.ERROR) return - endorders_start = getattr(objects[page.start_index() - 1], self.default_order_field) + endorders_start = getattr( + objects[page.start_index() - 1], self.default_order_field + ) endorders_step = -1 if self.order_by.startswith('-') else 1 - endorders = range(endorders_start, endorders_start + endorders_step * queryset_size, endorders_step) + endorders = range( + endorders_start, + endorders_start + endorders_step * queryset_size, + endorders_step + ) if page.number > current_page_number: # Move forward (like drag down) queryset = queryset.reverse() @@ -418,7 +476,7 @@ def get_update_url(self, request): """ Returns a callback URL used for updating items via AJAX drag-n-drop """ - return reverse('{}:{}'.format(self.admin_site.name, self._get_update_url_name())) + return reverse(f'{self.admin_site.name}:{self._get_update_url_name()}') class PolymorphicSortableAdminMixin(SortableAdminMixin): @@ -429,32 +487,44 @@ class PolymorphicSortableAdminMixin(SortableAdminMixin): ``SortableAdminMixin``. """ def get_max_order(self, request, obj=None): - return self.base_model.objects.aggregate(max_order=Coalesce(Max(self.default_order_field), 0))['max_order'] + return self.base_model.objects.aggregate( + max_order=Coalesce(Max(self.default_order_field), 0) + )['max_order'] -class CustomInlineFormSetMixin(): +class CustomInlineFormSetMixin: def __init__(self, *args, **kwargs): - self.default_order_direction, self.default_order_field = _get_default_ordering(self.model, self) + self.default_order_direction, self.default_order_field = \ + _get_default_ordering(self.model, self) if self.default_order_field not in self.form.base_fields: - self.form.base_fields[self.default_order_field] = self.model._meta.get_field(self.default_order_field).formfield() + self.form.base_fields[self.default_order_field] = \ + self.model._meta.get_field( + self.default_order_field + ).formfield() self.form.base_fields[self.default_order_field].is_hidden = True self.form.base_fields[self.default_order_field].required = False - self.form.base_fields[self.default_order_field].widget = widgets.HiddenInput() + self.form.base_fields[self.default_order_field].widget = \ + widgets.HiddenInput() super().__init__(*args, **kwargs) def get_max_order(self): - query_set = self.model.objects.filter(**{self.fk.get_attname(): self.instance.pk}) - return query_set.aggregate(max_order=Coalesce(Max(self.default_order_field), 0))['max_order'] + query_set = self.model.objects.filter( + **{self.fk.get_attname(): self.instance.pk} + ) + return query_set.aggregate( + max_order=Coalesce(Max(self.default_order_field), 0) + )['max_order'] def save_new(self, form, commit=True): """ - New objects do not have a valid value in their ordering field. On object save, add an order - bigger than all other order fields for the current parent_model. - Strange behaviour when field has a default, this might be evaluated on new object and the value - will be not None, but the default value. + New objects do not have a valid value in their ordering field. + On object save, add an order bigger than all other order fields + for the current parent_model. + Strange behaviour when field has a default, this might be evaluated + on new object and the value will be not None, but the default value. """ obj = super().save_new(form, commit=False) @@ -464,14 +534,17 @@ def save_new(self, form, commit=True): setattr(obj, self.default_order_field, max_order + 1) if commit: obj.save() - # form.save_m2m() can be called via the formset later on if commit=False + # form.save_m2m() can be called via the formset later on + # if commit=False if commit and hasattr(form, 'save_m2m'): form.save_m2m() return obj + class CustomInlineFormSet(CustomInlineFormSetMixin, BaseInlineFormSet): pass + class SortableInlineAdminMixin(SortableAdminBase): formset = CustomInlineFormSet @@ -485,8 +558,10 @@ def get_fields(self, request, obj=None): fields.append(default_order_field) elif fields[0] == default_order_field: """ - Remove the order field and add it again immediately to ensure it is not on first position. - This ensures that django's template for tabular inline renders the first column with colspan="2": + Remove the order field and add it again immediately to ensure it + is not on first position. + This ensures that django's template for tabular inline renders the + first column with colspan="2": ``` {% for field in inline_admin_formset.fields %} @@ -530,14 +605,28 @@ def template(self): return 'adminsortable2/stacked.html' elif self.is_tabular: return 'adminsortable2/tabular.html' - raise ImproperlyConfigured('Class {0}.{1} must also derive from admin.TabularInline or admin.StackedInline'.format(self.__module__, self.__class__)) + raise ImproperlyConfigured( + f'Class {self.__module__}.{self.__class__} must also derive ' + f'from admin.TabularInline or admin.StackedInline' + ) -class CustomGenericInlineFormSet(CustomInlineFormSetMixin, BaseGenericInlineFormSet): +class CustomGenericInlineFormSet(CustomInlineFormSetMixin, + BaseGenericInlineFormSet): def get_max_order(self): - query_set = self.model.objects.filter(**{self.ct_fk_field.name: self.instance.pk, - self.ct_field.name: ContentType.objects.get_for_model(self.instance, for_concrete_model=self.for_concrete_model)}) - return query_set.aggregate(max_order=Coalesce(Max(self.default_order_field), 0))['max_order'] + query_set = self.model.objects.filter( + **{ + self.ct_fk_field.name: self.instance.pk, + self.ct_field.name: ContentType.objects.get_for_model( + self.instance, + for_concrete_model=self.for_concrete_model + ) + } + ) + return query_set.aggregate( + max_order=Coalesce(Max(self.default_order_field), 0) + )['max_order'] + class SortableGenericInlineAdminMixin(SortableInlineAdminMixin): formset = CustomGenericInlineFormSet diff --git a/adminsortable2/management/commands/reorder.py b/adminsortable2/management/commands/reorder.py index 050d645..7c70d08 100644 --- a/adminsortable2/management/commands/reorder.py +++ b/adminsortable2/management/commands/reorder.py @@ -4,7 +4,8 @@ class Command(BaseCommand): args = '' - help = 'Restore the primary ordering fields of a model containing a special ordering field' + help = 'Restore the primary ordering fields of a model containing a ' \ + 'special ordering field' def add_arguments(self, parser): parser.add_argument('models', nargs='+', type=str) @@ -17,8 +18,12 @@ def handle(self, *args, **options): except ImportError: raise CommandError('Unable to load model "%s"' % modelname) - if not hasattr(Model._meta, 'ordering') or len(Model._meta.ordering) == 0: - raise CommandError('Model "{0}" does not define field "ordering" in its Meta class'.format(modelname)) + if not hasattr(Model._meta, 'ordering') or \ + len(Model._meta.ordering) == 0: + raise CommandError( + f'Model "{modelname}" does not define field "ordering" ' + f'in its Meta class' + ) orderfield = Model._meta.ordering[0] if orderfield[0] == '-': @@ -28,4 +33,6 @@ def handle(self, *args, **options): setattr(obj, orderfield, order) obj.save() - self.stdout.write('Successfully reordered model "{0}"'.format(modelname)) + self.stdout.write( + f'Successfully reordered model "{modelname}"' + ) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 588643e..f3d3fa7 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -5,7 +5,7 @@ Release history =============== -0.8 +1.0 --- * Drop support for Python-2.7, 3.4 and 3.5. * Drop support for Django-1.10, 1.11, 2.0 and 2.1. From 131934aba33856cdeb65bec8f0623410414d08a9 Mon Sep 17 00:00:00 2001 From: Mark Walker Date: Wed, 21 Apr 2021 01:44:19 +0100 Subject: [PATCH 5/5] flake8 ran with 119 line length --- adminsortable2/admin.py | 102 ++++++------------ adminsortable2/management/commands/reorder.py | 15 +-- 2 files changed, 36 insertions(+), 81 deletions(-) diff --git a/adminsortable2/admin.py b/adminsortable2/admin.py index 70cb052..bb4afa2 100644 --- a/adminsortable2/admin.py +++ b/adminsortable2/admin.py @@ -6,12 +6,8 @@ from django import forms from django.contrib import admin, messages from django.contrib.admin.views.main import ORDER_VAR -from django.contrib.contenttypes.admin import ( - GenericStackedInline, GenericTabularInline -) -from django.contrib.contenttypes.forms import ( - BaseGenericInlineFormSet, -) +from django.contrib.contenttypes.admin import GenericStackedInline, GenericTabularInline +from django.contrib.contenttypes.forms import BaseGenericInlineFormSet from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ImproperlyConfigured from django.core.paginator import EmptyPage @@ -48,8 +44,7 @@ def _get_default_ordering(model, model_admin): none, prefix, field_name = model._meta.ordering[0].rpartition('-') except (AttributeError, IndexError): raise ImproperlyConfigured( - f"Model {model.__module__}.{model.__name__} requires a list or " - f"tuple 'ordering' in its Meta class" + f"Model {model.__module__}.{model.__name__} requires a list or tuple 'ordering' in its Meta class" ) else: return prefix, field_name @@ -94,17 +89,13 @@ def change_list_template(self): opts = self.model._meta app_label = opts.app_label return [ - os.path.join( - 'adminsortable2', app_label, opts.model_name, - 'change_list.html' - ), + os.path.join('adminsortable2', app_label, opts.model_name, 'change_list.html'), os.path.join('adminsortable2', app_label, 'change_list.html'), 'adminsortable2/change_list.html' ] def __init__(self, model, admin_site): - self.default_order_direction, self.default_order_field = \ - _get_default_ordering(model, self) + self.default_order_direction, self.default_order_field = _get_default_ordering(model, self) super().__init__(model, admin_site) self.enable_sorting = False self.order_by = None @@ -112,8 +103,7 @@ def __init__(self, model, admin_site): self.exclude = [self.default_order_field] elif not self.exclude or self.default_order_field != self.exclude[0]: self.exclude = [self.default_order_field] + list(self.exclude) - if isinstance(self.list_display_links, (list, tuple)) and \ - len(self.list_display_links) == 0: + if isinstance(self.list_display_links, (list, tuple)) and len(self.list_display_links) == 0: self.list_display_links = [self.list_display[0]] self._add_reorder_method() self.list_display = list(self.list_display) @@ -135,8 +125,7 @@ def __init__(self, model, admin_site): ] # Remove *all* occurrences of the field from `list_display_links` - if self.list_display_links and \ - self.default_order_field in self.list_display_links: + if self.list_display_links and self.default_order_field in self.list_display_links: self.list_display_links = [ f for f in self.list_display_links if f != self.default_order_field @@ -152,8 +141,7 @@ def __init__(self, model, admin_site): self.ordering = [f for f in self.ordering if f != rev_field] def _get_update_url_name(self): - return f'{self.model._meta.app_label}_{self.model._meta.model_name}' \ - f'_sortable_update' + return f'{self.model._meta.app_label}_{self.model._meta.model_name}_sortable_update' def get_urls(self): my_urls = [ @@ -167,35 +155,28 @@ def get_urls(self): def get_actions(self, request): actions = super().get_actions(request) - paginator = self.get_paginator( - request, self.get_queryset(request), self.list_per_page - ) - if len(paginator.page_range) > 1 and \ - 'all' not in request.GET and self.enable_sorting: + paginator = self.get_paginator(request, self.get_queryset(request), self.list_per_page) + if len(paginator.page_range) > 1 and 'all' not in request.GET and self.enable_sorting: # add actions for moving items to other pages move_actions = ['move_to_exact_page'] cur_page = int(request.GET.get('p', 0)) + 1 - if len(paginator.page_range) > 2 and \ - cur_page > paginator.page_range[1]: + if len(paginator.page_range) > 2 and cur_page > paginator.page_range[1]: move_actions.append('move_to_first_page') if cur_page > paginator.page_range[0]: move_actions.append('move_to_back_page') if cur_page < paginator.page_range[-1]: move_actions.append('move_to_forward_page') - if len(paginator.page_range) > 2 and \ - cur_page < paginator.page_range[-2]: + if len(paginator.page_range) > 2 and cur_page < paginator.page_range[-2]: move_actions.append('move_to_last_page') for fname in move_actions: actions.update({fname: self.get_action(fname)}) return actions def get_changelist(self, request, **kwargs): - first_order_direction, first_order_field_index = \ - self._get_first_ordering(request) + first_order_direction, first_order_field_index = self._get_first_ordering(request) if first_order_field_index == self.default_order_field_index: self.enable_sorting = True - self.order_by = \ - f"{first_order_direction}{self.default_order_field}" + self.order_by = f"{first_order_direction}{self.default_order_field}" else: self.enable_sorting = False return super().get_changelist(request, **kwargs) @@ -246,9 +227,7 @@ def func(this, item): html = '' if this.enable_sorting: html = '
' \ - ' 
'.format( - getattr(item, this.default_order_field), item.pk - ) + ' '.format(getattr(item, this.default_order_field), item.pk) return mark_safe(html) setattr(func, 'allow_tags', True) @@ -271,9 +250,7 @@ def update_order(self, request): if request.method != 'POST': return HttpResponseNotAllowed('Must be a POST request') if not self.has_change_permission(request): - return HttpResponseForbidden( - 'Missing permissions to perform this request' - ) + return HttpResponseForbidden('Missing permissions to perform this request') startorder = int(request.POST.get('startorder')) endorder = int(request.POST.get('endorder', 0)) moved_items = list(self._move_item(request, startorder, endorder)) @@ -300,9 +277,7 @@ def move_to_back_page(self, request, queryset): def move_to_forward_page(self, request, queryset): self._bulk_move(request, queryset, self.FORWARD) - move_to_forward_page.short_description = _( - 'Move selected ... pages forward' - ) + move_to_forward_page.short_description = _('Move selected ... pages forward') def move_to_first_page(self, request, queryset): self._bulk_move(request, queryset, self.FIRST) @@ -349,9 +324,8 @@ def move_item(self, startorder, endorder, extra_model_filters=None): # noinspection PyProtectedMember raise model.MultipleObjectsReturned( - "Detected non-unique values in field '{rank_field}' used " - "for sorting this model.\nConsider to run \n" - " python manage.py reorder {model._meta.label}\n" + "Detected non-unique values in field '{rank_field}' used for sorting this model.\n" + "Consider to run \n python manage.py reorder {model._meta.label}\n" "to adjust this inconsistency." ) @@ -426,8 +400,7 @@ def _bulk_move(self, request, queryset, method): raise Exception('Invalid method') if target_page_number == current_page_number: - # If you want the selected items to be moved to the start - # of the current page, then just do not return here. + # If you want the selected items to be moved to the start of the current page, then just do not return here return try: @@ -439,10 +412,7 @@ def _bulk_move(self, request, queryset, method): queryset_size = queryset.count() page_size = page.end_index() - page.start_index() + 1 if queryset_size > page_size: - msg = _( - f"The target page size is {page_size}. " - f"It is too small for {queryset_size} items." - ) + msg = _(f"The target page size is {page_size}. It is too small for {queryset_size} items.") self.message_user(request, msg, level=messages.ERROR) return @@ -481,10 +451,9 @@ def get_update_url(self, request): class PolymorphicSortableAdminMixin(SortableAdminMixin): """ - If the admin class is used for a polymorphic model, hence inherits from - ``PolymorphicParentModelAdmin`` rather than ``admin.ModelAdmin``, then - additionally inherit from ``PolymorphicSortableAdminMixin`` rather than - ``SortableAdminMixin``. + If the admin class is used for a polymorphic model, hence inherits from ``PolymorphicParentModelAdmin`` + rather than ``admin.ModelAdmin``, then additionally inherit from ``PolymorphicSortableAdminMixin`` + rather than ``SortableAdminMixin``. """ def get_max_order(self, request, obj=None): return self.base_model.objects.aggregate( @@ -494,19 +463,15 @@ def get_max_order(self, request, obj=None): class CustomInlineFormSetMixin: def __init__(self, *args, **kwargs): - self.default_order_direction, self.default_order_field = \ - _get_default_ordering(self.model, self) + self.default_order_direction, self.default_order_field = _get_default_ordering(self.model, self) if self.default_order_field not in self.form.base_fields: self.form.base_fields[self.default_order_field] = \ - self.model._meta.get_field( - self.default_order_field - ).formfield() + self.model._meta.get_field(self.default_order_field).formfield() self.form.base_fields[self.default_order_field].is_hidden = True self.form.base_fields[self.default_order_field].required = False - self.form.base_fields[self.default_order_field].widget = \ - widgets.HiddenInput() + self.form.base_fields[self.default_order_field].widget = widgets.HiddenInput() super().__init__(*args, **kwargs) @@ -558,10 +523,8 @@ def get_fields(self, request, obj=None): fields.append(default_order_field) elif fields[0] == default_order_field: """ - Remove the order field and add it again immediately to ensure it - is not on first position. - This ensures that django's template for tabular inline renders the - first column with colspan="2": + Remove the order field and add it again immediately to ensure it is not on first position. + This ensures that django's template for tabular inline renders the first column with colspan="2": ``` {% for field in inline_admin_formset.fields %} @@ -606,13 +569,12 @@ def template(self): elif self.is_tabular: return 'adminsortable2/tabular.html' raise ImproperlyConfigured( - f'Class {self.__module__}.{self.__class__} must also derive ' - f'from admin.TabularInline or admin.StackedInline' + f'Class {self.__module__}.{self.__class__} must also derive from admin.TabularInline or ' + f'admin.StackedInline' ) -class CustomGenericInlineFormSet(CustomInlineFormSetMixin, - BaseGenericInlineFormSet): +class CustomGenericInlineFormSet(CustomInlineFormSetMixin, BaseGenericInlineFormSet): def get_max_order(self): query_set = self.model.objects.filter( **{ diff --git a/adminsortable2/management/commands/reorder.py b/adminsortable2/management/commands/reorder.py index 7c70d08..291f40c 100644 --- a/adminsortable2/management/commands/reorder.py +++ b/adminsortable2/management/commands/reorder.py @@ -4,8 +4,7 @@ class Command(BaseCommand): args = '' - help = 'Restore the primary ordering fields of a model containing a ' \ - 'special ordering field' + help = 'Restore the primary ordering fields of a model containing a special ordering field' def add_arguments(self, parser): parser.add_argument('models', nargs='+', type=str) @@ -18,12 +17,8 @@ def handle(self, *args, **options): except ImportError: raise CommandError('Unable to load model "%s"' % modelname) - if not hasattr(Model._meta, 'ordering') or \ - len(Model._meta.ordering) == 0: - raise CommandError( - f'Model "{modelname}" does not define field "ordering" ' - f'in its Meta class' - ) + if not hasattr(Model._meta, 'ordering') or len(Model._meta.ordering) == 0: + raise CommandError(f'Model "{modelname}" does not define field "ordering" in its Meta class') orderfield = Model._meta.ordering[0] if orderfield[0] == '-': @@ -33,6 +28,4 @@ def handle(self, *args, **options): setattr(obj, orderfield, order) obj.save() - self.stdout.write( - f'Successfully reordered model "{modelname}"' - ) + self.stdout.write(f'Successfully reordered model "{modelname}"')