Django 3 - Model.save() 为主键提供默认值时

Django 3 - Model.save() when providing a default for the primary key

我正在努力将我的项目从 Django 2 升级到 Django 3,我已经阅读了他们的 release notes of Django 3 and there is a point,我真的不明白它会对我当前的项目产生什么影响。他们在这里说:

据我了解,如果我们尝试调用 Model.save(),如果模型是现有记录,它总是会创建一个新记录而不是更新。例如:

car = Car.objects.first()
car.name = 'Honda'
car.save() # does it INSERT or UPDATE? I suspect it is an "INSERT" statement as their explanation and "UPDATE" statement in Django 2.

我做了一个实验,它仍然与 Django 2 的行为相同,不知道它们是什么意思。

In [5]: u = User.objects.first()                                                                                                                                                                          
(0.001) SELECT "accounts_user"."id", "accounts_user"."password", "accounts_user"."last_login", "accounts_user"."is_superuser", "accounts_user"."username", "accounts_user"."first_name", "accounts_user"."last_name", "accounts_user"."is_staff", "accounts_user"."is_active", "accounts_user"."date_joined", "accounts_user"."email", "accounts_user"."avatar", "accounts_user"."last_location"::bytea, "accounts_user"."uuid", "accounts_user"."country", "accounts_user"."city", "accounts_user"."phone" FROM "accounts_user" ORDER BY "accounts_user"."id" ASC LIMIT 1; args=()

In [6]: u.save()                                                                                                                                                                                          
(0.006) UPDATE "accounts_user" SET "password" = 'pbkdf2_sha256_sha5120000$FbFcNuPMrOZ6$GwIftEo+7+OpsORwn99lycye46aJn/aJNAtc50N478Y=', "last_login" = NULL, "is_superuser" = false, "username" = 'email0@mail.com', "first_name" = 'Noah', "last_name" = 'Spencer', "is_staff" = false, "is_active" = true, "date_joined" = '2020-05-12T07:06:20.605650+00:00'::timestamptz, "email" = 'email0@mail.com', "avatar" = 'account/user_avatar/example_HseJquC.jpg', "last_location" = NULL, "uuid" = 'f6992866-e476-409e-9f1b-098afadce5b7'::uuid, "country" = NULL, "city" = NULL, "phone" = NULL WHERE "accounts_user"."id" = 1; args=('pbkdf2_sha256_sha5120000$FbFcNuPMrOZ6$GwIftEo+7+OpsORwn99lycye46aJn/aJNAtc50N478Y=', False, 'email0@mail.com', 'Noah', 'Spencer', False, True, datetime.datetime(2020, 5, 12, 7, 6, 20, 605650, tzinfo=<UTC>), 'email0@mail.com', 'account/user_avatar/example_HseJquC.jpg', UUID('f6992866-e476-409e-9f1b-098afadce5b7'), 1)

更新:

In [38]: u1 = User.objects.first()                                                                                                                                                                        
(0.000) SELECT "accounts_user"."id", "accounts_user"."password", "accounts_user"."last_login", "accounts_user"."is_superuser", "accounts_user"."username", "accounts_user"."first_name", "accounts_user"."last_name", "accounts_user"."is_staff", "accounts_user"."is_active", "accounts_user"."date_joined", "accounts_user"."email", "accounts_user"."avatar", "accounts_user"."last_location"::bytea, "accounts_user"."uuid", "accounts_user"."country", "accounts_user"."city", "accounts_user"."phone" FROM "accounts_user" ORDER BY "accounts_user"."id" ASC LIMIT 1; args=()

In [39]: u1.pk                                                                                                                                                                                            
Out[39]: 1

In [40]: u2 = User(pk=1)                                                                                                                                                                                  

In [41]: u2.email = 'email@email.com'                                                                                                                                                                     

In [42]: u2.save()                                                                                                                                                                                        
(0.006) UPDATE "accounts_user" SET "password" = '', "last_login" = NULL, "is_superuser" = false, "username" = 'email@email.com', "first_name" = '', "last_name" = '', "is_staff" = false, "is_active" = true, "date_joined" = '2020-05-13T01:20:47.718449+00:00'::timestamptz, "email" = 'email@email.com', "avatar" = '', "last_location" = NULL, "uuid" = '89ba0924-03a7-44d2-bc6d-5fd2dcb0de0b'::uuid, "country" = NULL, "city" = NULL, "phone" = NULL WHERE "accounts_user"."id" = 1; args=('', False, 'email@email.com', '', '', False, True, datetime.datetime(2020, 5, 13, 1, 20, 47, 718449, tzinfo=<UTC>), 'email@email.com', '', UUID('89ba0924-03a7-44d2-bc6d-5fd2dcb0de0b'), 1)

一定要仔细阅读

no longer attempts to find a row when saving a new Model instance and a default value for the primary key is provided,

因此,如果您要使用数据库中已有的id 创建新对象,它现在会 fail 而不是具有类似于 update_or_create 的行为,因为它现在将执行 INSERT 语句而不是 UPDATE

考虑这个例子。假设我们有一个简单的模型

CONSTANT = 10


def foo_pk_default():
    return CONSTANT


class Foo(models.Model):
    id = models.IntegerField(primary_key=True, <b>default=foo_pk_default</b>)
    name = models.CharField(max_length=10)

我在这个例子中所做的主要事情是,我确实为主键设置了一个默认的可调用函数。另外,为了演示,我只从函数返回了一个值。

## Django 2.2
In [5]: foo_instance_1 = Foo(name='foo_name_1')

In [6]: foo_instance_1.save()

In [7]: print(foo_instance_1.__dict__)
{'_state': , 'id': 10, 'name': 'foo_name_1'}

In [8]: foo_instance_2 = Foo(name='foo_name_2')

In [9]: foo_instance_2.save()

In [10]: print(foo_instance_2.__dict__)
{'_state': , 'id': 10, 'name': 'foo_name_2'}

## Django 3.X
In [6]: foo_instance_1 = Foo(name='foo_name_1')

In [7]: foo_instance_1.save()

In [8]: print(foo_instance_1.__dict__)
{'_state': , 'id': 10, 'name': 'foo_name_1'}

In [9]: foo_instance_2 = Foo(name='foo_name_2')

In [10]: foo_instance_2.save()
# Raised "IntegrityError: UNIQUE constraint failed: music_foo.id"

结论

Django<3.0中,Model.save()会做一个update or insert operation 如果有与模型实例关联的 PK 值,而在 Django>=3.0 中,只执行 插入操作因此出现UNIQUE constraint failed异常。

此更改对当前项目的影响

因为此 Django 更改仅适用于创建 新实例 并且我们通常不会为主键。

简而言之,除非您在创建模型实例期间提供默认值,否则此更改不会造成任何问题。