Opened 14 months ago
Last modified 3 weeks ago
#35729 assigned Bug
Subclasses cannot opt out of natural key serialization
| Reported by: | Jonas Dittrich | Owned by: | rimchoi |
|---|---|---|---|
| Component: | Core (Serialization) | Version: | dev |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Accepted | |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | yes |
| Easy pickings: | no | UI/UX: | no |
Description
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 https://docs.djangoproject.com/en/5.1/topics/serialization/#natural-keys.
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 definesnatural_keyand django excludes the pk from the list of dumped fields when anatural_keyexists, see https://github.com/django/django/blob/c6a4f853c7167c1001761dcff30d7a64690e8236/django/core/serializers/python.py#L37. - overwriting
__getattribute__on the user profile, raising anAttributeErrorwhennatural_keyis requested. M2M fields callhasattron the class, not on the instance which has the modified__getattribute__function. - trying to delete the
natural_keyfunction from our user profile class usingdelanddelattr.hasattrfinds the function from the superclass; our user profile does not define thenatural_keyfunction.
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.
Change History (7)
comment:1 by , 14 months ago
| Component: | Uncategorized → contrib.auth |
|---|---|
| Resolution: | → invalid |
| Status: | new → closed |
| Type: | Uncategorized → New feature |
| Version: | 5.1 → dev |
comment:2 by , 7 weeks ago
| Component: | contrib.auth → Core (Serialization) |
|---|---|
| Triage Stage: | Unreviewed → Accepted |
| Type: | New feature → Bug |
After further discussion on the forum, and in a related ticket (#36225), I agree this is a usability bug in the serialization framework. The user model aspect is just orthogonal.
Django's pattern of calling hasattr(obj, "natural_key") is not subclass-friendly as pointed out above. There needs to be some way to cancel natural key serialization in a subclass and check for that. Maybe def natural_key() -> tuple[str] becomes def natural_key() -> tuple[str] | None, and hasattr(obj, "natural_key") becomes some friendly wrapper for getattr(obj, "natural_key", lambda obj: None)(obj) is not None?
comment:3 by , 7 weeks ago
| Summary: | How to serialize user profiles without natural keys? → Subclasses cannot opt out of natural key serialization |
|---|
comment:4 by , 7 weeks ago
| Resolution: | invalid |
|---|---|
| Status: | closed → new |
comment:5 by , 6 weeks ago
| Owner: | set to |
|---|---|
| Status: | new → assigned |
comment:6 by , 5 weeks ago
| Has patch: | set |
|---|
comment:7 by , 3 weeks ago
| Patch needs improvement: | set |
|---|
Great start here. Left a few comments to help advance this.
Hello Jonas, thank you for your ticket!
This report seems better suited to be a support request. The best place to get answers to your issue is using any of the user support channels from this link.
Since the goal of this issue tracker is to track issues about Django itself, and this report is about how to use Django for a specific niche need, I'll be closing this ticket as
invalidfollowing the ticket triaging process. If, after debugging or further conversations in the forum, you find out that this is indeed a bug in Django, please re-open with the specific details and please be sure to include a small Django project to reproduce or a failing test case.