#33004: Inconsistent / Unexpected handling of assigning unsaved model to Generic
Foreign Key
-------------------------------------+-------------------------------------
               Reporter:             |          Owner:  nobody
  Finndersen                         |
                   Type:             |         Status:  new
  Cleanup/optimization               |
              Component:  Database   |        Version:  dev
  layer (models, ORM)                |       Keywords:  fk, gfk, generic
               Severity:  Normal     |  foreign key, validation
           Triage Stage:             |      Has patch:  0
  Unreviewed                         |
    Needs documentation:  0          |    Needs tests:  0
Patch needs improvement:  0          |  Easy pickings:  0
                  UI/UX:  0          |
-------------------------------------+-------------------------------------
 https://code.djangoproject.com/ticket/10811 addresses the issue of
 assigned an unsaved model to a ForeignKey or OneToOneField (raises error
 when save() called), however the same logic / pattern does not apply to
 GFKs.

 Given:

 {{{#!python
 class ModelA(models.Model):
     name = models.CharField(max_length=20)


 class ModelB(models.Model):
     gfk_ctype = models.ForeignKey(ContentType, on_delete=models.PROTECT)
     gfk_id = models.PositiveIntegerField()
     gfk = GenericForeignKey('gfk_ctype', 'gfk_id')


 class ModelC(models.Model):
     fk = models.ForeignKey(ModelA, on_delete=models.CASCADE)
 }}}


 Foreign Key Behaviour:

 {{{
 In [2]: a = ModelA(name='Model A')

 In [3]: c = ModelC(fk=a)

 In [4]: c.fk
 Out[4]: <ModelA: ModelA object (None)>

 In [5]: c.save()
 ---------------------------------------------------------------------------
 ...
 ValueError: save() prohibited to prevent data loss due to unsaved related
 object 'fk'.

 In [6]: a.save()
 (0.016) INSERT INTO "test_app_modela" ("name") VALUES ('Model A');
 args=['Model A']

 In [7]: c.fk
 Out[7]: <ModelA: ModelA object (1)>

 In [8]: c.save()
 (0.016) INSERT INTO "test_app_modelc" ("fk_id") VALUES (1); args=[1]
 }}}

 GFK behaviour:



 {{{
 In [9]: a2 = ModelA(name='Model A2')

 In [10]: b = ModelB(gfk=a2)

 In [11]: b.gfk
 Out[11]: <ModelA: ModelA object (None)>

 In [12]: b.save()
 (0.000) INSERT INTO "test_app_modelb" ("gfk_ctype_id", "gfk_id") VALUES
 (9, NULL); args=[9, None]
 ---------------------------------------------------------------------------
 IntegrityError: NOT NULL constraint failed: test_app_modelb.gfk_id

 In [14]: b.gfk.save()
 (0.015) INSERT INTO "test_app_modela" ("name") VALUES ('Model A2');
 args=['Model A2']

 In [15]: b.gfk
 (0.000) SELECT "test_app_modela"."id", "test_app_modela"."name" FROM
 "test_app_modela" WHERE "test_app_modela"."id" IS NULL LIMIT 21; args=()
 None

 In [17]: b.gfk_ctype
 Out[17]: <ContentType: test_app | model a>
 }}}

 Two observations:
 * No check on b.gfk and b.gfk_id value during save() which could lead to
 silent data loss if b.gfk_id is nullable.
 * When a2 is saved, accessing b.gfk now does a redundant DB query to try
 and find ModelA instance with PK = None, then then returns None value
 (effectively un-assigning a2 model), while keeping b.gfk_ctype intact.
 This is because the new pk of a2 is different to the existing gfk_id
 (pk_val) of the GFK field (None)

 What should be done:
 * Modify Model.save() or Model._prepare_related_fields_for_save() to also
 perform verification check for GFK fields
 * Modify GenericForeignKey.__get__() to handle case of pk_val = None
 (update fk_field value using PK value of GFK model if present, do not
 perform redundant DB query on pk=None, return previously assigned (then
 saved) model instead of None)

-- 
Ticket URL: <https://code.djangoproject.com/ticket/33004>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/053.8deb458a1d22243c93cb974224d2f420%40djangoproject.com.

Reply via email to