Search code examples
pythondjangogeodjangodjango-leaflet

Django admin inline conditioned on model field values


I'm trying to show one of three different child model inlines for a parent model, based on a selected field value within the parent model. I have tried every solution that pops up from various google searches but nothing seems to work.

First a little background, I'm new to python and django but so far I feel that I have been a quick study. I'm attempting to build a web application to house information linked to various spatial locations. The geometry type (geom_type) for each location may be different (i.e., points, linestring, and polygons are possible). To capture this information I plan to create a parent model (Location) to house the name and geom_type (and possibly other metadata). The spatial data related to each Location would then be housed in three separate child models; one for each geom_type. When entering data I would like to create a new location and select the geom_type, which would then pull up the correct inline.

Now for the details:

Models

from django.contrib.gis.db import models

class Geometry(models.Model):

    TYPE = (
    ('Point', 'Point'),
    ('Linestring', 'Linestring'),
    ('Polygon', 'Polygon'),
    )

    geom_type = models.CharField('Geometry Type', choices = TYPE, max_length = 30)

    class Meta:
        verbose_name = 'Geometry'
        verbose_name_plural = 'Geometries'

    def __str__(self):
        return self.geom_type

class Location(models.Model):
    name = models.CharField('Location Name', max_length = 50)
    geom_type = models.ForeignKey(Geometry, on_delete=models.CASCADE)

    def __str__(self):
        return self.name

class Point(models.Model):
    name = models.OneToOneField(Location, on_delete=models.CASCADE)
    geometry = models.PointField()
    
    def __str__(self):
        return self.name.name

class Linestring(models.Model):
    name = models.OneToOneField(Location, on_delete=models.CASCADE)
    geometry = models.LineStringField()

    def __str__(self):
        return self.name.name

class Polygon(models.Model):
    name = models.OneToOneField(Location, on_delete=models.CASCADE)
    geometry = models.PolygonField()

    def __str__(self):
        return self.name.name

Admin

from django.contrib.gis import admin
from leaflet.admin import LeafletGeoAdmin, LeafletGeoAdminMixin
from .models import Geometry, Location, Point, Linestring, Polygon
   
class GeometryAdmin(admin.ModelAdmin):
    list_display = ('id', 'geom_type')

admin.site.register(Geometry, GeometryAdmin)

class PointInline(LeafletGeoAdminMixin, admin.StackedInline):
    model = Point

class LinestringInline(LeafletGeoAdminMixin, admin.StackedInline):
    model = Linestring

class PolygonInline(LeafletGeoAdminMixin, admin.StackedInline):
    model = Polygon

class LocationAdmin(admin.ModelAdmin):
    model = Location
    list_display = ('id', 'name', 'geom_type')
    inlines = [
        PointInline,
        LinestringInline,
        PolygonInline
    ]
       
admin.site.register(Location, LocationAdmin)

All three inlines show up correctly with the code above as expected. However, when I try to incorporate the conditional logic with different variations of get_inlines or get_inline_instances it always just ends up displaying the inline associated with the final "else" statement.

My failed attempt

def get_inlines(self, request, obj: Location):
    if obj.geom_type == 'Point':
        return [PointInline]
    elif obj.geom_type == 'Location':
        return [LinestringInline]
    elif obj.geom_type == 'Polygon':
        return [PolygonInline]
    else:
        return []

I believe the problem occurs because conditional statements are not referencing the model field correctly. But I can't seem to stumble upon the correct way to achieve my expected outcome.


Solution

  • Use related_name in model like below:

    next_question = models.ForeignKey(Question, on_delete=models.CASCADE, null = True, blank = True, related_name='next_question', limit_choices_to={'is_active': True})
    

    and then fk_name Like the example below: Then try. Hope you can find a solution by yourself.

    class Labels(admin.TabularInline):
        model = Label
        extra = 0
        fk_name = "next_question"
    

    Use admin.StackedInline for OneToOne and admin.TabularInline for ForeignKey.

    class ProfileInline(admin.StackedInline):
        model = Profile
        can_delete = False  
    

    Create separate admin for 'Geometry' and 'Location' if you stacked.