Thursday, April 26, 2007

[EN] Django extending User, custom User methods

Django doesn't allow to subclass the User model yet.

Each Django model lets you easily create model methods.
Behind the scene, each django model has its own methods, f.e. save()
which saves the model data into the database.

Django provides the lightweight User framework in "django/contrib/auth/",
where "User" model is defined with fields:

username, password, is_staff, first_name, last_name, email, date_joined, last_login,
is_superuser, is_active

What if we need the user model with additional data like: age, location ?

The answer is: "extending the User model" by creating a new model
and linking it with "User" model via ForeignKey.

Example:

from django.contrib.auth.models import User

class Userprofile(models.Model):
user = models.ForeignKey(User, unique=True)
age = models.IntegerField()
location = models.CharField(maxlength="100", verbose_name"Country")


To create a new user, django comes with a special "create_user" method.
user = User.objects.create_user('robert', 'rsome@email', 'somepassword')
user.save()


You could do this also using the standard model create like:
user = User(username='someusername', is_active=True, email='some@email', first_name='john', last_name='doe')
user.save()
user.set_password('somepassword')
user.save()


As we have a new User record, to create a new "linked" UserProfile you would:
user = User.objects.get(name='someusername')
user.userprofile_set.create(age=22, location='Poland')


Django will automagically create the new UserProfile with "user" ForeignKey pointing
to the right User instance. Cool !

Question! How do I work on two separated models? Do I need to grab the UserProfile
instance for each request.user I operate in my views ?

Answer: Normally, you woule have to use the code below:
userprofile = Userprofile.objects.get(user=request.user)


Django comes with a special settings.py variable "AUTH_PROFILE_MODULE' which should point to your Userprofile model.

Example:
AUTH_PROFILE_MODULE = 'yourapp.Userprofile'

which lets you easily get into the "UserProfile" by using the "get_profile()" method.

Example: (in your either views.py or template):

location = request.user.get_profile().location
{{ user.get_profile.location }}

Note:
user_profile() does not only allows you to get the model data.
It allows you to get the model methods as well.

Example:

class Userprofile(models.Model):
user = models.ForeignKey(User, unique=True)
age = models.IntegerField()
location = models.CharField(maxlength="100")

def __str__(self):
return self.location

def is_of_age(self):
if self.age < 18:
return False
else:
return True


This lets you do some conditioning in your views:
if request.user.get_profile().is_of_age():
print "Yes, I will sell you this beer"



Delicious!

Another +1 for Django !

Links for reading:
http://www.djangoproject.com/documentation/authentication/

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/