Code


Version 17 (modified by Rick <Rick.van.Hattem@…>, 7 years ago) (diff)

Added many 2 many / many 2 one link

CookBook - Data Model

A "category" Data Model

Description

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 displayed 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'

Note: the code below is somewhat out of date. meta should be replaced with models, for starters...

Code

class Category(meta.Model):
    category_name = meta.CharField(maxlength=50)
    parent = meta.ForeignKey('self', blank=True, null=True,
                             related_name='child')

    def _recurse_for_parents(self, cat_obj):
        p_list = []
        if cat_obj.parent_id:
            p = cat_obj.get_parent()
            p_list.append(p.category_name)
            more = self._recurse_for_parents(p)
            p_list.extend(more)
        if cat_obj == self and p_list:
            p_list.reverse()
        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)
    _parents_repr.short_description = "Category parents"

    # TODO: Does anybody know a better solution???
    def _pre_save(self):
        p_list = self._recurse_for_parents(self)
        if self.category_name in p_list:
            raise "You must not save a category in itself!"

    def __repr__(self):
        p_list = self._recurse_for_parents(self)
        p_list.append(self.category_name)
        return self.get_separator().join(p_list)

    class META:
        admin = meta.Admin(
            list_display = ('category_name', '_parents_repr'),
            search_fields = ['category_name'],
        )
        module_name = 'categories'

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)
...   new_cat.save()
>>>

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.save()
>>> 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
... 
Community
Development
Management
Systems
Development :: Python
Systems :: Administration
Systems :: Administration :: UNIX

Usage in Other Data Models

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

See Also

http://www.djangoproject.com/documentation/model_api/

http://www.djangoproject.com/documentation/models/m2o_recursive/

http://www.djangoproject.com/documentation/models/m2m_and_m2o/