Work in progress on refactoring the Sortable class into a non-obtrusive mixin.

master
Brandon Taylor 2015-08-23 22:25:55 -04:00
parent b14d747f82
commit 43fef78876
3 changed files with 57 additions and 27 deletions

View File

@ -26,7 +26,7 @@ from django.shortcuts import render
from django.template.defaultfilters import capfirst from django.template.defaultfilters import capfirst
from adminsortable.utils import get_is_sortable, check_model_is_sortable from adminsortable.utils import get_is_sortable, check_model_is_sortable
from adminsortable.models import Sortable from adminsortable.models import SortableMixin
STATIC_URL = settings.STATIC_URL STATIC_URL = settings.STATIC_URL
@ -246,20 +246,21 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
objects_dict = dict([(str(obj.pk), obj) for obj in objects_dict = dict([(str(obj.pk), obj) for obj in
klass.objects.filter(pk__in=indexes)]) klass.objects.filter(pk__in=indexes)])
if '-order' in klass._meta.ordering: if '-{}'.format(klass.order_field_name) in klass._meta.ordering:
step = -1 step = -1
start_object = max(objects_dict.values(), start_object = max(objects_dict.values(),
key=lambda x: getattr(x, 'order')) key=lambda x: getattr(x, klass.order_field_name))
else: else:
step = 1 step = 1
start_object = min(objects_dict.values(), start_object = min(objects_dict.values(),
key=lambda x: getattr(x, 'order')) key=lambda x: getattr(x, klass.order_field_name))
start_index = getattr(start_object, 'order', len(indexes)) start_index = getattr(start_object, klass.order_field_name,
len(indexes))
for index in indexes: for index in indexes:
obj = objects_dict.get(index) obj = objects_dict.get(index)
setattr(obj, 'order', start_index) setattr(obj, klass.order_field_name, start_index)
obj.save() obj.save()
start_index += step start_index += step
response = {'objects_sorted': True} response = {'objects_sorted': True}
@ -281,9 +282,9 @@ class SortableInlineBase(SortableAdminBase, InlineModelAdmin):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(SortableInlineBase, self).__init__(*args, **kwargs) super(SortableInlineBase, self).__init__(*args, **kwargs)
if not issubclass(self.model, Sortable): if not issubclass(self.model, SortableMixin):
raise Warning(u'Models that are specified in SortableTabluarInline' raise Warning(u'Models that are specified in SortableTabluarInline'
' and SortableStackedInline must inherit from Sortable') ' and SortableStackedInline must inherit from SortableMixin')
def get_queryset(self, request): def get_queryset(self, request):
if VERSION < (1, 6): if VERSION < (1, 6):

View File

@ -12,39 +12,53 @@ class MultipleSortableForeignKeyException(Exception):
return repr(self.value) return repr(self.value)
class Sortable(models.Model): class SortableMixin(models.Model):
""" """
`is_sortable` determines whether or not the Model is sortable by `is_sortable` determines whether or not the Model is sortable by
determining if the last value of `order` is greater than the default determining if the last value of the field used to determine the order
of 1, which should be present if there is only one object. of objects is greater than the default of 1, which should be present if
there is only one object.
`model_type_id` returns the ContentType.id for the Model that `model_type_id` returns the ContentType.id for the Model that
inherits Sortable inherits Sortable
`save` the override of save increments the last/highest value of `save` the override of save increments the last/highest value of
order by 1 `order_field_name` by 1
""" """
order = models.PositiveIntegerField(editable=False, default=1, # order = models.PositiveIntegerField(editable=False, default=1,
db_index=True) # db_index=True)
order_field_name = 'order'
is_sortable = False is_sortable = False
sorting_filters = () sorting_filters = ()
# legacy support # legacy support
sortable_by = None sortable_by = None
sortable_foreign_key = None sortable_foreign_key = None
class Meta: class Meta:
abstract = True abstract = True
ordering = ['order']
@classmethod @classmethod
def model_type_id(cls): def model_type_id(cls):
return ContentType.objects.get_for_model(cls).id return ContentType.objects.get_for_model(cls).id
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Sortable, self).__init__(*args, **kwargs) super(SortableMixin, self).__init__(*args, **kwargs)
# get the model field defined by `order_field_name`
self.order_field = self._meta.get_field(self.order_field_name)
integer_fields = (models.PositiveIntegerField, models.IntegerField,
models.PositiveSmallIntegerField, models.SmallIntegerField,
models.BigIntegerField,)
if not self.order_field or not isinstance(self.order_field,
integer_fields):
raise NotImplemented(u'You must define the field '
'`order_field_name` refers to, and it must be of type: '
'PositiveIntegerField, IntegerField, '
'PositiveSmallIntegerField, SmallIntegerField, '
'BigIntegerField')
# Validate that model only contains at most one SortableForeignKey # Validate that model only contains at most one SortableForeignKey
sortable_foreign_keys = [] sortable_foreign_keys = []
@ -59,15 +73,20 @@ class Sortable(models.Model):
elif sortable_foreign_keys_length == 1: elif sortable_foreign_keys_length == 1:
self.__class__.sortable_foreign_key = sortable_foreign_keys[0] self.__class__.sortable_foreign_key = sortable_foreign_keys[0]
def _get_order_field_value(self):
return int(self.order_field.value_to_string(self))
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.id: if not self.id:
try: try:
self.order = self.__class__.objects.aggregate( current_max = self.__class__.objects.aggregate(
models.Max('order'))['order__max'] + 1 models.Max(self.order_field_name))[self.order_field_name + '__max'] or 0
setattr(self, self.order_field_name, current_max + 1)
except (TypeError, IndexError): except (TypeError, IndexError):
pass pass
super(Sortable, self).save(*args, **kwargs) super(SortableMixin, self).save(*args, **kwargs)
def _filter_objects(self, filters, extra_filters, filter_on_sortable_fk): def _filter_objects(self, filters, extra_filters, filter_on_sortable_fk):
if extra_filters: if extra_filters:
@ -80,17 +99,28 @@ class Sortable(models.Model):
{self.sortable_foreign_key.name: sfk_obj.id}) {self.sortable_foreign_key.name: sfk_obj.id})
try: try:
order_by = '-order' if 'order__lt' in filters.keys() else 'order' order_by = '-{}'.format(self.order_field_name) \
obj = self.__class__.objects.filter(**filters).order_by(order_by)[:1][0] if '{}__lt'.format(self.order_field_name) in filters.keys() else self.order_field_name
obj = self.__class__.objects.filter(
**filters).order_by(order_by)[:1][0]
except IndexError: except IndexError:
obj = None obj = None
return obj return obj
def get_next(self, extra_filters={}, filter_on_sortable_fk=True): def get_next(self, extra_filters={}, filter_on_sortable_fk=True):
return self._filter_objects({'order__gt': self.order}, return self._filter_objects(
{'{}__gt'.format(self.order_field_name): self._get_order_field_value},
extra_filters, filter_on_sortable_fk) extra_filters, filter_on_sortable_fk)
def get_previous(self, extra_filters={}, filter_on_sortable_fk=True): def get_previous(self, extra_filters={}, filter_on_sortable_fk=True):
return self._filter_objects({'order__lt': self.order}, return self._filter_objects(
{'{}__lt'.format(self.order_field_name): self._get_order_field_value},
extra_filters, filter_on_sortable_fk) extra_filters, filter_on_sortable_fk)
# for easier legacy support of existing implementations
class Sortable(SortableMixin):
class Meta:
abstract = True

View File

@ -1,8 +1,8 @@
from .models import Sortable, SortableForeignKey from .models import SortableMixin, SortableForeignKey
def check_inheritance(obj): def check_inheritance(obj):
return issubclass(type(obj), Sortable) return issubclass(type(obj), SortableMixin)
def get_is_sortable(objects): def get_is_sortable(objects):
@ -16,7 +16,6 @@ def get_is_sortable(objects):
def is_self_referential(cls): def is_self_referential(cls):
cls_type = type(cls) cls_type = type(cls)
sortable_subclass = check_inheritance(cls_type) sortable_subclass = check_inheritance(cls_type)
# sortable_subclass = issubclass(cls_type, Sortable)
sortable_foreign_key_subclass = issubclass(cls_type, SortableForeignKey) sortable_foreign_key_subclass = issubclass(cls_type, SortableForeignKey)
if sortable_foreign_key_subclass and not sortable_subclass: if sortable_foreign_key_subclass and not sortable_subclass:
return True return True