Opened 9 years ago

Closed 9 years ago

Last modified 9 years ago

#25559 closed Uncategorized (wontfix)

Conditional admin inline causes ValidationError

Reported by: Zach Borboa Owned by: nobody
Component: contrib.admin Version: 1.8
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Attempting to save an object in the Django admin using a conditional to show/hide an admin inline causes ValidationError: ManagementForm data is missing or has been tampered with.

  1. Add a new student. Specify a name ("Alice") and click the Save and continue editing button.
  2. Now click the + to add a Team. Specify a team name ("Team A") and click the Save button.
  3. Back on the student page with the team now selected, click the Save and continue editing button.

Result: ValidationError

Expected: Student object to be saved and the inline to now be displayed.

# myapp/models.py
from django.db import models

class Team(models.Model):
    name = models.CharField(max_length=255)

    def __unicode__(self):
        return self.name

class Category(models.Model):
    name = models.CharField(max_length=255)

class Student(models.Model):
    name = models.CharField(max_length=255)
    team = models.ForeignKey(Team, blank=True, null=True)
    categories = models.ManyToManyField(Category, through='CategoryInfo')

class CategoryInfo(models.Model):
    category = models.ForeignKey(Category)
    student = models.ForeignKey(Student)
    score = models.CharField(max_length=255)
# myapp/admin.py
from django.contrib import admin

from models import Student
from models import Team

class CategoryInlineAdmin(admin.TabularInline):
    model = Student.categories.through

class StudentAdmin(admin.ModelAdmin):
    inlines = []
    def get_inline_instances(self, request, obj=None):
        inlines = self.inlines
        # Display inline when the object has been saved and a team has been selected.
        if obj and obj.team:
            inlines = [CategoryInlineAdmin,]
        return [inline(self.model, self.admin_site) for inline in inlines]

class TeamAdmin(admin.ModelAdmin):
    pass

admin.site.register(Student, StudentAdmin)
admin.site.register(Team, TeamAdmin)

Change History (4)

comment:1 by Tim Graham, 9 years ago

Please see the note for get_inline_instances() in the documentation, "If you override this method, make sure that the returned inlines are instances of the classes defined in inlines or you might encounter a “Bad Request” error when adding related objects." You need inlines=[CategoryInlineAdmin] in the class body I believe. If that's correct, we might amend the docs.

comment:2 by Tim Graham, 9 years ago

Component: Uncategorizedcontrib.admin
Resolution: wontfix
Status: newclosed

After looking at this a bit closer, I don't see a way that Django could generally solve it. Possibly you could fix this in your app by using some JavaScript to insert the necessary formset HTML fields based on whether or not a team is selected. Feel free to reopen if you have some ideas or a patch to share.

comment:3 by Zach Borboa, 9 years ago

Can we document overriding change_view is possible to achieve this?

Here's a solution that works. Use change_view to conditionally display the inline. Replace myapp/admin.py above with this:

# myapp/admin.py
from django.contrib import admin

from models import Student
from models import Team

class CategoryInlineAdmin(admin.TabularInline):
    model = Student.categories.through

class StudentAdmin(admin.ModelAdmin):
    inlines = []

    def change_view(self, request, object_id, form_url='', extra_context=None):
        # Display inline when the object has been saved and a team has been selected.
        self.inlines = []
        try:
            obj = self.model.objects.get(pk=object_id)
        except self.model.DoesNotExist:
            pass
        else:
            if obj.team:
                self.inlines = [CategoryInlineAdmin,]
        return super(StudentAdmin, self).change_view(request, object_id, form_url, extra_context)

class TeamAdmin(admin.ModelAdmin):
    pass

admin.site.register(Student, StudentAdmin)
admin.site.register(Team, TeamAdmin)

comment:4 by Tim Graham, 9 years ago

I'm not sure that modifying self.inlines like that is thread safe.

I don't think documenting every way the admin can be customized has a place in the Django documentation. It's not trivial to maintain examples and to ensure they continue working in future versions of Django.

Note: See TracTickets for help on using tickets.
Back to Top