Search code examples
djangodjango-admininline-formset

Django. Filter new entry inline with data based on parent pk


Making good progress with my Django project but for this sticking usability issue. I want to be able to filter dropdowns in inline forms based on the parent id. For example.

The Theme model belongs to a course.

class Theme (models.Model):
    id = models.AutoField(primary_key=True) # AutoField?
    code = models.CharField(max_length=5)
    course = models.ForeignKey(Course)
    theme_text = models.CharField(max_length=50)
    description = models.TextField(blank=True, max_length=1000)
    zorder = models.IntegerField()

The indicator model also belongs to a course

class Indicator (models.Model):
    id = models.AutoField(primary_key=True) # AutoField?
    code = models.CharField(max_length=10)
    indicator_text = models.TextField(blank=True)
    explained = models.TextField("Explained",blank=True)
    course = models.ForeignKey('curriculum.Course')
    theme = models.ForeignKey(Theme)        
    strand = models.ForeignKey(Strand,blank=True,null=True)
    level = models.ForeignKey(Level,blank=True,null=True)
    concept = models.ManyToManyField(Concept,blank=True)

The admin theme form calls an Indicator Inline

class ThemeAdmin(admin.ModelAdmin):
    list_display = ['code', 'theme_text','description','course']
    list_filter = ['course']
    inlines = [InlineIndicator]

which in turn calls a IndicatorInlineForm

class InlineIndicator(admin.TabularInline):
    form = IndicatorInlineForm
    model = Indicator
    extra = 0

the IndicatorInlineForm filters data to some fields (course, theme, strand and concept)

class IndicatorInlineForm (forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(IndicatorInlineForm, self).__init__(*args, **kwargs)
        if self.instance.pk is not None:
            self.fields['course'].queryset = Course.objects.filter(id=self.instance.course)
            self.fields['theme'].queryset = Theme.objects.filter(course=self.instance.course)
            self.fields['strand'].queryset = Strand.objects.filter(course=self.instance.course)
            self.fields['concept'].queryset = Concept.objects.filter(course=self.instance.course)
        else:
            self.fields['course'].queryset = Course.objects.filter(id=4)
            self.fields['theme'].queryset = Theme.objects.filter(course=4)
            self.fields['strand'].queryset = Strand.objects.filter(course=4)
            self.fields['concept'].queryset = Concept.objects.filter(course=4)

This is working for existing records where the course id has been set in the indicator record but it is not yet working for new indicator records.

I would like new indicator records to use the course the course.id as per the theme to filter the available options.

I'm not sure I see how the suggested duplicate solves this as it is for existing records or maybe I am wrong in which case help me!

I've been reading about formsets but it feels like it maybe over complicating it. I'm also a bit confused about where it would slot in with what I have already.

Thanks in anticipation of your help (and all the help so far).

Chris


Solution

  • I was able to answer this using formfield-for-foreignkey-and-inline-admin although it does not appear to work for the concept manytomany field.

        def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        changelist_filters = request.GET['_changelist_filters'].rsplit("=",1)
        course_id = int(changelist_filters[1])
    
        if db_field.name == 'course':
            kwargs['queryset'] = Course.objects.filter(pk = course_id)
        elif db_field.name == 'strand':
                kwargs['queryset'] = Strand.objects.filter(course = course_id)
        elif db_field.name == 'concept':
                kwargs['queryset'] = Concept.objects.filter(course = course_id)
    
        return super(InlineIndicator, self).formfield_for_foreignkey(db_field, request, **kwargs)   
    

    I removed the filters from the IndicatorInlineForm as these were no longer required.