Search code examples
pythondjangodjango-modelsblogs

How do I order a model by parent category in django?


I have a model "Category" with a ForeignKey to "parent_category". How can I order this model in the Django admin list view like:

- category 1
-- subcategory 1 of category 1
--- subsubcategory 1 of subcategory 1 of category 1
-- subcategory 2 of category 1
-- subcategory 3 of category 1
- category 2
-- subcategory 1 of category 2
-- subcategory 2 of category 2

I have tried the following but this won't work. So I need some help to order the function 'get_relative_name'.

class PrivateContentCategory(models.Model):
    name = models.CharField(
        max_length=250,
        verbose_name=_('Naam'),
    )
    slug = models.SlugField(
        verbose_name=_('Url'),
        blank=True,
    )
    parent_category = models.ForeignKey(
        'self',
        on_delete=models.SET_NULL,
        related_name='child_category_list',
        verbose_name=_('Hoofdcategorie'),
        blank=True,
        null=True,
    )

    def __str__(self):
        str = self.name
        parent_category_obj = self.parent_category
        while parent_category_obj is not None:
            str = parent_category_obj.name + ' --> ' + str
            parent_category_obj = parent_category_obj.parent_category
        return str

    def get_relative_name(self):
        str = self.name
        parent_category_obj = self.parent_category
        while parent_category_obj is not None:
            str = '--' + str
            parent_category_obj = parent_category_obj.parent_category
    get_relative_name.short_description = _('Naam')
    get_relative_name.admin_order_field = [
        'parent_category__parent_category',
        'name',
    ]

EDIT!!! The names of the parent category should not be displayed with the category. I had written it like this to display how the model should be ordered. The display of the list will just be:

- OS
-- Windows
--- Windows 7
--- Windows 8
--- Windows 10
-- Mac
-- Linux
--- Debian
---- Ubuntu
--- Fedora
---- CentOS
---- Oracle Linux

Solution

  • What worked for me was to add a new field "absolute_name" to the model which will be auto populated with a pre_save signal. After an instance is saved, this field will conain the names for all parent_categories of the instance before it own name. At last, I just needed to order the instance on this field:

    class PrivateContentCategory(models.Model):
        name = models.CharField(
            max_length=250,
            verbose_name=_('Naam'),
        )
        slug = models.SlugField(
            verbose_name=_('Url'),
            blank=True,
        )
        parent_category = models.ForeignKey(
            'self',
            on_delete=models.SET_NULL,
            related_name='child_category_list',
            verbose_name=_('Hoofdcategorie'),
            blank=True,
            null=True,
        )
        absolute_name = models.TextField(
            verbose_name=_('Absolute naam'),
            blank=True,
        )
    
        def __str__(self):
            return self.absolute_name
    
        def get_relative_name(self):
            str = self.name
            parent_category_obj = self.parent_category
            while parent_category_obj is not None:
                str = '--' + str
                parent_category_obj = parent_category_obj.parent_category
            return str
        get_relative_name.short_description = _('Naam')
        get_relative_name.admin_order_field = [
            'absolute_name',
        ]
    
        class Meta:
            verbose_name = _('Privé inhoud categorie')
            verbose_name_plural = _('Privé inhoud categorieën')
            ordering = [
                'absolute_name',
            ]
    
    
    @receiver(models.signals.pre_save, sender=PrivateContentCategory)
    def pre_save_private_content_category_obj(sender, instance, **kwargs):
        # START Generate instance.absolute_name
        instance.absolute_name = instance.name
        parent_category_obj = instance.parent_category
        while parent_category_obj is not None:
            instance.absolute_name = parent_category_obj.name + ' --> ' + instance.absolute_name
            parent_category_obj = parent_category_obj.parent_category
        # END Generate instance.absolute_name