Dans une méthode save () personnalisée de modèle django, comment identifier un nouvel objet?

172

Je souhaite déclencher une action spéciale dans la méthode save () d'un objet de modèle Django lorsque j'enregistre un nouvel enregistrement (sans mettre à jour un enregistrement existant).

La vérification de (self.id! = None) est-elle nécessaire et suffisante pour garantir que l'enregistrement automatique est nouveau et n'est pas mis à jour? Y a-t-il des cas particuliers que cela pourrait négliger?

MikeN
la source
Veuillez sélectionner stackoverflow.com/a/35647389/8893667 comme réponse correcte. La réponse ne fonctionne pas dans de nombreux cas comme unUUIDField pk
Kotlinboy

Réponses:

204

Mise à jour: avec la clarification qui self._staten'est pas une variable d'instance privée, mais nommée de cette façon pour éviter les conflits, la vérification self._state.addingest désormais le moyen préférable de vérifier.


self.pk is None:

renvoie True dans un nouvel objet Model, sauf si l'objet a UUIDFieldpour primary_key.

Le cas du coin dont vous pourriez avoir à vous soucier est de savoir s'il existe des contraintes d'unicité sur des champs autres que l'identifiant (par exemple, des index uniques secondaires sur d'autres champs). Dans ce cas, vous pourriez toujours avoir un nouvel enregistrement en main, mais être incapable de le sauvegarder.

Dave W. Smith
la source
20
Vous devriez utiliser is notplutôt que !=lors de la vérification de l'identité avec l' Noneobjet
Ben James
3
Tous les modèles n'ont pas d'attribut id, c'est-à-dire un modèle en étendant un autre via a models.OneToOneField(OtherModel, primary_key=True). Je pense que vous devez utiliserself.pk
AJP
4
Cela PEUT NE PAS FONCTIONNER dans certains cas. Veuillez vérifier cette réponse: stackoverflow.com/a/940928/145349
fjsj
5
Ce n'est pas la bonne réponse. Si vous utilisez a UUIDFieldcomme clé primaire, ce self.pkn'est jamais None.
Daniel van Flymen
1
Note latérale: Cette réponse est antérieure à UUIDField.
Dave
190

Autre moyen de vérifier que self.pknous pouvons vérifier self._statele modèle

self._state.adding is True créer

self._state.adding is False mise à jour

Je l'ai eu sur cette page

SaintTail
la source
12
C'est la seule méthode correcte lors de l'utilisation d'un champ de clé primaire personnalisé.
webtweakers
9
Pas sûr de tous les détails de la façon dont self._state.addingfonctionne, mais juste avertissement qu'il semble toujours égal Falsesi vous le vérifiez après avoir appelé super(TheModel, self).save(*args, **kwargs): github.com/django/django/blob/stable/1.10.x/django/db/models/ …
agilgur5
1
C'est la bonne manière et doit être votée / définie comme la bonne réponse.
flungo
7
@guival: _staten'est pas privé; comme _meta, il est précédé d'un trait de soulignement pour éviter toute confusion avec les noms de champ. (Remarquez comment il est utilisé dans la documentation liée.)
Ry-
2
C'est la meilleur façon. J'ai utilisé is_new = self._state.adding, puis super(MyModel, self).save(*args, **kwargs)et puisif is_new: my_custom_logic()
kotrfa
45

La vérification self.idsuppose qu'il ids'agit de la clé primaire du modèle. Une manière plus générique serait d'utiliser le raccourci pk .

is_new = self.pk is None

Gerry
la source
15
Astuce de pro: mettez ceci AVANT le super(...).save().
sbdchd
39

La vérification self.pk == Nonen'est pas suffisante pour déterminer si l'objet va être inséré ou mis à jour dans la base de données.

Django O / RM propose un hack particulièrement méchant qui consiste essentiellement à vérifier s'il y a quelque chose à la position PK et si c'est le cas, faites une MISE À JOUR, sinon faites un INSERT (cela est optimisé en INSERT si le PK est Aucun).

La raison pour laquelle il doit faire cela est que vous êtes autorisé à définir le PK lorsqu'un objet est créé. Bien que cela ne soit pas courant lorsque vous avez une colonne de séquence pour la clé primaire, cela ne s'applique pas aux autres types de champ de clé primaire.

Si vous voulez vraiment savoir, vous devez faire ce que fait l'O / RM et regarder dans la base de données.

Bien sûr, vous avez un cas spécifique dans votre code et pour cela il est fort probable que cela self.pk == Nonevous dise tout ce que vous devez savoir, mais ce n'est pas une solution générale.

KayEss
la source
Bon point! Je peux m'en tirer dans mon application (en vérifiant la clé primaire None) car je n'ai jamais défini le pk pour les nouveaux objets. Mais ce ne serait certainement pas une bonne vérification pour un plugin réutilisable ou une partie du framework.
MikeN
1
Cela est particulièrement vrai lorsque vous attribuez vous-même la clé primaire et via la base de données. Dans ce cas, la chose la plus sûre à faire est de faire un voyage vers la base de données.
Constantine M
1
Même si le code de votre application ne spécifie pas explicitement pks, les fixtures de vos cas de test pourraient le faire. Cependant, comme ils sont généralement chargés avant les tests, cela ne pose pas de problème.
Risadinha
1
Cela est particulièrement vrai dans le cas de l'utilisation de a UUIDFieldcomme clé primaire: la clé n'est pas remplie au niveau de la base de données, elle l' self.pkest toujours True.
Daniel van Flymen
10

Vous pouvez simplement vous connecter au signal post_save qui envoie un kwargs "créé", si vrai, votre objet a été inséré.

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

JF Simon
la source
8
Cela peut potentiellement causer des conditions de course s'il y a beaucoup de charge. C'est parce que le signal post_save est envoyé lors de la sauvegarde, mais avant que la transaction ne soit validée. Cela peut être problématique et rendre les choses très difficiles à déboguer.
Abel Mohler
Je ne sais pas si les choses ont changé (à partir des anciennes versions), mais mes gestionnaires de signaux sont appelés dans la même transaction, donc un échec n'importe où annule toute la transaction. J'utilise ATOMIC_REQUESTS, donc je ne suis pas vraiment sûr de la valeur par défaut.
Tim Tisdall
7

Vérifiez self.idet le force_insertdrapeau.

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

Ceci est pratique car votre objet nouvellement créé (soi) a de la pkvaleur

Kwaw Annor
la source
5

Je suis très en retard à cette conversation, mais j'ai rencontré un problème avec le self.pk étant rempli quand il a une valeur par défaut qui lui est associée.

La façon dont j'ai contourné cela consiste à ajouter un champ date_created au modèle

date_created = models.DateTimeField(auto_now_add=True)

De là, vous pouvez aller

created = self.date_created is None

Jordan
la source
4

Pour une solution qui fonctionne également même lorsque vous avez une UUIDFieldclé primaire (ce qui, comme d'autres l'ont noté, ne l'est pas Nonesi vous remplacez simplement save), vous pouvez vous connecter au signal post_save de Django . Ajoutez ceci à votre models.py :

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

Ce rappel bloquera la saveméthode, vous pouvez donc faire des choses comme déclencher des notifications ou mettre à jour le modèle avant que votre réponse ne soit renvoyée sur le fil, que vous utilisiez des formulaires ou le framework Django REST pour les appels AJAX. Bien sûr, utilisez de manière responsable et déchargez les tâches lourdes dans une file d'attente au lieu de faire attendre vos utilisateurs :)

metakermit
la source
3

utilisez plutôt pk au lieu de id :

if not self.pk:
  do_something()
yedpodtrzitko
la source
1

C'est la manière courante de le faire.

l'identifiant sera donné lors de la première sauvegarde dans la base de données

vikingosegundo
la source
0

Cela fonctionnerait-il pour tous les scénarios ci-dessus?

if self.pk is not None and <ModelName>.objects.filter(pk=self.pk).exists():
...
Sachin
la source
cela provoquerait un hit de base de données supplémentaire.
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
la source
En utilisant nettoyé_data.get (), j'ai pu déterminer si j'avais une instance, j'ai également eu un CharField où nul et vide où vrai. Cela sera mis à jour à chaque mise à jour en fonction de l'utilisateur connecté
Swelan Auguste
-3

Pour savoir si vous mettez à jour ou insérez l'objet (données), utilisez self.instance.fieldnamedans votre formulaire. Définissez une fonction de nettoyage dans votre formulaire et vérifiez si l'entrée de valeur actuelle est la même que la précédente, sinon vous la mettez à jour.

self.instanceet self.instance.fieldnamecomparez avec la nouvelle valeur

ha22109
la source