#28198: Doesn't override existing class attributes with a deferred instance
attribute
-----------------------------------------+------------------------
Reporter: Ryan Hiebert | Owner: nobody
Type: Bug | Status: new
Component: Uncategorized | Version: master
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 |
-----------------------------------------+------------------------
If you have a base model (whether concrete or abstract) with a generic
property set, and it is overridden by a field in a subclass.
Consider the following models:
{{{#!python
from django.db import models
class Super(models.Model):
is_super = True
class Sub(Super):
is_super = models.BooleanField(default=False)
}}}
Then, you would see this in a shell session:
{{{#!pycon
>>> sub = Sub.objects.create()
>>> sub.is_super
False
>>> sub_defer = Sub.objects.defer('is_super').get()
>>> sub_defer.is_super
True
>>> sub_defer.refresh_from_db()
>>> sub_defer.is_super
True
>>> Sub.objects.get().is_super
False
}}}
The deferred field doesn't get fixed, even by calling `refresh_from_db()`.
It also behaves this way if the Super model is an abstract model. A
particular instance of this is with `auth.User`, which displays this
behavior with the `is_active` field, which gets the value from
`AbstractBaseUser`.
{{{#!pycon
>>> from django.contrib.auth.models import User
>>> User.objects.create_user(username='spam', password='eggs',
is_active=False)
<User: spam>
>>> User.objects.get().is_active
False
>>> User.objects.defer('is_active').get().is_active
True
}}}
------
I found this bug while trying to get creative, and loading a sparse User
model with `User.from_db(None, ['id'], [1])`, to avoid a database access
when I just wanted to use it as a filter argument and I already had the
user id. It turned out, though, that it would affect any deferred access
of this property, whether using `from_db`, `defer`, or `only`.
I found this issue in Django 1.11.1, and I've confirmed that it exists in
master, all the way back through 1.8, which was the earliest version I
could use with the manage.py that was created with master Django, since
1.7 was Python 2 only.
I first sent an email to the security list out of an abundance of caution.
Because of the quite limited exposure this would cause, we decided to move
it over to a public issue.
This was the response, with some helpful information as to the cause of
the issue:
> The general problem is that Django doesn't override existing class
attributes with a deferred instance attribute:
https://github.com/django/django/blob/a87189fc5e2e874219d20700cf811345138dd365/django/db/models/fields/__init__.py#L699-L703.
>
> In the case of User.is_active, the class attribute defaulting to True
was added in Django 1.6 by
https://github.com/django/django/commit/4ea8105120121c7ef0d3dd6eb23f2bf5f55b496a
in order to fix https://code.djangoproject.com/ticket/19061.
>
> Unfortunately this looks like it isn't going to be trivial to fix.
--
Ticket URL: <https://code.djangoproject.com/ticket/28198>
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 [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/054.df7a7b7b0cc2362947df8f63b6eada83%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.