1- # -*- coding: utf-8 -*-
2- from __future__ import unicode_literals
3-
41import os
52import json
63from itertools import chain
1512else :
1613 from django .utils .translation import ugettext_lazy as _
1714from django .utils .safestring import mark_safe
18- from django .conf . urls import url
15+ from django .urls import path
1916from django .contrib import admin , messages
2017from django .core .exceptions import ImproperlyConfigured
2118from django .core .paginator import EmptyPage
3431from django .http import (
3532 HttpResponse , HttpResponseBadRequest ,
3633 HttpResponseNotAllowed , HttpResponseForbidden )
34+ from django .contrib .contenttypes .models import ContentType
35+ from django .contrib .contenttypes .forms import (
36+ BaseGenericInlineFormSet ,
37+ )
38+ from django .contrib .contenttypes .admin import (
39+ GenericStackedInline , GenericTabularInline
40+ )
3741
3842__all__ = ['SortableAdminMixin' , 'SortableInlineAdminMixin' ]
3943
@@ -82,7 +86,7 @@ def media(self):
8286 'adminsortable2/js/libs/jquery.ui.touch-punch-0.2.3.js' ,
8387 'adminsortable2/js/libs/jquery.ui.sortable-1.11.4.js' ,
8488 ]
85- return super (SortableAdminBase , self ).media + widgets .Media (css = css , js = js )
89+ return super ().media + widgets .Media (css = css , js = js )
8690
8791
8892class SortableAdminMixin (SortableAdminBase ):
@@ -101,7 +105,7 @@ def change_list_template(self):
101105
102106 def __init__ (self , model , admin_site ):
103107 self .default_order_direction , self .default_order_field = _get_default_ordering (model , self )
104- super (SortableAdminMixin , self ).__init__ (model , admin_site )
108+ super ().__init__ (model , admin_site )
105109 self .enable_sorting = False
106110 self .order_by = None
107111 if not isinstance (self .exclude , (list , tuple )):
@@ -141,14 +145,16 @@ def _get_update_url_name(self):
141145
142146 def get_urls (self ):
143147 my_urls = [
144- url (r'^adminsortable2_update/$' ,
148+ path (
149+ 'adminsortable2_update/' ,
145150 self .admin_site .admin_view (self .update_order ),
146- name = self ._get_update_url_name ()),
151+ name = self ._get_update_url_name ()
152+ ),
147153 ]
148- return my_urls + super (SortableAdminMixin , self ).get_urls ()
154+ return my_urls + super ().get_urls ()
149155
150156 def get_actions (self , request ):
151- actions = super (SortableAdminMixin , self ).get_actions (request )
157+ actions = super ().get_actions (request )
152158 paginator = self .get_paginator (request , self .get_queryset (request ), self .list_per_page )
153159 if len (paginator .page_range ) > 1 and 'all' not in request .GET and self .enable_sorting :
154160 # add actions for moving items to other pages
@@ -173,7 +179,7 @@ def get_changelist(self, request, **kwargs):
173179 self .order_by = "{}{}" .format (first_order_direction , self .default_order_field )
174180 else :
175181 self .enable_sorting = False
176- return super (SortableAdminMixin , self ).get_changelist (request , ** kwargs )
182+ return super ().get_changelist (request , ** kwargs )
177183
178184 def _get_first_ordering (self , request ):
179185 """
@@ -200,7 +206,7 @@ def _get_first_ordering(self, request):
200206
201207 @property
202208 def media (self ):
203- m = super (SortableAdminMixin , self ).media
209+ m = super ().media
204210 if self .enable_sorting :
205211 m = m + widgets .Media (js = [
206212 'adminsortable2/js/libs/jquery.ui.sortable-1.11.4.js' ,
@@ -249,7 +255,7 @@ def update_order(self, request):
249255 def save_model (self , request , obj , form , change ):
250256 if not change :
251257 setattr (obj , self .default_order_field , self .get_max_order (request , obj ) + 1 )
252- super (SortableAdminMixin , self ).save_model (request , obj , form , change )
258+ super ().save_model (request , obj , form , change )
253259
254260 def move_to_exact_page (self , request , queryset ):
255261 self ._bulk_move (request , queryset , self .EXACT )
@@ -406,7 +412,7 @@ def changelist_view(self, request, extra_context=None):
406412
407413 extra_context ['sortable_update_url' ] = self .get_update_url (request )
408414 extra_context ['default_order_direction' ] = self .default_order_direction
409- return super (SortableAdminMixin , self ).changelist_view (request , extra_context )
415+ return super ().changelist_view (request , extra_context )
410416
411417 def get_update_url (self , request ):
412418 """
@@ -426,7 +432,7 @@ def get_max_order(self, request, obj=None):
426432 return self .base_model .objects .aggregate (max_order = Coalesce (Max (self .default_order_field ), 0 ))['max_order' ]
427433
428434
429- class CustomInlineFormSet ( BaseInlineFormSet ):
435+ class CustomInlineFormSetMixin ( ):
430436 def __init__ (self , * args , ** kwargs ):
431437 self .default_order_direction , self .default_order_field = _get_default_ordering (self .model , self )
432438
@@ -437,7 +443,11 @@ def __init__(self, *args, **kwargs):
437443 self .form .base_fields [self .default_order_field ].required = False
438444 self .form .base_fields [self .default_order_field ].widget = widgets .HiddenInput ()
439445
440- super (CustomInlineFormSet , self ).__init__ (* args , ** kwargs )
446+ super ().__init__ (* args , ** kwargs )
447+
448+ def get_max_order (self ):
449+ query_set = self .model .objects .filter (** {self .fk .get_attname (): self .instance .pk })
450+ return query_set .aggregate (max_order = Coalesce (Max (self .default_order_field ), 0 ))['max_order' ]
441451
442452 def save_new (self , form , commit = True ):
443453 """
@@ -446,11 +456,11 @@ def save_new(self, form, commit=True):
446456 Strange behaviour when field has a default, this might be evaluated on new object and the value
447457 will be not None, but the default value.
448458 """
449- obj = super (CustomInlineFormSet , self ).save_new (form , commit = False )
459+ obj = super ().save_new (form , commit = False )
460+
450461 default_order_field = getattr (obj , self .default_order_field , None )
451462 if default_order_field is None or default_order_field >= 0 :
452- query_set = self .model .objects .filter (** {self .fk .get_attname (): self .instance .pk })
453- max_order = query_set .aggregate (max_order = Coalesce (Max (self .default_order_field ), 0 ))['max_order' ]
463+ max_order = self .get_max_order ()
454464 setattr (obj , self .default_order_field , max_order + 1 )
455465 if commit :
456466 obj .save ()
@@ -459,12 +469,14 @@ def save_new(self, form, commit=True):
459469 form .save_m2m ()
460470 return obj
461471
472+ class CustomInlineFormSet (CustomInlineFormSetMixin , BaseInlineFormSet ):
473+ pass
462474
463475class SortableInlineAdminMixin (SortableAdminBase ):
464476 formset = CustomInlineFormSet
465477
466478 def get_fields (self , request , obj = None ):
467- fields = super (SortableInlineAdminMixin , self ).get_fields (request , obj )
479+ fields = super ().get_fields (request , obj )
468480 _ , default_order_field = _get_default_ordering (self .model , self )
469481 fields = list (fields )
470482
@@ -488,25 +500,52 @@ def get_fields(self, request, obj=None):
488500
489501 return fields
490502
503+ @property
504+ def is_stacked (self ):
505+ return isinstance (self , admin .StackedInline )
506+
507+ @property
508+ def is_tabular (self ):
509+ return isinstance (self , admin .TabularInline )
510+
491511 @property
492512 def media (self ):
493513 shared = (
494- super (SortableInlineAdminMixin , self ).media
495- + widgets . Media ( js = ('adminsortable2/js/libs/jquery.ui.sortable-1.11.4.js' ,
496- 'adminsortable2/js/inline-sortable.js' )))
514+ super ().media + widgets . Media (
515+ js = ('adminsortable2/js/libs/jquery.ui.sortable-1.11.4.js' ,
516+ 'adminsortable2/js/inline-sortable.js' )))
497517 if isinstance (self , admin .StackedInline ):
498518 return shared + widgets .Media (
499519 js = ('adminsortable2/js/inline-sortable.js' ,
500520 'adminsortable2/js/inline-stacked.js' ))
501- if isinstance (self , admin .TabularInline ):
521+ else :
522+ # assume TabularInline (don't return None in any case)
502523 return shared + widgets .Media (
503524 js = ('adminsortable2/js/inline-sortable.js' ,
504525 'adminsortable2/js/inline-tabular.js' ))
505526
506527 @property
507528 def template (self ):
508- if isinstance ( self , admin . StackedInline ) :
529+ if self . is_stacked :
509530 return 'adminsortable2/stacked.html'
510- if isinstance ( self , admin . TabularInline ) :
531+ elif self . is_tabular :
511532 return 'adminsortable2/tabular.html'
512533 raise ImproperlyConfigured ('Class {0}.{1} must also derive from admin.TabularInline or admin.StackedInline' .format (self .__module__ , self .__class__ ))
534+
535+
536+ class CustomGenericInlineFormSet (CustomInlineFormSetMixin , BaseGenericInlineFormSet ):
537+ def get_max_order (self ):
538+ query_set = self .model .objects .filter (** {self .ct_fk_field .name : self .instance .pk ,
539+ self .ct_field .name : ContentType .objects .get_for_model (self .instance , for_concrete_model = self .for_concrete_model )})
540+ return query_set .aggregate (max_order = Coalesce (Max (self .default_order_field ), 0 ))['max_order' ]
541+
542+ class SortableGenericInlineAdminMixin (SortableInlineAdminMixin ):
543+ formset = CustomGenericInlineFormSet
544+
545+ @property
546+ def is_stacked (self ):
547+ return isinstance (self , GenericStackedInline )
548+
549+ @property
550+ def is_tabular (self ):
551+ return isinstance (self , GenericTabularInline )
0 commit comments