File: models.py

package info (click to toggle)
python-django-adminsortable 2.0.10-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 536 kB
  • sloc: python: 501; sh: 24; makefile: 3
file content (141 lines) | stat: -rw-r--r-- 5,193 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
from django.contrib.contenttypes.models import ContentType
from django.db import models

from adminsortable.fields import SortableForeignKey


class MultipleSortableForeignKeyException(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class SortableMixin(models.Model):
    """
    `is_sortable` determines whether or not the Model is sortable by
    determining if the last value of the field used to determine the order
    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
    inherits Sortable

    `save` the override of save increments the last/highest value of
    `Meta.ordering` by 1
    """

    is_sortable = False
    sorting_filters = ()

    # legacy support
    sortable_by = None
    sortable_foreign_key = None

    class Meta:
        abstract = True

    @classmethod
    def model_type_id(cls):
        return ContentType.objects.get_for_model(cls).id

    def __init__(self, *args, **kwargs):
        super(SortableMixin, self).__init__(*args, **kwargs)

        # Check that Meta.ordering contains one value
        try:
            self.order_field_name = self._meta.ordering[0].replace('-', '')
        except IndexError:
            raise ValueError(u'You must define the Meta.ordering '
                u'property on your model.')

        # get the model field defined by `Meta.ordering`
        self.order_field = self._meta.get_field(self.order_field_name)

        integer_fields = (models.PositiveIntegerField, models.IntegerField,
            models.PositiveSmallIntegerField, models.SmallIntegerField,
            models.BigIntegerField,)

        # check that the order field is an integer type
        if not self.order_field or not isinstance(self.order_field,
                integer_fields):
            raise NotImplemented(u'You must define the field '
                '`Meta.ordering` refers to, and it must be of type: '
                'PositiveIntegerField, IntegerField, '
                'PositiveSmallIntegerField, SmallIntegerField, '
                'BigIntegerField')

        # Validate that model only contains at most one SortableForeignKey
        sortable_foreign_keys = []
        for field in self._meta.fields:
            if isinstance(field, SortableForeignKey):
                sortable_foreign_keys.append(field)

        sortable_foreign_keys_length = len(sortable_foreign_keys)
        if sortable_foreign_keys_length > 1:
            raise MultipleSortableForeignKeyException(
                u'{0} may only have one SortableForeignKey'.format(self))
        elif sortable_foreign_keys_length == 1:
            self.__class__.sortable_foreign_key = sortable_foreign_keys[0]

    def _get_order_field_value(self):
        try:
            return int(self.order_field.value_to_string(self))
        except ValueError:
            raise u'The value from the specified order field could not be '
            'typecast to an integer.'

    def save(self, *args, **kwargs):
        if not self.id:
            try:
                current_max = self.__class__.objects.aggregate(
                    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):
                pass

        super(SortableMixin, self).save(*args, **kwargs)

    def _filter_objects(self, filters, extra_filters, filter_on_sortable_fk):
        if extra_filters:
            filters.update(extra_filters)

        if self.sortable_foreign_key and filter_on_sortable_fk:
            # sfk_obj == sortable foreign key instance
            sfk_obj = getattr(self, self.sortable_foreign_key.name)
            filters.update(
                {self.sortable_foreign_key.name: sfk_obj.id})

        try:
            order_by = '-{0}'.format(self.order_field_name) \
                if '{0}__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:
            obj = None

        return obj

    def get_next(self, extra_filters={}, filter_on_sortable_fk=True):
        return self._filter_objects(
            {'{0}__gt'.format(self.order_field_name): self._get_order_field_value()},
            extra_filters, filter_on_sortable_fk)

    def get_previous(self, extra_filters={}, filter_on_sortable_fk=True):
        return self._filter_objects(
            {'{0}__lt'.format(self.order_field_name): self._get_order_field_value()},
            extra_filters, filter_on_sortable_fk)


# for legacy support of existing implementations
class Sortable(SortableMixin):

    class Meta:
        abstract = True
        ordering = ['order']

    order = models.PositiveIntegerField(default=0, editable=False,
        db_index=True)