Code

Opened 20 months ago

Last modified 16 months ago

#19255 new Bug

Cannot validate generic foreignkey in new instance

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

Description

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:

generic.py:412
    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,
            self.ct_fk_field.get_attname(): 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.

Attachments (0)

Change History (2)

comment:1 Changed 20 months 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 import generic
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 = generic.GenericForeignKey()

    def clean(self):
        assert self.object_id
        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 import generic
from django.contrib import admin
from testcase.models import Model, AnotherModel 

class ModelInline(generic.GenericStackedInline):
    model = Model

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

admin.site.register(AnotherModel, AnotherModelAdmin)

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

Environment:


Request Method: POST
Request URL: http://127.0.0.1:8000/admin/testcase/anothermodel/add/

Django Version: 1.6.dev20121122090508
Python Version: 2.7.2
Installed Applications:
('django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'testcase',
 'django.contrib.admin')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware')


Traceback:
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/core/handlers/base.py" 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/options.py" 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/decorators.py" in _wrapped_view
  91.                     response = view_func(request, *args, **kwargs)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/views/decorators/cache.py" 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/sites.py" in inner
  202.             return view(request, *args, **kwargs)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/utils/decorators.py" in _wrapper
  25.             return bound_func(*args, **kwargs)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/utils/decorators.py" in _wrapped_view
  91.                     response = view_func(request, *args, **kwargs)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/utils/decorators.py" in bound_func
  21.                 return func(self, *args2, **kwargs2)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/db/transaction.py" in inner
  208.                 return func(*args, **kwargs)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/contrib/admin/options.py" in add_view
  1035.             if all_valid(formsets) and form_validated:
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/formsets.py" in all_valid
  380.         if not formset.is_valid():
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/formsets.py" in is_valid
  277.         err = self.errors
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/formsets.py" in errors
  259.             self.full_clean()
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/formsets.py" in full_clean
  298.             self._errors.append(form.errors)
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/forms.py" in _get_errors
  117.             self.full_clean()
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/forms.py" in full_clean
  274.         self._post_clean()
File "/Users/matt/Code/envs/django/lib/python2.7/site-packages/django/forms/models.py" in _post_clean
  333.             self.instance.clean()
File "/Users/matt/Code/envs/django/tickets/19255/testsite/testcase/models.py" in clean
  12.         assert self.object_id

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

comment:2 Changed 16 months ago by aaugustin

  • Type changed from Uncategorized to Bug

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as new
The owner will be changed from nobody to anonymous. Next status will be 'assigned'
as The resolution will be set. Next status will be 'closed'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.