What is the best way to create a multi-level category?

I’m working on a website where people can add exam questions. A question must have the following properties(categories):

Board (Edexcel, Cambridge) → Level (O, A - AS, A2) → Paper (subjects) → Year → Session (Aug, Jun, Jan)

In the frontend (React/Vue/Svelte), when someone clicks on one of the boards, it’ll show all the questions under that board (regardless of their levels, papers, years etc.)
When someone clicks on one of the levels, it’ll show all the questions under that level (regardless of their board, papers, years etc.)

There will also be options to filter with AND operator (combine filters to narrow down the list of questions)

I’m trying to figure out the best way to design the models so that there’s no repetition of data and also easy to query from the frontend. By repetition, I mean a year can be under many papers. Say, Math is a paper which has Years 2018, 2019, 2020. Chemistry is another paper which has Years 2018, 2019, 2020. I don’t want to have to create the same years repeatedly for each paper. So, a ManyToManyField seems logical here.

So, do I do something like this? Do I even need MPTT here?

Or, is the following a better approach?

class Year(models.Model):

  class Meta:

      verbose_name = _("Year")

      verbose_name_plural = _("Years")

      ordering = ["name"]

  name = models.CharField(max_length=255)

  def __str__(self):

      return self.name

class Session(models.Model):

   class Meta:

      verbose_name = _("Session")

      verbose_name_plural = _("Sessions")

      ordering = ["name"]

    name = models.CharField(max_length=255)

    def __str__(self):

       return self.name

class Question(models.Model):

    board = models.ManyToManyField(Board, verbose_name=_("Board"))

    level = models.ManyToManyField(Level, verbose_name=_("Level"))

    paper = models.ManyToManyField(Paper, verbose_name=_("Paper"))

    year = models.ManyToManyField(Year, verbose_name=_("Year"))

    session = models.ManyToManyField(Session, verbose_name=_("Session"))

Thank you very much for you advice!

Two side-bar comments here.

  1. Please do not post images of code. Pictures aren’t readable on every device and individual lines can’t be quoted / copied / pasted for clarifications / corrections. Copy / paste the original code.
  2. When posting code on this site, please enclose the code between lines consisting of three backtick - ` characters. This means you’ll have a line of ```, then your code, then another line of ```. This allows the forum software to keep your code formatted properly.
1 Like

I agree with your second idea here with the Question model containing the multiple M2M references. I’m not getting the impression that there’s a hierarchical structure involved with these relationships, so I probably wouldn’t set it up as a multi-level selection.

Now, if there truly is a hierarchical structure to these selections, then my answer would likely change.

Thanks a lot. Really sorry about posting images. I don’t have that code anymore so I just used a screenshot I took earlier.

You’re right. Hierarchical model isn’t exactly necessary here. The reason I was thinking that way was because my employer explained it using a tree so that got baked into my brain.
But, I guess if I go with the second way, I can just query from the frontend using something like
Question.objects.filter(board__name = 'Edexcel')