﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
29568	Avoid trying to UPDATE a parent model when its child just got INSERT	François Dupayrat	nobody	"Hello,

While sorting through queries, I noticed something unusual: let's say you have non-abstract models inheritance:
{{{
class Parent(models.Model):
  parent_field = models.BooleanField()

class DirectChild(Parent):
  direct_child_field = models.BooleanField()

class IndirectChild(DirectChild):
  indirect_child_field = models.BooleanField()
}}}

When trying to create IndirectChild, I would expect 3 queries to be done:
* INSERT Parent
* INSERT DirectChild
* INSERT IndirectChild

Instead, since they all share the same PK, when trying to save a IndirectChild not yet persisted to DB, 5 queries are done:
* INSERT Parent
* UPDATE DirectChild (since it has a PK)
* INSERT DirectChild (because previous UPDATE failed)
* UPDATE IndirectChild (since it has a PK)
* INSERT IndirectChild (because previous UPDATE failed)

Note: when trying to use IndirectChild.objects.create, only 4 queries are made, since QuerySet.create use force_insert=True (thanks to Tim Graham)

I found a fix that worked for me by modifying _save_parents and save_base in django/db/models/base.py: _save_parents return if something was inserted (default to fAlse if there is no parent), and force_insert if the parent was inserted in both methods (because if there parent was inserted, the child can't possibly exist).

Being a beginner, I'm really wary of breaking something. Could someone if it's something wanted by Django and check if there is obvious mistake?
Here is the modified method (without irrelevant code, materialized as [...]):
{{{
    def _save_parents([...]):
        [...]
        inserted = False
        for parent, field in meta.parents.items():
            [...]
            parent_inserted = self._save_parents(cls=parent, using=using, update_fields=update_fields)
            updated = self._save_table(cls=parent, using=using, update_fields=update_fields, force_insert=parent_inserted)
            if not updated:
                inserted = True
            [...]
        return inserted

    def save_base([...]):
        [...]
        with transaction.atomic(using=using, savepoint=False):
            parent_inserted = False
            if not raw:
                parent_inserted = self._save_parents(cls, using, update_fields)
            updated = self._save_table(raw, cls, force_insert or parent_inserted, force_update, using, update_fields)
        [...]
}}}"	Cleanup/optimization	closed	Database layer (models, ORM)	dev	Normal	fixed			Accepted	1	0	0	0	0	0
