question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Allow FieldTracker on Django user model (and other children of abstract base classes)

See original GitHub issue

Problem

Here is a branch with failing tests that I think should work (and that I think worked in earlier versions?): https://github.com/jazzband/django-model-utils/compare/master...jcushman:abstract-test-failure

This seems to be related to the stuff that @lucaswiman added in #317. @lucaswiman, I’m hoping based on that work you might have some clever idea for how to fix this. 😃

Here’s the situation: you have an abstract base class that defines a static attribute like is_active = True, and a concrete model inheriting from that class that defines a field like is_active = models.BooleanField(default=True). The model then throws an error on save():

from django.contrib.auth.models import AbstractUser

class MyUser(AbstractUser):
    tracker = FieldTracker()

MyUser().save()

Result:

    def __get__(self, instance, owner):
        if instance is None:
            return self
        was_deferred = self.field_name in instance.get_deferred_fields()
>       value = self.descriptor.__get__(instance, owner)
E       AttributeError: 'bool' object has no attribute '__get__'

model_utils/tracker.py:43: AttributeError

It would be great for this to work, because tracking changes on the Django user model is handy.

Debugging that error, I found the problem boils down to this:

class AbstractUser(models.Model):
    is_active = True

    class Meta:
        abstract = True

class MyUser(AbstractUser):
    is_active = models.BooleanField(default=True)
    tracker = FieldTracker()

MyUser().save()

The reason that fails is https://github.com/jazzband/django-model-utils/blob/master/model_utils/tracker.py#L218 :

descriptor = getattr(sender, field_name)
...
setattr(sender, field_name, wrapped_descriptor)

… which boils down to setting MyUser.is_active = DescriptorWrapper(MyUser.is_active). And that doesn’t work because you expect MyUser.is_active to start with a value of DeferredAttribute('is_active'), but it actually returns True. For reasons I don’t understand, when you override a static attribute on an abstract class, you get the base class’s value back instead of the subclass’s.

I tried tweaking tracker.py with variations on descriptor = getattr(sender, field_name) if field_name in sender.__dict__ else DeferredAttribute(field_name), but that broke foreign key fields and feels pretty janky anyway.

Any ideas on how to get this working?

Thanks!

Environment

  • Django Model Utils version: master
  • Django version: 2.1
  • Python version: 3.6
  • Other libraries used, if any:

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:1
  • Comments:27 (21 by maintainers)

github_iconTop GitHub Comments

3reactions
dmugtasimovcommented, Dec 6, 2020

The reason for this issue is that django.contrib.auth.models.AbstractBaseUser has class level attribute is_active:

class AbstractBaseUser(models.Model):
    password = models.CharField(_('password'), max_length=128)
    last_login = models.DateTimeField(_('last login'), blank=True, null=True)

    is_active = True
(Pdb++) instance.is_active
True
(Pdb++) instance.is_active = False
(Pdb++) instance.is_active
True

After applying this work around it works just fine:

if hasattr(AbstractBaseUser, 'is_active'):
    del AbstractBaseUser.is_active
(Pdb++) instance.is_active
False
(Pdb++) instance.is_active = True
(Pdb++) instance.is_active
True
(Pdb++) instance.is_active = False
(Pdb++) instance.is_active
False

I think it deserves a fix either in django-model-utlils or in Django.

3reactions
mxschmittcommented, Aug 22, 2019

The issue for me is still persistent in version 3.2.0. 😕 Same behavior with the is_active attribute like described above.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Accessing all children of abstract base class in django?
I'm looking at the abstract base classes right now. Assume that I have a few models like this: class App(models.Model): name = CharField( ......
Read more >
django-model-utils Documentation
A DateTimeField subclass that monitors another field on the model, ... An abstract base class for any model that expresses a time-range.
Read more >
django-model-utils 1.4.0 - PyPI
This abstract base class just provides self-updating created and modified fields on any model that inherits from it. QueryManager. Many custom model managers...
Read more >
Using abstract models in Django - DEV Community ‍ ‍
All you have to do is create a base model with all the common fields and set abstract = True in the meta...
Read more >
How to create an abstract model class in Django - Dev Genius
“Abstract base classes are useful when you want to put some common information into a number of other models. You write your base...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found