#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.

Reply via email to