#35073 closed Bug (fixed)

models.SET's callable is called when there are no objects to update.

Reported by: Fabio Sangiovanni Owned by: bcail
Component: Database layer (models, ORM) Version: 4.2
Severity: Normal Keywords:
Cc: Simon Charette, bcail 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

Hello everybody.

With an upgrade from Django 4.1 to 4.2 (but also verified in Django 5.0), I've noticed a change in behavior with how on_delete=models.SET is handled.
Given the following models:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=32)

    def __str__(self):
        return self.name


def get_default_person():
    return Person.objects.get_or_create(name="ghost")[0]


class Pet(models.Model):
    name = models.CharField(max_length=32)
    person = models.ForeignKey(Person, related_name="pets", on_delete=models.SET(get_default_person))

    def __str__(self):
        return self.name

I can see what follows in Django 4.2+ (in ./manage.py shell):

>>> from pets.models import Person, Pet
>>> Person.objects.all()
<QuerySet []>
>>> Pet.objects.all()
<QuerySet []>
>>> Person.objects.create(name="johndoe")
<Person: johndoe>
>>> Person.objects.all()
<QuerySet [<Person: johndoe>]>
>>> Person.objects.all().delete()
(1, {'pets.Person': 1})
>>> Person.objects.all()
<QuerySet [<Person: ghost>]>

What is strange to me is that the "ghost" Person instance is created upon deletion of the "johndoe" instance, even if there are no Pets with a ForeignKey to "johndoe".
Django 4.1 behaves differently (no "ghost" Person is created on deletion of other Person objects).

Is this an intended change? I couldn't find any documentation of this in the release notes.

Thanks so much for your help.

Fabio

Change History (10)

comment:1 by O'ktamjon, 12 months ago

Owner: changed from nobody to O'ktamjon
Status: newassigned

comment:2 by Mariusz Felisiak, 12 months ago

Cc: Simon Charette added
Summary: New behavior of ForeignKey with on_delete=models.SET (Django 4.2 and 5.0)models.SET's callable is called when there are no objects to update.
Type: UncategorizedBug

Thanks for the report. Regression in 0701bb8e1f1771b36cdde45602ad377007e372b3.

comment:3 by Simon Charette, 12 months ago

Triage Stage: UnreviewedAccepted

We should fix that.

I think the most straightforward solution is to only set lazy_sub_objs = True on the function returned by SET if the value is not a callable. The same problem exists for SET_DEFAULT when the default is callable.

comment:4 by bcail, 11 months ago

Cc: bcail added

@O'ktamjon are you still working on this? If not, I can work on it.

comment:5 by bcail, 11 months ago

I opened an in-progress PR with two unit tests, for SET and SET_DEFAULT.

I pinged Simon on the PR, because I haven't figured out how to fix SET_DEFAULT yet.

comment:6 by bcail, 11 months ago

Has patch: set

I updated the PR.

comment:7 by Mariusz Felisiak, 11 months ago

Patch needs improvement: set

comment:8 by bcail, 11 months ago

Patch needs improvement: unset

I updated the PR.

comment:9 by Mariusz Felisiak, 11 months ago

Owner: changed from O'ktamjon to bcail
Triage Stage: AcceptedReady for checkin

comment:10 by GitHub <noreply@…>, 11 months ago

Resolution: fixed
Status: assignedclosed

In 9c5e382b:

Fixed #35073 -- Avoided unnecessary calling of callables used by SET/SET_DEFAULT in Collector.collect().

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