Eugene's Blog

I can't believe it's blog!

Code: my categories

After some requests I decided to publish my code for categories. It’s very simple. It was inspired by following articles: A "category" Data Model (note: this article uses old-style model format, it doesn’t work anymore) and Relating an object to itself, many-to-one.

Categories
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from django.core import meta

class Category(meta.Model):
    """
    Category defines following fields:

    name        - simple name of category, e.g., 'C++'
    full_name   - full name of category, which includes names of all parents,
                  e.g. 'Development::C++'
    parent      - reference to parent category or null
    description - HTML description of category, or null

    Notes:

    1) full_name is not editable. It is calculated automatically
       by calculate_full_name() method during save phase (hook _pre_save).
    2) Separator specified by get_separator() method. It can be
       overridden in subclasses.
    """
    name        = meta.CharField(maxlength=50)
    full_name   = meta.CharField(maxlength=250, unique=True,
                                 editable=False)
    parent      = meta.ForeignKey('self', blank=True, null=True,
                                  related_name='child')
    description = meta.TextField(blank=True, null=True,
                                 help_text="Use raw HTML.")

    class META:
        verbose_name_plural = 'Categories'
        module_name = verbose_name_plural.lower()
        admin = meta.Admin(
            list_display  = ('full_name',),
            search_fields = ['full_name',],
            js = (
                '/tiny_mce/tiny_mce.js',
                '/appmedia/admin/js/textareas.js',
            ),
        )
        ordering = ('full_name',)

    def __repr__(self):
        return self.full_name

    def _pre_save(self):
        self.full_name = self.calculate_full_name()

    def get_separator(self):
        return '::'

    # note: used in templates
    def get_all_children(self):
        "get list of all children of the category"
        output = []
        children = self.get_child_list()
        for c in children:
            output.append(c)
            output.extend(c.get_all_children())
        return output

    # note: the last object of the list is the category itself
    # note: used in templates
    def get_all_parents(self):
        "get list of all parents of the category from top level parent down"
        id_list = []
        object = self
        while True:
            id_list.append(object)
            if not object.parent_id:
                break
            object = object.get_parent()
        id_list.reverse()
        return id_list

    # note: used in templates
    def calculate_full_name(self):
        "calculate full name from parent list"
        return self.get_separator().join([x.name for x in self.get_all_parents()])

    # note: used in templates
    def get_path(self):
        "get relative path of the category"
        return 'categories/%d/' % self.id

As you can see it is very simple. This model defines full_name field, which is auto-populated. I was toying with an idea to keep parent_name and produce full_name by concatenating parent_name with name. But after some trials I decided against it: it introduced a lot of complexities and runtime overhead by saving a few bytes in database. In my opinion it doesn’t worth it. This model defines some convenience methods as well. They are meant to be used in templates.

What is js in META subclass? It injects TinyMCE for WYSIWYG editing of description. I wrote about it in Django Wiki.