Dalam metode django model custom save (), bagaimana seharusnya Anda mengidentifikasi objek baru?

172

Saya ingin memicu tindakan khusus dalam metode save () dari objek model Django ketika saya menyimpan catatan baru (tidak memperbarui catatan yang ada.)

Apakah pemeriksaan untuk (self.id! = Tidak ada) diperlukan dan memadai untuk menjamin bahwa catatan diri itu baru dan tidak diperbarui? Adakah kasus khusus yang mungkin diabaikan?

MikeN
sumber
Silakan pilih stackoverflow.com/a/35647389/8893667 sebagai jawaban yang benar. Jawabannya tidak bekerja dalam banyak kasus sepertiUUIDField pk
Kotlinboy

Jawaban:

204

Diperbarui: Dengan klarifikasi yang self._statebukan variabel instance pribadi, tetapi dinamai demikian untuk menghindari konflik, pengecekan self._state.addingsekarang merupakan cara yang lebih baik untuk memeriksa.


self.pk is None:

mengembalikan True dalam objek Model baru, kecuali objek tersebut memiliki UUIDFieldsebagai primary_key.

Kasus sudut yang mungkin harus Anda khawatirkan adalah apakah ada batasan keunikan pada bidang selain id (misalnya, indeks unik sekunder di bidang lain). Dalam hal ini, Anda masih dapat memiliki catatan baru di tangan, tetapi tidak dapat menyimpannya.

Dave W. Smith
sumber
20
Anda harus menggunakan is notdaripada !=ketika memeriksa identitas dengan Noneobjek
Ben James
3
Tidak semua model memiliki atribut id, yaitu model yang memperpanjang yang lain melalui a models.OneToOneField(OtherModel, primary_key=True). Saya pikir Anda perlu menggunakanself.pk
AJP
4
Ini MUNGKIN TIDAK BEKERJA dalam beberapa kasus. Silakan periksa jawaban ini: stackoverflow.com/a/940928/145349
fjsj
5
Ini bukan jawaban yang benar. Jika menggunakan UUIDFieldkunci utama, self.pktidak pernah None.
Daniel van Flymen
1
Catatan tambahan: Jawaban ini sebelumnya adalah UUIDField.
Dave W. Smith
190

Cara alternatif untuk memeriksa self.pkkita dapat memeriksa self._statemodel

self._state.adding is True menciptakan

self._state.adding is False memperbarui

Saya mendapatkannya dari halaman ini

SaintTail
sumber
12
Ini adalah satu-satunya cara yang benar ketika menggunakan bidang kunci utama khusus.
webtweakers
9
Tidak yakin tentang semua detail cara self._state.addingkerjanya, tetapi peringatan yang adil bahwa tampaknya selalu sama Falsejika Anda memeriksanya setelah menelepon super(TheModel, self).save(*args, **kwargs): github.com/django/django/blob/stable/1.10.x/django/db/models/ ...
agilgur5
1
Ini adalah cara yang benar dan harus dibesarkan / ditetapkan sebagai jawaban yang benar.
flungo
7
@guival: _statetidak pribadi; seperti _meta, diawali dengan garis bawah untuk menghindari kebingungan dengan nama bidang. (Perhatikan bagaimana ini digunakan dalam dokumentasi yang ditautkan.)
Ry-
2
Ini cara terbaik. Saya menggunakan is_new = self._state.adding, kemudian super(MyModel, self).save(*args, **kwargs)dan kemudianif is_new: my_custom_logic()
kotrfa
45

Memeriksa self.idmengasumsikan bahwa itu idadalah kunci utama untuk model. Cara yang lebih umum adalah dengan menggunakan pintasan pk .

is_new = self.pk is None

Gerry
sumber
15
Pro Tip: menempatkan ini SEBELUM tersebut super(...).save().
sbdchd
39

Cek untuk self.pk == Noneini tidak cukup untuk menentukan apakah objek tersebut akan dimasukkan atau diperbarui dalam database.

Django O / RM menampilkan peretasan jahat yang pada dasarnya untuk memeriksa apakah ada sesuatu pada posisi PK dan jika demikian lakukan PEMBARUAN, jika tidak lakukan INSERT (ini akan dioptimalkan menjadi INSERT jika PK Tidak Ada).

Alasan mengapa harus melakukan ini adalah karena Anda diizinkan untuk mengatur PK ketika suatu objek dibuat. Meskipun tidak umum di mana Anda memiliki kolom urutan untuk kunci utama, ini tidak berlaku untuk jenis bidang kunci utama lainnya.

Jika Anda benar-benar ingin tahu, Anda harus melakukan apa yang dilakukan O / RM dan mencari di database.

Tentu saja Anda memiliki kasus khusus dalam kode Anda dan untuk itu sangat mungkin self.pk == Nonememberi tahu Anda semua yang perlu Anda ketahui, tetapi itu bukan solusi umum.

KayEss
sumber
Poin bagus! Saya bisa lolos dengan ini di aplikasi saya (memeriksa Tidak ada kunci utama) karena saya tidak pernah mengatur pk untuk objek baru. Tapi ini pasti bukan pemeriksaan yang baik untuk plugin yang dapat digunakan kembali atau bagian dari kerangka kerja.
MikeN
1
Ini khususnya benar ketika Anda menetapkan kunci utama sendiri dan dan melalui database. Dalam hal itu hal yang paling pasti untuk dilakukan adalah melakukan perjalanan ke db.
Constantine M
1
Bahkan jika kode aplikasi Anda tidak menentukan pks secara eksplisit perlengkapan untuk kasus pengujian Anda mungkin. Padahal, karena mereka biasanya dimuat sebelum tes mungkin tidak menjadi masalah.
Risadinha
1
Ini terutama benar dalam kasus menggunakan UUIDFieldsebagai Kunci Utama: kunci tidak diisi pada tingkat DB, jadi self.pkselalu True.
Daniel van Flymen
10

Anda bisa saja terhubung ke sinyal post_save yang mengirimkan kwarg "dibuat", jika benar, objek Anda telah dimasukkan.

http://docs.djangoproject.com/en/stable/ref/signals/#post-save

JF Simon
sumber
8
Itu berpotensi menyebabkan kondisi balapan jika ada banyak beban. Itu karena sinyal post_save dikirim pada save, tetapi sebelum transaksi dilakukan. Ini bisa menjadi masalah dan dapat membuat hal-hal sangat sulit untuk di-debug.
Abel Mohler
Saya tidak yakin apakah semuanya berubah (dari versi yang lebih lama), tetapi penangan sinyal saya dipanggil dalam transaksi yang sama sehingga kegagalan di mana pun mengembalikan seluruh transaksi. Saya menggunakan ATOMIC_REQUESTS, jadi saya tidak begitu yakin tentang default.
Tim Tisdall
7

Periksa self.iddan force_insertbenderanya.

if not self.pk or kwargs.get('force_insert', False):
    self.created = True

# call save method.
super(self.__class__, self).save(*args, **kwargs)

#Do all your post save actions in the if block.
if getattr(self, 'created', False):
    # So something
    # Do something else

Ini berguna karena objek (diri) Anda yang baru dibuat memiliki pknilai

Kwaw Annor
sumber
5

Saya sangat terlambat untuk percakapan ini, tetapi saya mengalami masalah dengan self.pk yang sedang diisi ketika memiliki nilai default yang terkait dengannya.

Cara saya menyiasatinya adalah menambahkan bidang date_created ke model

date_created = models.DateTimeField(auto_now_add=True)

Dari sini kamu bisa pergi

created = self.date_created is None

Yordania
sumber
4

Untuk solusi yang juga berfungsi bahkan ketika Anda memiliki UUIDFieldsebagai kunci utama (yang seperti yang dicatat orang lain tidak Nonejika Anda hanya menimpanya save), Anda dapat mencolokkan sinyal post_save Django . Tambahkan ini ke models.py Anda :

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=MyModel)
def mymodel_saved(sender, instance, created, **kwargs):
    if created:
        # do extra work on your instance, e.g.
        # instance.generate_avatar()
        # instance.send_email_notification()
        pass

Callback ini akan memblokir savemetode ini, sehingga Anda dapat melakukan hal-hal seperti memicu notifikasi atau memperbarui model lebih lanjut sebelum respons Anda dikirim kembali melalui kabel, apakah Anda menggunakan formulir atau kerangka Django REST untuk panggilan AJAX. Tentu saja, gunakan secara bertanggung jawab dan bongkar tugas berat ke antrian pekerjaan alih-alih membuat pengguna Anda menunggu :)

metakermit
sumber
3

alih-alih gunakan pk alih-alih id :

if not self.pk:
  do_something()
yedpodtrzitko
sumber
1

Ini adalah cara umum untuk melakukannya.

id akan diberikan saat disimpan pertama kali ke db

vikingosegundo
sumber
0

Apakah ini akan berhasil untuk semua skenario di atas?

if self.pk is not None and <ModelName>.objects.filter(pk=self.pk).exists():
...
Sachin
sumber
ini akan menyebabkan hit basis data tambahan.
David Schumann
0
> def save_model(self, request, obj, form, change):
>         if form.instance._state.adding:
>             form.instance.author = request.user
>             super().save_model(request, obj, form, change)
>         else:
>             obj.updated_by = request.user.username
> 
>             super().save_model(request, obj, form, change)
Swelan Auguste
sumber
Dengan menggunakan clean_data.get (), saya dapat menentukan apakah saya memiliki instance, saya juga punya CharField di mana null dan blank mana true. Ini akan diperbarui pada setiap pembaruan sesuai dengan pengguna yang masuk
Swelan Auguste
-3

Untuk mengetahui apakah Anda memperbarui atau memasukkan objek (data), gunakan self.instance.fieldnamedi formulir Anda. Tentukan fungsi bersih dalam formulir Anda dan periksa apakah entri nilai saat ini sama dengan sebelumnya, jika tidak maka Anda memperbaruinya.

self.instancedan self.instance.fieldnamebandingkan dengan nilai baru

ha22109
sumber