Version 8 (modified by aCC, 17 years ago) (diff)


CookBook - Data Model

A "category" Data Model


I like to have parent/child categories for my documents and weblog entries. I also am a fan of simplicity. This recipe lets you create new categories, and children of categories while providing an intuitively obvious presentation to the user. A parent category of 'Development' with a child category of 'Python' will be display as 'Development :: Python'. Using that example, other data models/content types that utilize this code will have drop-downs in the admin interface with entries of 'Development' and 'Development :: Python'


class Category(meta.Model):
    verbose_name_plural = 'Categories'
    module_name = verbose_name_plural.lower()
    fields = (
        meta.CharField('category_name', maxlength=50),
        meta.CharField('category_parents', maxlength=250, editable=False),
        meta.ForeignKey('self', blank=True, null=True, 
            rel_name='parent', related_name='child'),
    unique_together = (("category_name", "category_parents"),)

    def _recurse_for_parents(self, cat_obj):
        p_list = []
        if cat_obj.parent_id:
            p = cat_obj.get_parent()
            more = self._recurse_for_parents(p)
        if cat_obj == self and p_list:

        return p_list

    def get_separator(self):
        return ' :: '

    def _parents_repr(self):
        p_list = self._recurse_for_parents(self)
        return self.get_separator().join(p_list)

    def __repr__(self):
        if self.category_parents:
            l = [self.category_parents, self.category_name]
            return self.get_separator().join(l)
        return self.category_name

    def _pre_save(self):
        self.category_parents = self._parents_repr()

    admin = meta.Admin(
        list_display = ('category_name', 'category_parents'),
        search_fields = ['category_name', 'category_parents'],
    ordering = ('category_parents', 'category_name')

API Usage

Let's fill in some root-level categories:

>>> from yourpkg.apps.yourapp.models import yourapp
>>> base_cats = [
...   {'category_name': 'Development'},
...   {'category_name': 'Systems'},
...   {'category_name': 'Management'},
...   {'category_name': 'Community'},
... ]
>>> for cat in base_cats:
...   new_cat = yourapp.Category(**cat)

Now, let's create some sub categories, examining the dict as we go:

>>> cat = {'parent_id': 1, 'category_name': 'Python'}
>>> new_cat = yourapp.Category(**cat)
>>> new_cat.__dict__
{'parent_id': 1, 'id': '', 'category_parents': '', 'category_name': 'Python'}
>>> new_cat._pre_save()
>>> new_cat.__dict__
{'_parent_cache': Development, 'parent_id': 1, 'id': '', 'category_parents': 'Development', 'category_name': 'Python'}
>>> new_cat.__dict__
{'_parent_cache': Development, 'parent_id': 1, 'id': 5, 'category_parents': 'Development', 'category_name': 'Python'}
>>> new_cat
Development :: Python

Now, let's add a few more and print all the categories:

>>> cat = {'parent_id': 2, 'category_name': 'Administration'}
>>> yourapp.Category(**cat).save()
>>> cat = {'parent_id': 7, 'category_name': 'UNIX'}
>>> yourapp.Category(**cat).save()
>>> for cat in yourapp.categories.get_list():
...   print cat
Development :: Python
Systems :: Administration
Systems :: Administration :: UNIX

Usage in Other Data Models

class Document(meta.Model):
    fields = (
        meta.CharField('title', maxlength=200),
        meta.ForeignKey(Category, name='subject', blank=True, null=True),

See Also


This example has a bug: the field "category_parents" gets not updated, if a parent category was moved.

E.g. you create the following categories:

secondcat :: thirdcat

Now you move "secondcat" under "firstcat". The correct display would be:

firstcat :: secondcat
firstcat :: secondcat :: thirdcat

But because "category_parents" of thirdcat was not updated, you see:

firstcat :: secondcat
secondcat :: thirdcat

In the database it is correct, just the display is incorrect.

Can anybody fix this?

Back to Top