#34884: Half bug/half enhancement : inconsistent behavior of get_or_create()
regarding related attributes cache
-------------------------------------+-------------------------------------
Reporter: Laurent Lyaudet | Owner: nobody
Type: Uncategorized | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: invalid
Keywords: ORM get_or_create | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Laurent Lyaudet):
Hello Natalia,
Your solution is suboptimal to say the least and denotes a lack of thought
on what happens under the hood.
I was somehow prepared for such an answer that proves that BS reigns
suprems in most FOSS.
Here are the problems :
- First: adding a select_related is as cumbersome to write than the line:
{{{
my_object.related_object = related_object
}}}
in my first ticket description :
{{{
my_object = MyModel.objects.get_or_create(related_object=related_object)
my_object.related_object = related_object
}}}
- Second: adding a select_related is worse than the line above when it
comes to the extra computation that you add both in Python and in the
DBMS.
This join is useless, it is superfluous work for the machine, bad
programming.
- Third and most important: for the consistency of your data, with
select_related you now have 2 objects of type ContentType.
If you update one of these 2 objects in Python, the other is not updated
unless, you save the first and refresh from db the second.
This is plain bad programming.
- Fourth: setting arrogantly * status: new => closed * resolution: =>
invalid is a full proof of how BS this kind of answer is.
Replying to [comment:3 Natalia Bidart]:
> Hello Laurent, thank you for your ticket.
>
> As far as I understand your report, the key to your issue is about using
`select_related` when getting the `Permission` you need. Specifically, see
how if I create a permission using the content_type's PK, the resulting
object does not have the ContentType instance fetched by default:
>
> {{{#!python
> >>> permission, created = Permission.objects.get_or_create(name="Test",
content_type_id=content_type.pk, codename="OtherTest")
> >>> print("\n\n".join(i["sql"] for i in connection.queries))
> SELECT "auth_permission"."id", "auth_permission"."name",
"auth_permission"."content_type_id", "auth_permission"."codename" FROM
"auth_permission" WHERE ("auth_permission"."codename" = 'OtherTest' AND
"auth_permission"."content_type_id" = 2 AND "auth_permission"."name" =
'Test') LIMIT 21
>
> BEGIN
>
> INSERT INTO "auth_permission" ("name", "content_type_id", "codename")
VALUES ('Test', 2, 'OtherTest') RETURNING "auth_permission"."id"
>
> COMMIT
>
> >>> permission.content_type
> <ContentType: Authentication and Authorization | permission>
> >>> print("\n\n".join(i["sql"] for i in connection.queries))
> SELECT "auth_permission"."id", "auth_permission"."name",
"auth_permission"."content_type_id", "auth_permission"."codename" FROM
"auth_permission" WHERE ("auth_permission"."codename" = 'OtherTest' AND
"auth_permission"."content_type_id" = 2 AND "auth_permission"."name" =
'Test') LIMIT 21
>
> BEGIN
>
> INSERT INTO "auth_permission" ("name", "content_type_id", "codename")
VALUES ('Test', 2, 'OtherTest') RETURNING "auth_permission"."id"
>
> COMMIT
>
> SELECT "django_content_type"."id", "django_content_type"."app_label",
"django_content_type"."model" FROM "django_content_type" WHERE
"django_content_type"."id" = 2 LIMIT 21
> }}}
>
> You can see the extra query at the end when the `content_type` attribute
is accessed. Similarly, you can avoid the "extra" query when the object
exists by doing:
>
> {{{#!python
> >>> permission, created =
Permission.objects.select_related("content_type").get_or_create(name="Test",
content_type_id=content_type.pk, codename="OtherTest")
> >>> print("\n\n".join(i["sql"] for i in connection.queries))
> SELECT "auth_permission"."id", "auth_permission"."name",
"auth_permission"."content_type_id", "auth_permission"."codename",
"django_content_type"."id", "django_content_type"."app_label",
"django_content_type"."model" FROM "auth_permission" INNER JOIN
"django_content_type" ON ("auth_permission"."content_type_id" =
"django_content_type"."id") WHERE ("auth_permission"."codename" =
'OtherTest' AND "auth_permission"."content_type_id" = 2 AND
"auth_permission"."name" = 'Test') LIMIT 21
>
> >>> permission.content_type
> <ContentType: Authentication and Authorization | permission>
> >>> print("\n\n".join(i["sql"] for i in connection.queries))
> SELECT "auth_permission"."id", "auth_permission"."name",
"auth_permission"."content_type_id", "auth_permission"."codename",
"django_content_type"."id", "django_content_type"."app_label",
"django_content_type"."model" FROM "auth_permission" INNER JOIN
"django_content_type" ON ("auth_permission"."content_type_id" =
"django_content_type"."id") WHERE ("auth_permission"."codename" =
'OtherTest' AND "auth_permission"."content_type_id" = 2 AND
"auth_permission"."name" = 'Test') LIMIT 21
> }}}
--
Ticket URL: <https://code.djangoproject.com/ticket/34884#comment:4>
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/0107018af25ffb84-c3574612-4912-411b-bd0c-1c7d1cc19e75-000000%40eu-central-1.amazonses.com.