Models¶
Multiple managers¶
A Model class can have multiple managers, depending upon your needs. Suppose you do not want to display any object on your site which is unapproved(is_approved = False in your Model).:
class ModelClassApprovedOnlyManager(models.Manager):
def get_query_set(*args, **kwargs):
return super(ModelClassApprovedOnlyManager, self).get_query_set(*args, **kwargs).filter(is_approved = True)
class ModelClass(models.Model):
...
is_approved = models.BooleanField(default = False)
objects = models.Manager()
approved_objects = ModelClassApprovedOnlyManager()
If you use multiple managers, the first manager should be the default manager. This is as the first manager is accessible as ModelClass._default_manager, which is used by admin to get all objects.
Custom Manager Methods¶
Imagine you have a query like this:
Event.objects.filter(is_published=True).filter(start_date__gte=datetime.datetime.now()).order_by('start_date')
you probably will need to filter by status and created date again, to avoid duplicating code you could add custom methods to your default manager:
class EventQuerySet(models.query.QuerySet):
def published(self):
return self.filter(is_published=True)
def upcoming(self):
return self.filter(start_date__gte=datetime.datetime.now())
class EventManager(models.Manager):
def get_query_set(self):
return EventQuerySet(self.model, using=self._db) # note the `using` parameter, new in 1.2
def published(self):
return self.get_query_set().published()
def upcoming(self):
return self.get_query_set().upcoming()
class Event(models.Model):
is_published = models.BooleanField(default=False)
start_date = models.DateTimeField()
...
objects = EventManager() # override the default manager
This way you keep your logic in your model. Why do you need a custom QuerySet? To be able to chain method calls. Now that query could be:
Event.objects.published().upcoming().order_by('start_date')
Hierarchical Relationships¶
You may want to model hierarchical relationships. The simplest way to do this is:
class ModelClass(models.Model):
...
parent = models.ForeignKey('ModelClass')
This is called adjacency list model, and is very inefficient for large trees. If your trees are very shallow you can use this. Otherwise you want to use a more efficient but complex modeling called MPTT. Fortunately, you can just use django-mptt.
Singleton classes¶
Sometimes you want to make sure that only one Object of a Model can be created.
Logging¶
To make sure, when an object is create/edited/deleted, there is a log.
Audit Trail and rollback¶
When an object is modified or deleted, to be able to go back to the previous version.
Define an __unicode___¶
Until you define an __unicode__ for your ModelClass, in Admin and at various other places you will get an <ModelClass object> where the object needs to be displayed. Define a meaningful __unicode__ for you ModelClass, to get meaningful display. Once you define __unicode__, you do not need to define __str__.
Define a get_absolute_url()¶
get_absolute_url is used at various places by Django. (In Admin for “view on site” option, and in feeds framework).
Use reverse() for calculating get_absolute_url¶
You want only one canonical representation of your urls. This should be in urls.py
The permalink decorator is no longer recommended for use.
If you write a class like:
class Customer(models.Model)
...
def get_absolute_url(self):
return /customer/%s/ % self.slug
You have this representation at two places. You instead want to do:
class Customer(models.Model)
...
def get_absolute_url(self):
return reverse('customers.detail', args=[self.slug])
AuditFields¶
You want to keep track of when an object was created and updated. Create two DateTimeFields with auto_now and auto_now_add.:
class ItemSold(models.Model):
name = models.CharField(max_length = 100)
value = models.PositiveIntegerField()
...
#Audit field
created_on = models.DateTimeField(auto_now_add = True)
updated_on = models.DateTimeField(auto_now = True)
Now you want, created_by and updated_by. This is possible using the threadlocals technique, but since we do not want to do that, we will need to pass user to the methods.:
class ItemSoldManager(models.Manager):
def create_item_sold(self, user, ...):
class ItemSold(models.Model):
name = models.CharField(max_length = 100)
value = models.PositiveIntegerField()
...
#Audit field
created_on = models.DateTimeField(auto_now_add = True)
updated_on = models.DateTimeField(auto_now = True)
created_by = models.ForeignKey(User, ...)
updated_by = models.ForeignKey(User, ...)
def set_name(self, user, value):
self.created_by = user
self.name = value
self.save()
...
objects = ItemSoldManager()
Working with denormalised fields¶
Working with child tables.¶
You want to keep track of number of employees of a department.:
class Department(models.Model):
name = models.CharField(max_length = 100)
employee_count = models.PositiveIntegerField(default = 0)
class Employee(models.Model):
department = models.ForeignKey(Department)
One way to do so would be to override, save and delete.:
class Employee(models.Model):
...
def save(self, *args, **kwargs):
if not self.id:
#this is a create, not an update
self.department.employee_count += 1
self.department.save()
super(Employee, self).save(*args, **kwargs)
def delete(self):
self.department.employee_count -= 1
self.department.save()
super(Employee, self).delete()
Other option would be to attach listeners for post_save and post_delete.:
from django.db.models import signals
def increment_employee_count(sender, instance, created, raw, **kwargs):
if created:
instance.department.employee_count += 1
instance.department.save()
def decrement_employee_count(sender, instance, **kwargs):
instance.department.employee_count -= 1
instance.department.save()
signals.post_save.connect(increment_employee_count, sender=Employee)
signals.post_delete.connect(decrement_employee_count, sender=Employee)
Abstract custom queries in Manager methods.¶
If you have some complex Sql query, not easily representable via Django ORM, you can write custom Sql. These should be abstracted as Manager methods.