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








