djangodjango-modelsdjango-admindjango-admin-filters

How to show a relationship in the Django Admin?


I am using Django 2.1 and I am having a hard time trying to figure out how to show relationships in the Admin. I have two Models Hospital and Unit and a third one is Exam. They are set up like this:

class Hospital(models.Model):

    name = models.CharField(max_length=200)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

class Unit(models.Model):

    unit_name = models.CharField(max_length=200)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    parent_company = models.ForeignKey(
        Hospital, related_name='unit_parent', on_delete=models.CASCADE, verbose_name="Hospital")


class Exam(models.Model):

    unit_name = models.ForeignKey(
        Hospital, related_name='hospital_pk', on_delete=models.CASCADE)
    exam_name = models.CharField(max_length=255, blank=True,
                                 null=True, verbose_name="Exam's name")
    date = models.DateField()
    time = models.TimeField()

    def __str__(self):
        return self.exam_name


class CustomExamAdmin(admin.ModelAdmin):

    model = Exam
    list_display = ('exam_name', 'time', 'alert',
                    'document', 'notes', 'report', 'patients_name', 'unit_name')
    list_filter = ('exam_name', 'time')
    list_select_related = ('unit_name',)
    fieldsets = (
        ("Hospital's Name:", {
            'fields': ('unit_name',)}),
        ('Types of Exams:', {
            'fields': ('exam_name', 'modality', 'model_used')}),
        ("Patient's name:", {
            'fields': ('patients_name',)}),
     )

So, in the Exams page in Django Admin I am able to select the Hospital name, however I am not able to select the Unit that belongs to the hospital. I need to select the hospital as well as the unit that belongs to it. Should I add another ForeignKey to Exam, like this: unit_selected = models.ForeignKey( Unit, related_name='unit_pk', on_delete=models.CASCADE)

For instance: Hospital: Brookdale University Hospital Unit: Downtown Unit

What I get: enter image description here


Solution

  • In order to select the Unit in the Admin, you need to first add it to the Exam model. You have a field called unit_name, but you have defined it as a foreign key to the Hospital model, not the Unit model. If you have a unit_name field, you don't really need a separate hospital field, since you can easily get it from unit_name.parent_company. So you can do this:

    class Exam(models.Model):
    
        unit_name = models.ForeignKey(
            Unit, related_name='exams', on_delete=models.CASCADE)
        exam_name = models.CharField(max_length=255, blank=True,
                                     null=True, verbose_name="Exam's name")
        date = models.DateField()
        time = models.TimeField()
    
        def __str__(self):
            return self.exam_name
    

    For the admin, rather than selecting the hospital and unit separately, the simplest approach, in my opinion, is to include the hospital name as part of the unit name when you select it in the admin, like Brookdale University Hospital|Downtown Unit. That way you only need one dropdown field. You can do that like this:

    from django import forms
    
    
    class UnitChoiceField(forms.ModelChoiceField):
         def label_from_instance(self, obj):
             return "{}|{}".format(obj.parent_company.name, obj.unit_name)
    
    
    class CustomExamAdmin(admin.ModelAdmin):
    
        model = Exam
        list_display = ('exam_name', 'time', 'alert',
                        'document', 'notes', 'report', 'patients_name', 'get_hospital', 'unit_name')
        list_filter = ('exam_name', 'time')
        list_select_related = ('unit_name__parent_company',)
        fieldsets = (
            ("Hospital's Name:", {
                'fields': ('unit_name',)}),
            ('Types of Exams:', {
                'fields': ('exam_name', 'modality', 'model_used')}),
            ("Patient's name:", {
                'fields': ('patients_name',)}),
         )
    
        def formfield_for_foreignkey(self, db_field, request, **kwargs):
            if db_field.name == 'unit_name':
                return UnitChoiceField(
                    queryset=Unit.objects.all().select_related('parent_company').order_by(
                    'parent_company.name', 'unit_name'))
            return super().formfield_for_foreignkey(db_field, request, **kwargs)
    
        def get_hospital(self, obj):
        #  required to add hospital name to list_display
            return obj.unit_name.parent_company.name           
        get_hospital.short_description = 'Hospital'
    

    One other note: the related_name in a ForeignKeyField is the relation from the foreign model back to the model you are defining. So in the Unit model

    parent_company = models.ForeignKey(
        Hospital, related_name='unit_parent', on_delete=models.CASCADE, verbose_name="Hospital")
    

    should be

     parent_company = models.ForeignKey(
        Hospital, related_name='units', on_delete=models.CASCADE, verbose_name="Hospital")
    

    Then from the Hospital model you can refer to the list of units as units.