Django - Remplacer la méthode Model.create ()?

87

La documentation Django ne répertorie que des exemples de remplacement save()et delete(). Cependant, j'aimerais définir un traitement supplémentaire pour mes modèles uniquement lorsqu'ils sont créés . Pour quiconque connaît Rails, cela équivaudrait à créer un :before_createfiltre. Est-ce possible?

sol5hark
la source

Réponses:

160

Le remplacement __init__()entraînerait l'exécution du code chaque fois que la représentation python de l'objet est instanciée. Je ne connais pas les rails, mais un :before_createdfiltre me semble être du code à exécuter lorsque l'objet est créé dans la base de données. Si vous souhaitez exécuter du code lorsqu'un nouvel objet est créé dans la base de données, vous devez le remplacer save(), en vérifiant si l'objet a un pkattribut ou non. Le code ressemblerait à ceci:

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
la source
7
J'ai en fait trouvé une solution en utilisant des signaux: docs.djangoproject.com/en/dev/topics/signals (le signal pre_save, en particulier). Cependant, cela semble être une solution beaucoup plus pragmatique. Merci beaucoup.
ground5hark
4
Je suppose que vous voulez dire remplacer la méthode du gestionnaire create? C'est une solution intéressante, mais cela ne fonctionnerait pas dans les cas où l'objet est créé en utilisant Object(**kwargs).save()ou toute autre variation à ce sujet.
Zach
3
Je ne pense pas que ce soit un hack. C'est l'une des solutions officielles.
les
6
Ne devrait-il pas l'être super(MyModel, self).save(*args, **kwargs)?
Mark Chackerian
1
La vérification self.pkn'est peut-être pas la meilleure option pour vérifier si l'objet vient d'être créé ou s'il est simplement mis à jour. Parfois, vous fournissez un identifiant d'objet au moment de la création (une valeur personnalisée non générée par la base de données, comme KSUID), et cette clause ne s'exécutera jamais ... Il y a une self._state.addingvaleur pour s'assurer qu'elle est enregistrée pour la première fois ou simplement mise à jour, ce qui aide dans ces cas.
Shahinism
22

un exemple de création d'un signal post_save (à partir de 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)

voici une discussion approfondie pour savoir s'il est préférable d'utiliser des signaux ou des méthodes de sauvegarde personnalisées https://web.archive.org/web/20120815022107/http://www.martin-geber.com/thought/2007/10/29/ django-signaux-vs-méthode-de-sauvegarde-personnalisée /

À mon avis, l'utilisation de signaux pour cette tâche est plus robuste, plus facile à lire mais plus longue.

Michael Bylstra
la source
C'est la méthode préférée au lieu de jouer avec les éléments internes des objets, cependant, si vous apportez des modifications au modèle en question, et pas simplement en créer un autre dans l'exemple ci-dessus, n'oubliez pas d'appelerinstance.save() . Donc, dans ce cas, il y a également une pénalité de performances car il y aura une requête INSERT et une requête UPDATE dans la base de données.
Mike Shultz du
Le lien entre les signaux et les méthodes de sauvegarde personnalisées est rompu.
Sander Vanden Hautte
21

C'est vieux, a une réponse acceptée qui fonctionne (celle de Zach), et une plus idiomatique aussi (celle de Michael Bylstra), mais comme c'est toujours le premier résultat sur Google que la plupart des gens voient, je pense que nous avons besoin d'un plus de bonnes pratiques modernes-django réponse de style ici :

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)

Le point est le suivant:

  1. utiliser des signaux (en savoir plus ici dans la documentation officielle )
  2. utilisez une méthode pour un bel espace de noms (si cela a du sens) ... et je l'ai marqué comme @classmethodau lieu de @staticmethodparce que vous devrez probablement faire référence à des membres de classe statiques dans le code

Encore plus propre serait si le noyau Django avait un post_createsignal réel . (Imho si vous devez passer un argument booléen pour changer le comportement d'une méthode, cela devrait être 2 méthodes.)

NeuronQ
la source
15

Pour répondre littéralement à la question, la createméthode dans le gestionnaire d'un modèle est un moyen standard de créer de nouveaux objets dans Django. Pour remplacer, faites quelque chose comme

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

Dans cet exemple, je remplace la méthode de la méthode du gestionnaire createpour effectuer un traitement supplémentaire avant que l'instance ne soit réellement créée.

REMARQUE: Code comme

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

exécutera cette createméthode modifiée , mais un code comme

my_new_unsaved_instance = MyModel(my_field='my_field value')

Ne fera pas.

Mark Chackerian
la source
3

Le remplacement __init__()vous permettra d'exécuter du code lorsque le modèle est instancié. N'oubliez pas d'appeler les parents __init__().

Ignacio Vazquez-Abrams
la source
Ah oui c'était la réponse. Je ne sais pas comment j'ai négligé cela. Merci Ignacio.
ground5hark
1

La réponse préférée est correcte, mais le test pour savoir si l'objet est en cours de création ne fonctionne pas si votre modèle dérive de UUIDModel. Le champ pk aura déjà une valeur.

Dans ce cas, vous pouvez faire ceci:

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

Julio Carrera
la source