First off: This snippet is more or less obsoleted by snippet #259. The way that snippet #259 uses feels cleaner and more usable. It is basically the same code but the magic works a little bit different. In case you prefer this way, I've left this snippet as-is.
Maybe you know this problem: you have some objects in your database and would like to display them in some way. The problem: the Django ORM does not provide any way to sort model instances by a user-defined criterion. There was already a plug-in in SQLAlchemy called orderinglist, so implementing this in Django was basically not a big deal.
Usage is (due to use of meta-classes) quite simple. It is recommended to save this snippet into a separate file called positional.py. To use it, you only have to import PositionalSortMixIn from the positional module and inherit from it in your own, custom model (but before you inherit from models.Model, the order counts).
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 | #!/usr/bin/env python
# -*- coding: utf-8 -*-
from django.db import models
class SortingManager(models.Manager):
"""An extended manager that extends the default manager
by `get_sorted()` which returns the objects in the database
sorted by their position"""
def get_sorted(self):
"""Returns a sorted list"""
return self.model.objects.all().order_by('position')
class InjectingModelBase(models.base.ModelBase):
"""This helper metaclass is used by PositionalSortMixIn.
This metaclass injects two attributes:
- objects: this replaces the default manager
- position: this field holds the information about the position
of the item in the list"""
def __new__(cls, name, bases, attrs):
"""Metaclass constructor calling Django and then modifying
the resulting class"""
# get the class which was alreeady built by Django
child = models.base.ModelBase.__new__(cls, name, bases, attrs)
# try to add some more or less neccessary fields
try:
# add the sorting manager
child.add_to_class('objects', SortingManager())
# add the IntegerField
child.add_to_class('position', models.IntegerField(editable=False))
except AttributeError:
# add_to_class was not yet added to the class.
# No problem, this is called twice by Django, add_to-class
# will appear later
pass
# we're done - output the class, it's ready for use
return child
class PositionalSortMixIn(object):
"""This mix-in class implements a user defined order in the database.
To apply this mix-in you need to inherit from it before you inherit
from `models.Model`. It adds a custom manager and a IntegerField
called `position` to your model. be careful, it overwrites any existing
fiels that you might have defined."""
# get a metaclass which injects the neccessary fields
__metaclass__ = InjectingModelBase
def get_object_at_offset(self, offset):
"""Get the object whose position is `offset` positions away
from it's own."""
# get the class in which this was mixed in
model_class = self.__class__
try:
return model_class.objects.get(position=self.position+offset)
except model_class.DoesNotExist:
# no such model? no deal, just return None
return None
# some shortcuts, convenience methods
get_next = lambda self: self.get_object_at_offset(1)
get_previous = lambda self: self.get_object_at_offset(-1)
def move_down(self):
"""Moves element one position down"""
# get the element after this one
one_after = self.get_next()
if not one_after:
# already the last element
return
# flip the positions
one_after.position, self.position = self.position, one_after.position
for obj in (one_after, self):
obj.save()
def move_up(self):
"""Moves element one position up"""
# get the element before this one
one_before = self.get_previous()
if not one_before:
# already the first
return
# flip the positions
one_before.position, self.position = self.position, one_before.position
for obj in (one_before, self):
obj.save()
def save(self):
"""Saves the model to the database.
It populates the `position` field of the model automatically if there
is no such field set. In this case, the element will be appended at
the end of the list."""
model_class = self.__class__
# is there a position saved?
if not self.position:
# no, it was ampty. Find one
try:
# get the last object
last = model_class.objects.all().order_by('-position')[0]
# the position of the last element
self.position = last.position +1
except IndexError:
# IndexError happened: the quary did not return any objects
# so this has to be the first
self.position = 0
# save the now properly set-up model
return models.Model.save(self)
def delete(self):
"""Deletes the item from the list."""
model_class = self.__class__
# get all objects with a position greater than this objects position
objects_after = model_class.objects.filter(position__gt=self.position)
# iterate through all objects which were found
for element in objects_after:
# decrease the position in the list (means: move forward)
element.position -= 1
element.save()
# now we can safely remove this model instance
return models.Model.delete(self)
|
More like this
- Add Toggle Switch Widget to Django Forms by OgliariNatan 2 months, 2 weeks ago
- get_object_or_none by azwdevops 6 months, 1 week ago
- Mask sensitive data from logger by agusmakmun 8 months ago
- Template tag - list punctuation for a list of items by shapiromatron 1 year, 10 months ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 1 year, 10 months ago
Comments
Please login first before commenting.