Django - Menimpa metode Model.create ()?

89

Dokumen Django hanya mencantumkan contoh untuk menimpa save()dan delete(). Namun, saya ingin mendefinisikan beberapa pemrosesan tambahan untuk model saya hanya saat model dibuat . Bagi siapa pun yang akrab dengan Rails, itu akan sama dengan membuat :before_createfilter. Apakah ini mungkin?

ground5hark
sumber

Jawaban:

164

Penimpaan __init__()akan menyebabkan kode dieksekusi setiap kali representasi objek python dibuat. Saya tidak tahu rel, tetapi :before_createdfilter terdengar bagi saya seperti kode itu untuk dieksekusi ketika objek dibuat dalam database. Jika Anda ingin mengeksekusi kode saat objek baru dibuat di database, Anda harus menimpanya save(), memeriksa apakah objek tersebut memiliki pkatribut atau tidak. Kode tersebut akan terlihat seperti ini:

def save(self, *args, **kwargs):
    if not self.pk:
        # This code only happens if the objects is
        # not in the database yet. Otherwise it would
        # have pk
    super(MyModel, self).save(*args, **kwargs)
Zach
sumber
7
Saya sebenarnya telah menemukan solusi menggunakan sinyal: docs.djangoproject.com/en/dev/topics/signals (sinyal pre_save, khususnya). Namun, ini tampaknya merupakan solusi yang jauh lebih pragmatis. Terima kasih banyak.
ground5hark
4
Saya berasumsi bahwa maksud Anda mengesampingkan metode manajer create? Itu adalah solusi yang menarik, tetapi tidak akan berhasil jika objek dibuat menggunakan Object(**kwargs).save()atau variasi lain apa pun darinya .
Zach
4
Saya tidak berpikir itu hack. Itu salah satu solusi resmi.
les
6
Bukankah seharusnya begitu super(MyModel, self).save(*args, **kwargs)?
Mark Chackerian
1
Mungkin memeriksa self.pkbukanlah pilihan terbaik untuk memeriksa apakah objek tersebut baru dibuat atau hanya memperbarui. Kadang-kadang Anda memberikan id objek dalam waktu pembuatan (nilai yang dihasilkan non-database yang disesuaikan seperti KSUID), dan itu akan menyebabkan klausa ini tidak pernah dieksekusi ... Ada self._state.addingnilai untuk memastikan apakah itu menyimpan untuk pertama kali atau hanya memperbarui, yang mana membantu dalam kasus tersebut.
Shahinisme
22

Ini sudah tua, memiliki jawaban yang diterima yang berfungsi (Zach), dan yang lebih idiomatik juga (Michael Bylstra), tetapi karena ini masih hasil pertama di Google yang dilihat kebanyakan orang, saya pikir kita membutuhkan praktik terbaik modern-django gaya jawaban di sini :

from django.db.models.signals import post_save

class MyModel(models.Model):
    # ...
    @classmethod
    def post_create(cls, sender, instance, created, *args, **kwargs):
        if not created:
            return
        # ...what needs to happen on create

post_save.connect(MyModel.post_create, sender=MyModel)

Intinya begini:

  1. menggunakan sinyal (baca lebih lanjut di sini di dokumen resmi )
  2. menggunakan metode untuk namespacing yang bagus (jika masuk akal) ... dan saya menandainya sebagai @classmethodbukan @staticmethodkarena kemungkinan besar Anda akan perlu merujuk anggota kelas statis dalam kode

Bahkan lebih bersih jika inti Django memiliki post_createsinyal yang sebenarnya . (Imho jika Anda perlu meneruskan argumen boolean untuk mengubah perilaku suatu metode, itu harus menjadi 2 metode.)

NeuronQ
sumber
22

contoh cara membuat sinyal post_save (dari http://djangosnippets.org/snippets/500/ )

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

@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
    """Create a matching profile whenever a user object is created."""
    if created: 
        profile, new = UserProfile.objects.get_or_create(user=instance)

berikut adalah diskusi yang bijaksana tentang apakah yang terbaik untuk menggunakan sinyal atau metode penyimpanan kustom https://web.archive.org/web/20120815022107/http://www.martin-geber.com/thought/2007/10/29/ django-signal-vs-custom-save-method /

Menurut pendapat saya, menggunakan sinyal untuk tugas ini lebih kuat, lebih mudah dibaca tetapi lebih panjang.

Michael Bylstra
sumber
Ini adalah cara yang lebih disukai daripada mengotak-atik objek internal, namun, jika Anda membuat modifikasi pada model yang dimaksud, dan tidak hanya membuat model lain pada contoh di atas, jangan lupa untuk memanggilinstance.save() . Jadi dalam kasus ini, ada juga penalti kinerja karena akan ada satu kueri INSERT dan satu UPDATE ke database.
Mike Shultz
Tautan ke sinyal vs. metode penyimpanan khusus rusak.
Sander Vanden Hautte
18

Untuk menjawab pertanyaan secara harfiah, createmetode dalam manajer model adalah cara standar untuk membuat objek baru di Django. Untuk mengganti, lakukan sesuatu seperti

from django.db import models

class MyModelManager(models.Manager):
    def create(self, **obj_data):
        # Do some extra stuff here on the submitted data before saving...
        # For example...
        obj_data['my_field'] = my_computed_value(obj_data['my_other_field'])

        # Now call the super method which does the actual creation
        return super().create(**obj_data) # Python 3 syntax!!

class MyModel(models.model):
    # An example model
    my_field = models.CharField(max_length=250)
    my_other_field = models.CharField(max_length=250)

    objects = MyModelManager()

Dalam contoh ini, saya mengganti metode createmetode Manajer untuk melakukan beberapa pemrosesan tambahan sebelum instance benar-benar dibuat.

CATATAN: Kode seperti

my_new_instance = MyModel.objects.create(my_field='my_field value')

akan menjalankan createmetode yang dimodifikasi ini , tetapi kode suka

my_new_unsaved_instance = MyModel(my_field='my_field value')

tidak akan.

Mark Chackerian
sumber
3

Mengganti __init__()akan memungkinkan Anda untuk mengeksekusi kode saat model dibuat. Jangan lupa menelepon orang tua __init__().

Ignacio Vazquez-Abrams
sumber
Ah ya ini jawabannya. Tidak tahu bagaimana saya mengabaikan ini. Terima kasih Ignacio.
ground5hark
1

Jawaban yang disukai benar tetapi pengujian untuk mengetahui apakah objek sedang dibuat tidak berfungsi jika model Anda berasal dari UUIDModel. Bidang pk sudah memiliki nilai.

Dalam hal ini, Anda dapat melakukan ini:

already_created = MyModel.objects.filter(pk=self.pk).exists()

Julio Carrera
sumber