Opened 3 years ago

Last modified 3 days ago

#19255 new Bug

BaseGenericInlineFormSet runs validation methods before linking form instances to their related object

Reported by: bouke Owned by: nobody
Component: contrib.contenttypes Version: 1.4
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no


Given this model:

class Model(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    object = generic.GenericForeignKey()

    def clean(self):
        assert self.object_id
        assert self.object

and this admin:

class ModelInline(generic.GenericStackedInline):
    model = Model

When saving a new Model, the assertion will fail. The object will not be linked, nor will the object_id be available to the clean function. This is because the fields are excluded from the formset and the values are set through the generic InlineAdmin, see this excerpt:
    def save_new(self, form, commit=True):
        # Avoid a circular import.
        from django.contrib.contenttypes.models import ContentType
        kwargs = {
            self.ct_field.get_attname(): ContentType.objects.get_for_model(self.instance).pk,
        new_obj = self.model(**kwargs)
        return save_instance(form, new_obj, commit=commit)

To hack around this issue, the forms and formsets involved should not cache the validation result. The clean function should then validate the value if it is present (the second time when cleaning). The hack is quite ugly and requires a lot of coding. It should be possible to easily validate the newly related-to foreign key object.

Change History (3)

comment:1 Changed 3 years ago by matt@…

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Triage Stage changed from Unreviewed to Accepted

I'm able to reproduce the problem by adding an extra field into the Model class and creating another models which Model can point to:

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType

class Model(models.Model):
    number = models.IntegerField()
    content_type = models.ForeignKey(ContentType, related_name='content')
    object_id = models.PositiveIntegerField()
    object = GenericForeignKey()

    def clean(self):
        assert self.object_id

class AnotherModel(models.Model):
    another_number = models.IntegerField()

Then I create a model admin for the other model:

from django.contrib.contenttypes.admin import GenericStackedInline
from django.contrib import admin

from .models import Model, AnotherModel

class ModelInline(GenericStackedInline):
    model = Model

class AnotherModelAdmin(admin.ModelAdmin):
    inlines = (ModelInline,), AnotherModelAdmin)

When I create a new AnotherModel and try to save it I get this:


Request Method: POST
Request URL:

Django Version: 1.6.dev20121122090508
Python Version: 2.7.2
Installed Applications:
Installed Middleware:

File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/core/handlers/" in get_response
  116.                         response = callback(request, *callback_args, **callback_kwargs)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/contrib/admin/" in wrapper
  369.                 return self.admin_site.admin_view(view)(*args, **kwargs)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/utils/" in _wrapped_view
  91.                     response = view_func(request, *args, **kwargs)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/views/decorators/" in _wrapped_view_func
  89.         response = view_func(request, *args, **kwargs)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/contrib/admin/" in inner
  202.             return view(request, *args, **kwargs)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/utils/" in _wrapper
  25.             return bound_func(*args, **kwargs)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/utils/" in _wrapped_view
  91.                     response = view_func(request, *args, **kwargs)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/utils/" in bound_func
  21.                 return func(self, *args2, **kwargs2)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/db/" in inner
  208.                 return func(*args, **kwargs)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/contrib/admin/" in add_view
  1035.             if all_valid(formsets) and form_validated:
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/" in all_valid
  380.         if not formset.is_valid():
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/" in is_valid
  277.         err = self.errors
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/" in errors
  259.             self.full_clean()
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/" in full_clean
  298.             self._errors.append(form.errors)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/" in _get_errors
  117.             self.full_clean()
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/" in full_clean
  274.         self._post_clean()
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/" in _post_clean
  333.             self.instance.clean()
File "/Users/matt/Code/envs/django/tickets/19255/testsite/testcase/" in clean
  12.         assert self.object_id

Exception Type: AssertionError at /admin/testcase/anothermodel/add/
Exception Value: 

Last edited 3 days ago by timgraham (previous) (diff)

comment:2 Changed 3 years ago by aaugustin

  • Type changed from Uncategorized to Bug

comment:3 Changed 3 days ago by timgraham

  • Summary changed from Cannot validate generic foreignkey in new instance to BaseGenericInlineFormSet runs validation methods before linking form instances to their related object

See #25488 for a duplicate which proposes a solution.

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