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: