We want to have a custom UserProfile that inherits from AbstractBaseUser without the need to define a natural key. There should be a nice way to do this.
Our custom UserProfile inherits from AbstractBaseUser. We don’t have any possible unique field combination that we could use as a natural_key. (Our USERNAME_FIELD is not unique. It’s an email that might be NULL for multiple users)
When dumping data, we want to use natural_foreign=True and natural_primary=True matching the
documentation recommendations, see django serialization docs.
Now, AbstractBaseUser defines the natural_key function to return the value of USERNAME_FIELD and there doesn’t seem to be an alternative implementation in our derived class.
Is there a way to dump our data using natural_foreign=True and natural_primary=True without serializing the user profile with natural keys?
What we tried so far:
making natural_key just return (pk,). This does not work. The pk of the user profile is not serialized because the model defines natural_key and django excludes the pk from the list of dumped fields when a natural_key exists, see source.
overwriting __getattribute__ on the user profile, raising an AttributeError when natural_key is requested. M2M fields call hasattr on the class, not on the instance which has the modified __getattribute__ function.
trying to delete the natural_key function from our user profile class using del and delattr. hasattr finds the function from the superclass; our user profile does not define the natural_key function.
Basically the problem is that removing the natural_key function from the child class violates Liskov’s substitution principle.
In theory, we could del AbstractBaseUser.natural_key but this deeply interferes with Django; we don’t want to do that.
I created ticket 35729 at first, but got forwarded to the forum instead.
I’m convinced that this is a bug in Django, at least in the documentation, but I think it also warrants changes in the code.
The docs on custom user models explicitly say that using AbstractBaseUser is allowed (and recommend it as the “easiest way to construct a compliant custom user model”) without requiring any additional unique/key fields on the model and without requiring users to override natural_key. This is wrong. Natural key serialization will be (subtly) broken, without any hints in the documentation.
I can see that a default implementation of AbstractBaseUser.natural_key has value for some projects, but when the user model simply does not have any other key fields, there should be a canonical way to opt-out.
del AbstractBaseUser.natural_key cannot be the canonical way to solve this. Overriding object attributes (which we could do by overriding __getattribute__) does not work, since Django also tests for existence for natural_key on the model class (also: still hacky).
If we override natural_key to return (self.pk, ), our intention is very clear: Serialize by primary key, this model does not support natural key serialization. Yet, django silently filters out the primary key from the list of serialized fields later. I think it shouldn’t do that, it should follow the intention of the user (or at least produce an error).
Forcing projects to use del AbstractBaseUser.natural_key makes this not just a documentation issue, but also a usability bug.
I’m thus asking (cc @nessita) for ticket 35729 to be reopened, maybe with an adapted title (suggestion: Inheriting user model from AbstractBaseUser results in unexpected (unfixable) broken natural_key implementation)
While I agree with the facts of what you have discovered, I disagree with the conclusion.
Fundamentally, as I see it, you have a situation where inheriting from AbstractBaseUser is not suitable for your use-case.
Like any other inheritance type situation, there are some assumptions and preconditions that must exist before you consider inheritance as the structural solution for your class implementations. One of these assumptions are that the subclass only adds to the parent class, it does not try to subtract from that class.
So while the docs do say that it is the easiest way to implement a custom user class, there is no implication that it is the only way.
My conclusion then is that in your situation, inheriting from AbstractBaseUser is not the right answer, and that the appropriate solution would be for you to create your own User class. That the current AbstractBaseUser class is not suitable for your needs is not a bug.
Then, I think this the documentation needs to be adapted to include these assumptions, which are not obvious IMHO.
Two paragraphs mention that the USERNAME_FIELD should be unique, unless using a custom authentication backend - we have a custom authentication backend.
The docs do not mention that you have to implement a custom natural_key, if the username field is not unique and that AbstractBaseUser should not be used if there is no suitable unique group of fields for this natural_key implementation.
IMHO this is a major limitation, unnecessarily forcing users to re-implement AbstractBaseUser.