We started using Mixins in our Django project for HackerEarth about an year back and they truly embody the DRY principle in Django. In object-oriented programming languages, a mixin is a class which contains a combination of methods from other classes.
Add timestamp and trashed attribute for every model object - This was one of the most important thing that we should have been implemented as early as possible. The idea is that you create a Base abstract class and then sub-class every model from this class.
class Base(models.Model):
"""
Base parent model for all the models
"""
timestamp = models.DateTimeField(blank=True, db_index=True)
hidden = models.BooleanField(default=False, db_index=True)
objects = BaseManager()
def __init__(self, *args, **kwargs):
super(Base, self).__init__(*args, **kwargs)
class Meta:
abstract = True
# Override save method.
def save(self, *args, **kwargs):
if not self.timestamp:
self.timestamp = datetime.now()
update_timestamp = kwargs.pop('update_timestamp', False)
if update_timestamp:
self.timestamp = datetime.now()
super(Base, self).save(*args, **kwargs)
# Override delete method.
def delete(self, **kwargs):
self._forced_delete = kwargs.pop('forced', False)
if not self._forced_delete:
model = self.__class__
kwargs.update({'trashed': True})
model.objects.using(self._db).filter(
pk=self.id).update(**kwargs)
else:
super(Base, self).delete(**kwargs)
Every model in our code base is derived from the Base model and has the following 3 attributes
timestamp
- I don't need to give a reason for why you should save the timestamp when any object is created in your database, there are so many things that you can do out of this data that this is an absolute must. It might not be very intuitive in the beginning but as your system becomes complex you will realize the benefits of having this.
trashed
- This is again a very important attribute, as you would progress along you will realize that its better to trash objects rather than completely deleting them from your database. If you delete them once its completely lost but as your application becomes complex you realize you don't need to delete everything, but just need to make them unavailable to the application.
Note that implementing the trashed functionality requires more than this mixin because in addition to adding this attribute, one also needs to make sure that whenever there is a query from the model, the trashed object should not be included in the querylist.
class BaseManager(models.Manager):
"""
Base Manager for Base model
"""
def __init__(self):
super(BaseManager, self).__init__()
def get_query_set(self):
return BaseQuerySet(model=self.model, using=self._db).filter(trashed=False)
# If you are using newer versions of Django you will need to define this
def get_queryset(self):
return BaseQuerySet(model=self.model, using=self._db).filter(trashed=False)
Slugged - for adding slug field to a model with title/name - This class can be used for any object where you need to create a slugged field, this is suitable for any content object which is used to store content, for instance the model object that stores this 'Note' is derived from Slugged class because we use the slug generated from the title for creating urls.
class Slugged(models.Model):
"""
Abstract model that handles auto-generating slugs.
"""
title = models.CharField(_("Title"), max_length=255, default='')
slug = models.CharField(_("Slug"), max_length=255, unique=True,
db_index=True, blank=True)
class Meta:
abstract = True
ordering = ("title",)
def __unicode__(self):
return self.title
def clean(self):
from django.core.exceptions import ValidationError
if self.title:
slug = self.get_slug()
if not slug:
raise ValidationError('Please enter a \
valid title/name, it must contain at least one character')
def save(self, *args, **kwargs):
"""
Create a unique slug by appending an index.
"""
update_slug = kwargs.pop('update_slug', False)
concrete_model = base_concrete_model(Slugged, self)
new_slug = False
if not self.slug or update_slug:
new_slug = True
if new_slug:
self.slug = self.get_slug()
def get_slug(self):
"""
Allows subclasses to implement their own slug creation logic.
"""
slug = ''
# if title is None, slugify returns 'none' as string.
if self.title is not None:
slug = slugify(self.title)
# For titles like !@#$!@#$, slugify returns an empty string.
if slug == '':
slug = str(uuid.uuid1())[:7]
return slug[:256]
So if there is any model object where you have a title and you need a slug to generate unique urls you can sub-class that model from this class. There is a function in the class named 'get_slug', this is a function that has to be overridden by the model class and contains the logic to create the slug. One can use slugify function in Python to create slug out of the title and then check for duplicate slug and if there is a duplicated slug then append a digit or random uuid at the end of the slug. In fact, one should make it a standard practice to append a uuid at the end of every slug to ensure uniqueness.
Generic - for adding a Generic foreign key - Before I start explaining this, I will also explain what Generic contenttypes are. Suppose you have a model which could have a foreign key to different model types, instead of adding a nullable foreign key for each one of them, one can use generic content type, where essentially we store the object_id of the related object and the content_type to identify which object we are referring to. You can read more about generic foreign keys in Django here - http://axiacore.com/blog/how-use-genericforeignkey-django/
Once your application starts becoming more and more complex you will realize that you start using Django Generic foreign key more often and its always good to have an abstract base class that allows you to add this without having to repeat your code.
class Generic(models.Model):
"""
Abstract model for generic content types.
"""
content_type = models.ForeignKey(ContentType, db_index=True)
object_id = models.PositiveIntegerField(db_index=True)
content_object = generic.GenericForeignKey('content_type', 'object_id')
class Meta:
abstract = True
These are some of the mixins that we use most often in our codebase. Every model in our application is derived from Base and a lot of other models are also derived from Slugged and Generic. Apart from the obvious benefit that you don't have to repeat your code, using such mixins also standardizes the name you assign to attributes add add structure to the way you define models.