Django Admin Generic content type multiple models inline form
Asked Answered
C

2

5

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):

django admin create entity

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'.

Creath answered 7/3, 2019 at 22:52 Comment(0)
C
6

If I am understanding what you would like to do correctly you should have your StudentSolutionInlineAdmin in each of the Org, Institution, and Campus admins and it should be a GenericTabularInline (https://docs.djangoproject.com/en/2.2/ref/contrib/contenttypes/#generic-relations-in-admin).

So you would have something like this in (for example) your org/admin.py:

from django.contrib import admin
from django.contrib.contenttypes.admin import GenericTabularInline
from django import forms

from .models import Org
from student_solution.models import StudentSolution

class StudentSolutionInlineAdmin(GenericTabularInline):
    model = StudentSolution
    extra = 1

class StudentSolutionAdminForm(forms.ModelForm):
    class Meta:
        model = StudentSolution
        fields = '__all__'  # Keep all fields 

@admin.register(Org)
class OrgAdmin(admin.ModelAdmin):
    form = StudentSolutionAdminForm
    inlines = [StudentSolutionInlineAdmin]

This will allow you to add a StudentSolution related to an Org from within the Org admin.

Ceuta answered 9/9, 2019 at 20:25 Comment(1)
Thanks for the reply, I'm not able to check if this would work, as I have since changed project. Also, as you're a new member I'll upvote your answer, therefore assuming that your solution is correct. I'll let the community let me know if that's not the case. - I'm doing so because you wrote a clear and detailed answer and only got 10 reputation at the time of writing. I know how hard it is to get some rep' at the beginning on SO and hope that'll make it easier for you. Use your new rights wisely! :)Creath
A
0

You could have a look on this one: https://docs.djangoproject.com/en/2.2/ref/contrib/contenttypes/#generic-relations-in-admin

They have special inline types to use if you are using the content type feature framework

Antofagasta answered 31/8, 2019 at 20:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.