Setting relationship with chosen model class in Django Admin interface

Viewed 373

Problem: How to add relationship from chosen model instance to any other Django model dynamically via Django Admin interface?

Description:

I want to create Categories via Django Admin interface. Each Category has multiple Choices assigned to it. Choice(s) from given Category may be assigned only to objects of another specific Django class (model). Let's present a pseudocode example:

class Category(models.Model):
    category_name = models.CharField()


class Choice(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="choices")
    choice_name = models.CharField()


class ModelWithChoosableFields(models.Model):
    possible_categories = ...   # objects of class Category
    selected_choices = ...   # objects of class Choice

    class Meta:
        abstract = True


class Book(ModelWithChoosableFields):
    ...

class Animal(ModelWithChoosableFields):
    ...
  • Category with category_name = 'Genre' has three possible Choices: choice_name = 'biography', choice_name = 'thriller' and choice_name = 'poetry'.

    Category with category_name = 'Animal type' has two possible Choices: choice_name = 'mammal' and choice_name = 'reptile'.

  • Class Book may have one of the Choices from Category category_name = 'Genre' assigned. However, Choices related to category_name = 'Animal type' cannot be assigned to class Book.

    Analogically, class Animal can only have Choices related to category_name = 'Animal type' assigned to it.

In other words, in Admin panel for instance of class Book I want to have a way of selecting Choice objects from Categories appropriate for Book class.

The reason I want to do this is so that user using Django Admin interface can add dynamically possible Categories to chosen models (e.g. add Category category_name = "Conservation status" choosable for class Animal), add more Choices to Categories if needed (e.g. add another choice_name = 'fish' to category_name = 'Animal type'. This way it is very flexible for end admin user, no need to change anything in code.

I tried achieving it with Generic Relations - however, it wasn't successful, because AFAIK generic relation ties given object (e.g. Category) to instance of object of any other class, not generally to any other class (so, when using Generic Relations, for Category I would have to specify relationship with given Book object instance, not with Book class in general).

I wonder if such action is even feasible - I searched a lot and couldn't find anything. Maybe there is a simpler way? Thank you in advance!

1 Answers

With ContentTypes you can relate model instances to entire model classes, no overwriting necessary to achieve your goal.

Heres how to do it:

  1. In your Category model define a many-to-many relationship to ContentType. This way, in your Category model-forms you will be able to choose which models this category applies to and you will be able to filter Choices based on whether their category contains a particular model. Use the limit_choices_to parameter of the ManyToManyField to restrict the ContentType choices to those with the correct app_label and of course exclude the Choice and Category models.

  2. From the Book/Animal/Etc. models add many-to-many relationships to the Choice model and use the limit_choices_to parameter to limit the Choices to only those with a category which is related to the respective model.

Your models should then look somewhat like this:

from django.db import models


def get_ContentTypes():
    appQ = models.Q(app_label='YourAppName') #change value of app_label to match your app's name
    modelIsCatQ = models.Q(model='category')
    modelIsChoice = models.Q(model='choice')
    return appQ & ~modelIsCatQ & ~modelIsChoice

class Category(models.Model):
    category_name = models.CharField(max_length=128)
    asigned_models = models.ManyToManyField(ContentType,limit_choices_to=get_ContentTypes)

class Choice(models.Model):
    choice_name = models.CharField(max_length=128)
    category = models.ForeignKey(Category,on_delete=models.Model)

class Animal(models.Model):
    choices = models.ManyToManyField(Choice,limit_choices_to=models.Q(category_assigned_models__model__startswith='animal'))

class Book(models.Model):
    choices = models.ManyToManyField(Choice,limit_choices_to=models.Q(category_assigned_models__model__startswith='book'))

Aaand Voila. There you have it:

  1. When creating/editing a category, you choose which models it should apply to
  2. When creating/editing a Book/Animal/etc. you can only see the relevant choices.
Related