Wednesday, April 25, 2007

[EN] Django model managers, Do Not Repeat Yourself !

I've recently come up with a great django feature "model Managers".

Each django model has it's own manager class, which can be overriden by your custom class.

Let's say there's a model containing fields:

class Person(models.Model):
name = models.CharField(maxlength="100", blank=False, verbose_name="Your name")
age = models.IntegerField(verbose_name="Your age")

def __str__(self):
return self.name



You need to use this model in many places, and you need to select people by age and mark
them as child, teen, older.

Each time you select them you would need to crate a queryset like:

children = Person.objects.filter(age__lt=13)
teen = Person.objects.filter(age__gte=13).filter(age__lt=18)
older = Person.objects.filter(age__gte=18)


Anytime you use this function, you would need to write the filter statements.

Thanks to model managers, you can do it once, and use it in your code anywhere.

class PersonManager(models.Manager):
def get_children(self):
return self.filter(age__lt=13)
def get_teens(self):
return self.filter(age__ge=13).filter(age_lt=18)
def get_olders(self):
return self.filter(age__gte=18)


and put:
objects = PersonManger() in your model class so the class looks like:

from django.db import models

class Person(models.Model):
name = models.CharField(maxlength="100", blank=False, verbose_name="Your name")
age = models.IntegerField(verbose_name="Your age")

objects = PersonManger()

def __str__(self):
return self.name


To select teens, you can use: Person.objects.get_teens().
You can use any standard queryset options like .filter, .exclude etc, for example:

To get teens, with name not starting with "s".
Person.objects.get_teens.exclude(name__startswith: "s")

Also, you can change parameters for model managers functions.

Let's say you want to select teens, olders, children with name starting with some letter.

Modify your model manager functions, so they look like (I will modify just the one here).

def get_teens(self, name_filter_letter=None):
if name_filter_letter is None:
return self.filter(age__ge=13, age_lt=18)
else:
return self.filter(age__ge=13).filter(age__lt=18).filter(name__startswith=name_filter_letter)


And use it like:
Person.objects.get_teens("s")

Custom managers can do many other things, they are a standard python methods.

Note that you don't need to use the name:
objects = PersonManager()

You can use your own name as well:
persons = PersonManager()

which results in:
Person.persons.get_teens()

One more thing:
You can go deeply and override the models.Manager get_query_set() method, for example:

You want to grab teens from the model:

Create model subclass like:

class PersonTeensManager(models.Manager):
def get_query_set(self):
return super(PersonTeensManager, self).get_query_set().filter(age__lt=18).filter(age__gte=3)


and put:

teens = PersonTeensManager() in your model class.

In your views, use the queryset:

teens = Person.teens.all()

Isn't that simple?!

For more documentation take a look here:
http://www.djangoproject.com/documentation/models/custom_managers/
http://www.djangoproject.com/documentation/models/get_object_or_404/

No comments: