#29655 closed Bug (invalid)
Cannot enter Django admin interface when model instances are validated on save
Reported by: | Evgeny Arshinov | Owned by: | nobody |
---|---|---|---|
Component: | Uncategorized | Version: | 2.1 |
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
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:
- Use a project that includes Django admin interface.
- 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()
- Visit Django admin interface start page (
/admin
). - 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 isTrue
, which causesdjango.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()
Change History (4)
comment:1 by , 6 years ago
comment:2 by , 6 years ago
As a temporary solution in our project we excluded instances of django.contrib.sessions.Session
objects from validation:
@receiver(pre_save) def pre_save_handler(sender, instance, *args, **kwargs): if not isinstance(instance, Session): instance.full_clean() pass
but it certainly is an ugly workaround.
comment:3 by , 6 years ago
Resolution: | → invalid |
---|---|
Status: | new → closed |
Enabling model validation on save is likely to break a few things and degrade performance significantly. I don't think that's a pattern we should encourage or support at this point.
Django and third-party applications simply don't expect ValidationError
to be thrown on save()
, this effectively change the signature of the function.
I have committed a demo project:
https://github.com/earshinov/django_sample/tree/issue-29655
For reproducing:
Next, visit http://localhost:9726/admin/, enter the credentials and click the [Log in] button.