Django - Zastąpienie metody Model.create ()?

87

Dokumentacja Django zawiera tylko przykłady przesłaniania save()i delete(). Chciałbym jednak zdefiniować dodatkowe przetwarzanie dla moich modeli tylko wtedy, gdy są tworzone . Dla każdego, kto zna Railsy, ​​byłoby to równoznaczne z utworzeniem :before_createfiltru. czy to możliwe?

ground5hark
źródło

Odpowiedzi:

160

Zastępowanie __init__()spowodowałoby wykonanie kodu za każdym razem, gdy tworzona jest instancja reprezentacji obiektu w języku Python. Nie znam railsów, ale :before_createdwydaje mi się, że filtr jest kodem, który ma zostać wykonany, gdy obiekt jest tworzony w bazie danych. Jeśli chcesz wykonać kod, gdy nowy obiekt zostanie utworzony w bazie danych, powinieneś przesłonić save(), sprawdzając, czy obiekt ma pkatrybut, czy nie. Kod wyglądałby mniej więcej tak:

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
źródło
7
Właściwie znalazłem rozwiązanie za pomocą sygnałów: docs.djangoproject.com/en/dev/topics/signals (konkretnie sygnał pre_save). Wydaje się jednak, że jest to dużo bardziej pragmatyczne rozwiązanie. Wielkie dzięki.
ground5hark
4
Zakładam, że masz na myśli zastąpienie metody menedżera create? To ciekawe rozwiązanie, ale nie działałoby w przypadkach, gdy obiekt jest tworzony przy użyciu Object(**kwargs).save()lub jakiejkolwiek innej odmiany tego.
Zach
3
Nie sądzę, że to hack. To jedno z oficjalnych rozwiązań.
les
6
Nie powinno być super(MyModel, self).save(*args, **kwargs)?
Mark Chackerian
1
Być może sprawdzenie, self.pkczy nie jest najlepszym sposobem sprawdzenia, czy obiekt jest nowo tworzony, czy tylko aktualizowany. Czasami podajesz identyfikator obiektu w czasie tworzenia (dostosowaną wartość, która nie została wygenerowana przez bazę danych, np. KSUID) I spowoduje to, że ta klauzula nigdy nie zostanie wykonana ... Istnieje self._state.addingwartość, aby upewnić się, czy zapisuje się po raz pierwszy, czy tylko aktualizuje, co pomaga w takich przypadkach.
Shahinism
22

przykład tworzenia sygnału post_save (z 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)

tutaj jest przemyślana dyskusja na temat tego, czy najlepiej używać sygnałów, czy niestandardowych metod zapisywania https://web.archive.org/web/20120815022107/http://www.martin-geber.com/thought/2007/10/29/ django-signal-vs-custom-save-method /

Moim zdaniem użycie sygnałów do tego zadania jest solidniejsze, łatwiejsze do odczytania, ale dłuższe.

Michael Bylstra
źródło
Jest to preferowany sposób zamiast mieszania się z wewnętrznymi obiektami, jednak jeśli wprowadzasz modyfikacje do danego modelu, a nie tylko tworzysz inny w powyższym przykładzie, nie zapomnij zadzwonićinstance.save() . Tak więc w tym przypadku występuje również spadek wydajności, ponieważ do bazy danych zostanie wysłane jedno zapytanie INSERT i jedno zapytanie UPDATE.
Mike Shultz
Łącze między sygnałami a niestandardowymi metodami zapisu jest uszkodzone.
Sander Vanden Hautte
21

To jest stare, ma akceptowaną odpowiedź, która działa (Zach) i bardziej idiomatyczną (Michaela Bylstry), ale ponieważ jest to nadal pierwszy wynik w Google, który większość ludzi widzi, myślę, że potrzebujemy więcej najlepszych praktyk modern-django stylowa odpowiedź tutaj :

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)

Chodzi o to:

  1. używaj sygnałów (przeczytaj więcej tutaj w oficjalnej dokumentacji )
  2. użyj metody do ładnej przestrzeni nazw (jeśli ma to sens) ... i oznaczyłem ją jako @classmethodzamiast, @staticmethodponieważ najprawdopodobniej będziesz musiał odwoływać się do statycznych członków klasy w kodzie

Jeszcze czystsze byłoby, gdyby rdzeń Django miał rzeczywisty post_createsygnał. (Imho, jeśli musisz przekazać argument logiczny, aby zmienić zachowanie metody, powinny to być 2 metody).

NeuronQ
źródło
15

Aby odpowiedzieć na to pytanie dosłownie, createmetoda w menadżerze modelu jest standardowym sposobem tworzenia nowych obiektów w Django. Aby zastąpić, zrób coś takiego

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()

W tym przykładzie nadpisuję metodę createmetody Managera, aby wykonać dodatkowe przetwarzanie przed faktycznym utworzeniem instancji.

UWAGA: Kod jak

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

wykona tę zmodyfikowaną createmetodę, ale kod podobny do

my_new_unsaved_instance = MyModel(my_field='my_field value')

nie będzie.

Mark Chackerian
źródło
3

Zastępowanie __init__()umożliwi wykonanie kodu podczas tworzenia wystąpienia modelu. Nie zapomnij zadzwonić do rodziców __init__().

Ignacio Vazquez-Abrams
źródło
Ach tak, to była odpowiedź. Nie wiem, jak to przeoczyłem. Dzięki Ignacio.
ground5hark
1

Preferowana odpowiedź jest poprawna, ale test sprawdzający, czy obiekt jest tworzony, nie działa, jeśli model pochodzi z UUIDModel. Pole pk będzie już miało wartość.

W takim przypadku możesz to zrobić:

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

Julio Carrera
źródło