2008-03-04

Hacking Contributed Models

Django comes with a bunch of contributed applications like auth for authentication, admin for basic data manipulation, or flatpages for the dynamic content that tends to be rarely changed. Sometimes you want to use them, but their functionality doesn't completely fit your needs.

You have several options in that case:
  • Use custom models with one-to-one or many-to-one relations, creating extensions for existing models. The main drawback of this approach is that those custom models will be editable in the contributed administration separately from the origin or you will need to create custom views to combine everything nicely.
  • Use modified duplicates instead of the contributed applications. The main drawback of this case is that it will be hard to manage the updates of the models.
  • Use signals to modify the models. This can be too complicated for simple changes.
  • Modify the properties of the models on the fly.
Let's dig deeper into the last option. When you start the shell or request for a page on the web server, at first Django loads all modules according the order of the INSTALLED_APPS setting. As Python is an interpreted language and all methods and properties are public, you can access and change them on the fly.

For example, if your task is to rename verbose name of groups to "Roles" and show related users at the list view of contributed administration, then it can be achieved by the following code in some model which application appears somewhere below the "django.contrib.auth" in the INSTALLED_APPS.
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import Group

def modify_group_class():
""" A function modifying the contributed Group model """
# modifying the verbose names
Group._meta.verbose_name = _("Role")
Group._meta.verbose_name_plural = _("Roles")

def display_users(group):
""" A function displaying users for a group """
links = []
for user in group.user_set.all():
links.append(
"""<a href="/admin/auth/user/%d">%s</a>""" % (
user.id,
("%s %s" % ( # show the real name
user.first_name,
user.last_name, # or the username
)).strip() or user.username,
)
)
return "<br />".join(links)
display_users.allow_tags = True
display_users.short_description = _("Users")

# attaching the new function to the Group model
Group.display_users = display_users

# changing the list_display for the Group model
Group._meta.admin.list_display = ("__str__", "display_users")

modify_group_class()


As you might see from this example, Group._meta returns the Meta class and Group._meta.admin returns the Admin class for the Group model. You can prove it to yourself by browsing objects in Django shell aka Python IDE.

Use this kind of hacking whenever you reasonably need to change Django core functionalities or some third party modules and have no other choice. However, don't overuse it, 'cause it might lead to difficulties in maintaining the project.

1 comment:

  1. Very nifty approach. Thanks for posting!

    ReplyDelete