#29655: Cannot enter Django admin interface when model instances are validated 
on
save
-------------------------------------------+------------------------
               Reporter:  Evgeny Arshinov  |          Owner:  nobody
                   Type:  Bug              |         Status:  new
              Component:  Uncategorized    |        Version:  2.1
               Severity:  Normal           |       Keywords:
           Triage Stage:  Unreviewed       |      Has patch:  0
    Needs documentation:  0                |    Needs tests:  0
Patch needs improvement:  0                |  Easy pickings:  0
                  UI/UX:  0                |
-------------------------------------------+------------------------
 Hello,

 In #29549 I was advised to enable model validation on save to properly
 validate choice fields, like this:

 {{{
 @receiver(pre_save)
 def pre_save_handler(sender, instance, *args, **kwargs):
     instance.full_clean()
 }}}

 Turned out this approach breaks Django session's middleware and, as a
 result, no one can enter Django admin interface (which uses the session
 middleware).

 Scenario:
 1. Use a project that includes Django admin interface.
 2. Enable model validation:

 {{{
 from django.db.models.signals import pre_save
 from django.dispatch import receiver

 @receiver(pre_save)
 def pre_save_handler(sender, instance, *args, **kwargs):
     instance.full_clean()
 }}}

 3. Visit Django admin interface start page (`/admin`).
 4. Enter valid credentials.

 The result is error 500 due to a `ValidationError`:

 {{{
 Internal Server Error: /admin/login/
 Traceback (most recent call last):
   File "C:\django_sample\venv\lib\site-
 packages\django\core\handlers\exception.py", line 34, in inner
     response = get_response(request)
   File "C:\django_sample\venv\lib\site-
 packages\django\utils\deprecation.py", line 93, in __call__
     response = self.process_response(request, response)
   File "C:\django_sample\venv\lib\site-
 packages\django\contrib\sessions\middleware.py", line 58, in
 process_response
     request.session.save()
   File "C:\django_sample\venv\lib\site-
 packages\django\contrib\sessions\backends\db.py", line 87, in save
     obj.save(force_insert=must_create, force_update=not must_create,
 using=using)
   File "C:\django_sample\venv\lib\site-packages\django\db\models\base.py",
 line 717, in save
     force_update=force_update, update_fields=update_fields)
   File "C:\django_sample\venv\lib\site-packages\django\db\models\base.py",
 line 742, in save_base
     update_fields=update_fields,
   File "C:\django_sample\venv\lib\site-
 packages\django\dispatch\dispatcher.py", line 175, in send
     for receiver in self._live_receivers(sender)
   File "C:\django_sample\venv\lib\site-
 packages\django\dispatch\dispatcher.py", line 175, in <listcomp>
     for receiver in self._live_receivers(sender)
   File "C:\django_sample\django_sample\models.py", line 41, in
 pre_save_handler
     instance.full_clean()
   File "C:\django_sample\venv\lib\site-packages\django\db\models\base.py",
 line 1151, in full_clean
     raise ValidationError(errors)
 django.core.exceptions.ValidationError: {'session_key': ['Session with
 this Session key already exists.']}
 }}}

 We dug this problem a bit, and found out that during the login procedure
 saving a `django.contrib.sessions.Session` instance is performed twice
 with the same session key:
 - In the first case, the object is saved with `force_insert=True,
 force_update=False`
 - In the second case, the object is saved with `force_insert=False,
 force_update=True`
 - Hovewer, in both cases the object's `_state.adding` flag is `True`,
 which causes `django.db.Model._perform_unique_checks` to include the value
 of the unique field of the object being saved (in this case, a session
 key) in unique validation. Since there is already an object with the given
 session key in the database after the first save, `validate_unique`
 reports an error.

 Traceback for the first save operation (some bottom frames omitted for
 brevity):

 {{{
   ...
   File "C:\django_sample\venv\lib\site-
 packages\django\contrib\auth\views.py", line 90, in form_valid
     auth_login(self.request, form.get_user())
   File "C:\django_sample\venv\lib\site-
 packages\django\contrib\auth\__init__.py", line 108, in login
     request.session.cycle_key()
   File "C:\django_sample\venv\lib\site-
 packages\django\contrib\sessions\backends\base.py", line 298, in cycle_key
     self.create()
   File "C:\django_sample\venv\lib\site-
 packages\django\contrib\sessions\backends\db.py", line 55, in create
     self.save(must_create=True)
   File "C:\django_sample\venv\lib\site-
 packages\django\contrib\sessions\backends\db.py", line 87, in save
     obj.save(force_insert=must_create, force_update=not must_create,
 using=using)
   File "C:\django_sample\venv\lib\site-packages\django\db\models\base.py",
 line 717, in save
     force_update=force_update, update_fields=update_fields)
   File "C:\django_sample\venv\lib\site-packages\django\db\models\base.py",
 line 742, in save_base
     update_fields=update_fields,
   File "C:\django_sample\venv\lib\site-
 packages\django\dispatch\dispatcher.py", line 175, in send
     for receiver in self._live_receivers(sender)
   File "C:\django_sample\venv\lib\site-
 packages\django\dispatch\dispatcher.py", line 175, in <listcomp>
     for receiver in self._live_receivers(sender)
   File "C:\django_sample\django_sample\models.py", line 41, in
 pre_save_handler
     instance.full_clean()
 }}}

 Traceback for the second save operation:

 {{{
   ...
   File "C:\django_sample\venv\lib\site-
 packages\django\contrib\sessions\middleware.py", line 58, in
 process_response
     request.session.save()
   File "C:\django_sample\venv\lib\site-
 packages\django\contrib\sessions\backends\db.py", line 87, in save
     obj.save(force_insert=must_create, force_update=not must_create,
 using=using)
   File "C:\django_sample\venv\lib\site-packages\django\db\models\base.py",
 line 717, in save
     force_update=force_update, update_fields=update_fields)
   File "C:\django_sample\venv\lib\site-packages\django\db\models\base.py",
 line 742, in save_base
     update_fields=update_fields,
   File "C:\django_sample\venv\lib\site-
 packages\django\dispatch\dispatcher.py", line 175, in send
     for receiver in self._live_receivers(sender)
   File "C:\django_sample\venv\lib\site-
 packages\django\dispatch\dispatcher.py", line 175, in <listcomp>
     for receiver in self._live_receivers(sender)
   File "C:\django_sample\django_sample\models.py", line 41, in
 pre_save_handler
     instance.full_clean()
 }}}

-- 
Ticket URL: <https://code.djangoproject.com/ticket/29655>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-updates+unsubscr...@googlegroups.com.
To post to this group, send email to django-updates@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/052.012a4efa3d8c0514570d973051db1fc4%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to