Proudly ProcrasDonating

Technology Thoughts

Django Tricks, Part 3 – Model mixins

This post is the third in a series on Django tricks.

In the last post we covered automating initialization. Continuing with initialization, this post will cover model mixins.

Model mixins mix convenient utilities into model classes.

Specified Mixins

Mixins is a general term for mixing attributes of an abstract class into a non-abstract subclass.

In this case, we want to mix convenience methods into Model Foo. The reason why these methods aren’t defined in Foo is because of clean, modular code. An example will help:

class Tagging(models.Model):     tag = models.ForeignKey(Tag)     post = models.ForeignKey(Post)     @classmethod     def Initialize(klass):         model_utils.mixin(TaggingMixin, Post)

There are three models at play in the above example:

  • Post – a blog post
  • Tag – a tag
  • Tagging – links a tag with a post

There is also a normal python class, TaggingMixin, that provides convenience methods for Posts:

class TaggingMixin(object):     """ mixed into Post """         @property     def tags(self):         return Tag.objects.filter(tagging_set__post=self)

In the Tagging model’s Initialization function, mixin mixes TaggingMixin into Post. This allows us to encapsulate all the Tag logic, including the ForeignKeys to Post, inside the Tagging models and the TagMixin class. The Post model remains clean, with only those functions that deal with its direct fields.

This separation becomes a necessity when Models are spread between different apps.

The mixin code is appended to the end of this post.

Standard Mixins

We automatically mixin in the following convenience functions to every Model:

  • add
  • find
  • get
  • get_or_none

get_or_none is a nice way to retrieve an object if it exists, such as:

lucy = User.get_or_none(name="Lucy") if lucy:     print lucy.favorite_number else:     print "Lucy is not in the database"

add is a convenient constructor for creating objects, though it requires adding a Make classmethod to every Model. The advantage is that we not only put constructor logic inside a constructor, but we also abstract that call into add…in case making things becomes more complicated later.

some view function

lucy = User.add("Lucy", 22)

models.py

class User(models.Model):     name = models.CharField(max_length=222)     favorite_number = models.IntegerField()     @classmethod     def make(klass, name, favnum):         favnum = int(round(favnum))         return User(name=name, favorite_number=favnum)

In order to mix these methods into every Model we resort to the __init__.py trick discussed previously:

from models import ALL_MODELS from lib import model_utils model_utils.mixin(model_utils.ModelMixin, ALL_MODELS)

Proudly ProcrasDonating,

Lucy

mixin definition

def mixin(mixin, klasses, last=0):     if not isinstance(klasses, (list, tuple)):         klasses = (klasses,)     for klass in klasses:         if mixin not in klass.__bases__:             if last:                 klass.__bases__ = klass.__bases__+(mixin,)             else:                 # sometimes this fails, but if change order seems to work                 try:                     klass.__bases__ = (mixin,)+klass.__bases__                 except:                     klass.__bases__ = klass.__bases__+(mixin,)

ModelMixin definition

class ModelMixin(object):     """     @summary:     B{ModelMixin} is a mix-in used to provide common methods, attributes,     and hooks across all models.         At initialization, ModelMixin is mixed into all models listed in     """     @classmethod     def find(klass, ids=None, **kwargs):         """         @summary:         Convenience method: B{find} simply passes arguments through to         klass.objects.filter()         """         if isinstance(ids, (int, long)):             return klass.objects.filter(id=ids, **kwargs)         elif isinstance(ids, list):             return klass.objects.filter(id__in=ids, **kwargs)         else:             return klass.objects.filter(**kwargs)     @classmethod     def get_or_none(klass, **kwargs):         """         @summary:         Convenience method: B{get_or_none} simply passes arguments through to         klass.objects.filter()         """         f = klass.objects.filter(**kwargs)         if len(f) == 0:             return None         else:             return f[0]             @classmethod     def get(klass, id=None, **kwargs):         """         Convenience method: B{get} simply passes arguments through to         klass.objects.get()                 @note: Raises exceptions anytime 'get' would.         """         if isinstance(id, (int, long)):             #return klass.objects.get_object_or_404(id=id, **kwargs)             return get_object_or_404(klass, id=id, **kwargs)         elif len(kwargs) > 0:             return klass.objects.get(**kwargs)         else:             raise RuntimeError("No id or conditions given to 'get'!")         @classmethod     def add(klass, *args, **kwargs):         """         @summary:         This is a general method which simply calls 'make' with the same         arguments and then saves the returned object.         """         o = klass.make(*args, **kwargs)         o.save()         #if publish:         #    o.publish()         return o

1 Comment »

[...] automating Admin Model creation, and continued with automating initialization and installation, model mixins and [...]


Your comment

HTML-Tags:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>