I'm unsure of the best way to design my models for this task. Searching around suggests that I need to use something like Django-MPTT.
I am creating a listings application where any 'item' can be listed under multiple categories. From any category I will need to be able to query for 'items' that match.
An example should illustrate what I want a little better.
I have one Item1 that is to be assigned to two sub categories.
TopLevel1
--- Sublevel1
--- Sublevel2
-----> Item1
TopLevel1
--- Sublevel1
------> Item1
--- Sublevel2
Can anyone suggest how the models/relationships should be constructed? My current implementation
class Category(models.Model):
name = models.CharField(max_length=128, blank=False)
parent = models.ForeignKey('self', null=True, blank=True)
class Item(TimeStampedModel):
name = models.TextField(null=False, blank=False)
categories = models.ManyToManyField(Category)
Does not feel like the correct solution?
This is how I see it...
Your models seem close, a m2m seems appropriate, but your diagram would look more like:
Item1
---> TopLevel1 (accessed through `categories`)
---> SubLevel1 (accessed through `Category.children`, see below)
---> SubLevel2 (accessed through `Category.children`, see below)
and your models would look more like
class Category(models.Model):
name = models.CharField(max_length=128) # `blank` is false by default
parent = models.ForeignKey('self', null=True, blank=True, related_name='children') # by giving this field a nice `related_name`, we can easily access the linked categories
class Item(TimeStampedModel):
name = models.TextField() # `null` and `blank` are false by default
categories = models.ManyToManyField(Category, null=True, blank=True, related_name='items')
Now, if you have an item and would like to get it's top-level categories, you can loop through item.categories.all()
{% for category in item.categories.all %}
Top Level: {{ category.name }}
{% endfor %}
and to access the sub-level categories, you loop through children.all()
in each category
{% for category in item.categories.all %}
Top Level: {{ category.name }}
{% for child in category.children.all %}
Sub Level: {{ child.name }}
Parent: {{ child.parent.name }}
{% endfor %}
{% endfor %}
you can also get all of the items in a category
{% for item in category.items.all %}
Item: {{ item.name }}
{% endfor %}
or if you are in a top-level category
{% for child in category.children.all %}
{{ child.name }}
{% for item in child.items.all %}
Item: {{ item.name }}
{% endfor %}
{% endfor %}
And with
category_one = Category.objects.create('category one')
category_two = Category.objects.create('category two')
item_one = Item.objects.create('item one')
You can add a Category
through the related manager categories
on Item
item_one.categories.add(category_one, category_two)
or you can add an Item
through the related manager items
on Category
category_one.items.add(item_one)