I'm getting started with Django and I'm a bit stuck on a multi-models field, AKA Generic Relation (Content Type)
I have a generic content type "student_solution" that can belong to either:
- a
Org
model - a
Institution
model - a
Campus
model
Therefore, in each of those 3 models, I have a reversed relationship as follow, in each models.py
:
# Reverse generic relation - XXX See https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/#reverse-generic-relations
student_solutions = GenericRelation('student_solution.StudentSolution')
I'm not sure whether this is the right approach, I think so, but a confirmation is welcome :)
It's working fine as it is now, but it's not user-friendly in the Django Admin UI, see how it display on django admin, when creating a Student Solution (I would expect a select box showing a label
field, instead of entering the Content Type ID by hand):
When creating either an Org, Institution or Campus, the field doesn't show at all in the Django Admin (so I probably misconfigured something)
I tried following How to replace content_type and object_id fields by a field with actual object in admin inline? to improve the UI by allowing to select the right Content Type and "object" using the object's label. But it doesn't work at this time.
student_solution/models.py
:
from django.contrib.contenttypes import fields
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Q
from jsonfield import JSONField
from tfp_backoffice.apps.institution.models import Institution
CONTENT_TYPE_CHOICES = (
Q(app_label='org', model='org') |
Q(app_label='institution', model='institution') |
Q(app_label='campus', model='campus')
)
class StudentSolution(models.Model):
# Dynamic relationship to either Org, Institution or Campus entities
# XXX https://simpleisbetterthancomplex.com/tutorial/2016/10/13/how-to-use-generic-relations.html
content_type = models.ForeignKey(
ContentType,
on_delete=models.CASCADE, # TODO check if good thing
limit_choices_to=CONTENT_TYPE_CHOICES,
)
object_id = models.PositiveIntegerField()
content_object = fields.GenericForeignKey(
'content_type',
'object_id'
)
student_solution/admin.py
:
from django.contrib import admin
from modeltranslation.admin import TranslationAdmin
from tfp_backoffice.apps.org.models import Org
from tfp_backoffice.apps.student_solution.forms import StudentSolutionAdminForm, GenericStudentSolutionOwnerChoicesFieldForm
from tfp_backoffice.apps.student_solution.models import StudentSolution
class StudentSolutionInlineAdmin(admin.TabularInline):
form = GenericStudentSolutionOwnerChoicesFieldForm
model = Org # TODO not sure at all about that, should be either of 3 related ContentTypes (Org | Institution | Campus)
# This throw error "<class 'tfp_backoffice.apps.student_solution.admin.StudentSolutionInlineAdmin'>: (admin.E202) 'org.Org' has no ForeignKey to 'student_solution.StudentSolution'."
class StudentSolutionAdmin(TranslationAdmin):
form = StudentSolutionAdminForm
inlines = [
StudentSolutionInlineAdmin,
]
admin.site.register(StudentSolution, StudentSolutionAdmin)
student_solution/forms.py
:
from django import forms
from django.contrib.contenttypes.models import ContentType
from tfp_backoffice.apps.org.models import Org
from tfp_backoffice.apps.student_solution.models import CONTENT_TYPE_CHOICES, StudentSolution
class StudentSolutionAdminForm(forms.ModelForm):
class Meta:
model = StudentSolution
fields = '__all__' # Keep all fields
class GenericStudentSolutionOwnerChoicesFieldForm(forms.ModelForm):
ct_place_type = ContentType.objects.get_for_model(Org) # TODO not sure at all about that, should be either of 3 related ContentTypes (Org | Institution | Campus)
object_id = forms.ModelChoiceField(
Org.objects.all(),
limit_choices_to=CONTENT_TYPE_CHOICES,
label='Student solution'
)
content_type = forms.ModelChoiceField(
ContentType.objects.all(),
initial=ct_place_type,
limit_choices_to=CONTENT_TYPE_CHOICES, # should I use this here?
widget=forms.HiddenInput()
)
def clean_object_id(self):
return self.cleaned_data['object_id'].pk
def clean_content_type(self):
return self.ct_place_type
But this code isn't working and throw this error when starting the server
django.core.management.base.SystemCheckError: SystemCheckError: System check identified some issues:
<class 'tfp_backoffice.apps.student_solution.admin.StudentSolutionInlineAdmin'>: (admin.E202) 'org.Org' has no ForeignKey to 'student_solution.StudentSolution'.