Opened 2 years ago

Closed 10 months ago

#35729 closed Bug (fixed)

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: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
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 defines natural_key and django excludes the pk from the list of dumped fields when a natural_key exists, see ​https://github.com/django/django/blob/c6a4f853c7167c1001761dcff30d7a64690e8236/django/core/serializers/python.py#L37.
  • 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.

Change History (9)

comment:1 by Natalia Bidart, 2 years ago

Component: Uncategorized → contrib.auth
Resolution: → invalid
Status: new → closed
Type: Uncategorized → New feature
Version: 5.1 → dev

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 invalid following 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.

comment:2 by Jacob Walls, 12 months 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?

Last edited 12 months ago by Jacob Walls (previous) (diff)

comment:3 by Jacob Walls, 12 months ago

Summary: How to serialize user profiles without natural keys? → Subclasses cannot opt out of natural key serialization

comment:4 by Jacob Walls, 12 months ago

Resolution: invalid
Status: closed → new

comment:5 by rimchoi, 12 months ago

Owner: set to rimchoi
Status: new → assigned

comment:6 by rimchoi, 12 months ago

Has patch: set

comment:7 by Jacob Walls, 12 months ago

Patch needs improvement: set

Great start here. Left a few comments to help advance this.

comment:8 by Jacob Walls, 10 months ago

Patch needs improvement: unset
Triage Stage: Accepted → Ready for checkin

comment:9 by Jacob Walls <jacobtylerwalls@…>, 10 months ago

Resolution: → fixed
Status: assigned → closed

In 93540b34:

Fixed #35729 -- Enabled natural key serialization opt-out for subclasses.

Refactored serialization logic to allow models inheriting a natural_key()
method (e.g. AbstractBaseUser) to explicitly opt out of natural key
serialization by returning an empty tuple from the method.

Thanks Jonas Dittrich for the report.

Co-authored-by: Jacob Walls <jacobtylerwalls@…>

Note: See TracTickets for help on using tickets.
Back to Top