Opened 6 years ago

Closed 4 years ago

#29497 closed Bug (fixed)

Saving parent object after setting on child leads to unexpected data loss in bulk_create().

Reported by: Robin Ramael Owned by: Hannes Ljungberg
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords: orm, foreign key, bulk_create
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Example:

class Country(models.Model):
    name = models.CharField(max_length=255)
    iso_two_letter = models.CharField(max_length=2)
    description = models.TextField()


class City(models.Model):
    name = models.CharField(max_length=255)
    country = models.ForeignKey(Country, on_delete=models.CASCADE)


class BulkCreateTests(TestCase):
    def test_fk_bug(self):
        country_nl = Country(name='Netherlands', iso_two_letter='NL')
        country_be = Country(name='Belgium', iso_two_letter='BE')

        city = City(country=country_be, name='Brussels')  # (1)
        country_be.save()  # (2)

        city.save()

This results in an integrity error:

======================================================================
ERROR: test_fk_bug (bulk_create.tests.BulkCreateTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/robin/src/django/django/db/backends/utils.py", line 85, in _execute
    return self.cursor.execute(sql, params)
psycopg2.IntegrityError: null value in column "country_id" violates not-null constraint
DETAIL:  Failing row contains (1, Brussels, null).


The above exception was the direct cause of the following exception:

...

----------------------------------------------------------------------

I wonder wether there's a reason that this doesn't work. If setting a related object on a model instance automatically sets instance.related_object_id, you'd expect this behavior to continue working if the related object receives its primary key after the referencing instance was initialized.

Of course, switching lines (1) and (2) makes it all work, but this behavior means that bulk creating with related objects (with postgres returning the primary keys, bless it's heart) becomes more complex than it should be, forcing the user to use mappings or even set the foreign keys themselves:

for country_data, city_data in data:
    country = Country(**country_data)
    countries.append(country)
    city = City(country=country, **city_data)
    cities.append(city)

Country.objects.bulk_create(countries)

# needs this for the bulk create to not give an integrity error:
for city in cities:
    city.country_id = city.country.id

City.objects.bulk_create(cities)

Ideally, the main instance would, when saved, 'reach into' its related objects and set the foreign key field like the loop does. Is there a reason this can't work?

Change History (4)

comment:1 by Tim Graham, 6 years ago

Resolution: duplicate
Status: newclosed

Duplicate of #29085.

comment:2 by Mariusz Felisiak, 4 years ago

Resolution: duplicate
Status: closednew
Summary: Initializing model instance with an unsaved related object, then saving that related object and then saving the instance doesn't workSaving parent object after setting on child leads to unexpected data loss in bulk_create().
Triage Stage: UnreviewedAccepted

Fix for this case was missing in the main ticket (#28147).

#32133 was closed as a duplicate.

comment:3 by Mariusz Felisiak, 4 years ago

Has patch: set
Owner: changed from nobody to Hannes Ljungberg
Status: newassigned

comment:4 by Mariusz Felisiak <felisiak.mariusz@…>, 4 years ago

Resolution: fixed
Status: assignedclosed

In 10f8b82d:

Fixed #29497 -- Fixed loss of assigned parent when saving child with bulk_create() after parent.

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