Comment cloner un objet d'instance de modèle Django et l'enregistrer dans la base de données?

261
Foo.objects.get(pk="foo")
<Foo: test>

Dans la base de données, je veux ajouter un autre objet qui est une copie de l'objet ci-dessus.

Supposons que ma table ait une ligne. Je veux insérer le premier objet de ligne dans une autre ligne avec une clé primaire différente. Comment puis je faire ça?

user426795
la source

Réponses:

438

Modifiez simplement la clé primaire de votre objet et exécutez save ().

obj = Foo.objects.get(pk=<some_existing_pk>)
obj.pk = None
obj.save()

Si vous souhaitez une clé générée automatiquement, définissez la nouvelle clé sur Aucune.

Plus d'informations sur UPDATE / INSERT ici .

Documents officiels sur la copie des instances de modèle: https://docs.djangoproject.com/en/2.2/topics/db/queries/#copying-model-instances

miah
la source
2
À noter que cela cite Django 1.2, nous en sommes maintenant à Django 1.4. Je n'ai pas testé si cela fonctionne ou non, mais n'utilisez pas cette réponse sans être sûr qu'elle fonctionne pour vous.
Joe
7
Fonctionne bien dans 1.4.1 C'est probablement l'une de ces choses qui continuera de fonctionner pendant longtemps.
2012
8
J'ai dû régler les deux obj.pket obj.idfaire ce travail dans Django 1.4
Petr Peller
3
@PetrPeller - les documents suggèrent que c'est parce que vous utilisez l'héritage de modèle.
Dominic Rodger
12
Remarque: les choses peuvent être un peu plus complexes s'il y a des clés étrangères, des one2one et des m2m impliqués (c'est-à-dire qu'il peut y avoir des scénarios de "copie profonde" plus complexes)
Ben Roberts
135

La documentation Django pour les requêtes de base de données comprend une section sur la copie des instances de modèle . En supposant que vos clés primaires sont générées automatiquement, vous obtenez l'objet que vous souhaitez copier, définissez la clé primaire Noneet enregistrez à nouveau l'objet:

blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1

blog.pk = None
blog.save() # blog.pk == 2

Dans cet extrait, le premier save()crée l'objet d'origine et le second save()crée la copie.

Si vous continuez à lire la documentation, il existe également des exemples sur la façon de gérer deux cas plus complexes: (1) copie d'un objet qui est une instance d'une sous-classe de modèle, et (2) copie également des objets connexes, y compris des objets dans plusieurs-à -de nombreuses relations.


Remarque sur la réponse de miah: régler le pk sur None est mentionné dans la réponse de miah, bien qu'il ne soit pas présenté à l'avant et au centre. Donc ma réponse sert principalement à souligner cette méthode comme étant la manière recommandée par Django de le faire.

Note historique: Cela n'a pas été expliqué dans les documents Django jusqu'à la version 1.4. Cela a été possible depuis avant 1.4 cependant.

Fonctionnalité future possible: la modification des documents susmentionnée a été effectuée dans ce ticket . Sur le fil de commentaires du ticket, il y avait aussi une discussion sur l'ajout d'une copyfonction intégrée pour les classes de modèles, mais pour autant que je sache, ils ont décidé de ne pas s'attaquer à ce problème pour le moment. Cette méthode de copie "manuelle" devra donc probablement le faire pour l'instant.

S. Kirby
la source
46

Faites attention ici. Cela peut être extrêmement coûteux si vous êtes dans une boucle quelconque et que vous récupérez les objets un par un. Si vous ne voulez pas l'appel à la base de données, faites simplement:

from copy import deepcopy

new_instance = deepcopy(object_you_want_copied)
new_instance.id = None
new_instance.save()

Il fait la même chose que certaines de ces autres réponses, mais il n'appelle pas la base de données pour récupérer un objet. Ceci est également utile si vous souhaitez faire une copie d'un objet qui n'existe pas encore dans la base de données.

Troy Grosfield
la source
1
Cela fonctionne très bien si vous avez un objet, vous pouvez copier en profondeur l'objet d'origine avant d'apporter des modifications, apporter des modifications au nouvel objet et l'enregistrer. Ensuite, vous pouvez faire une vérification de condition et en fonction de leur réussite, c'est-à-dire que l'objet se trouve dans une autre table que vous vérifiez, vous pouvez définir new_instance.id = original_instance.id et enregistrer :) Merci!
radtek
2
Cela ne fonctionne pas si le modèle a plusieurs niveaux d'héritage.
David Cheung
1
sur mon cas, je voulais créer une méthode de clonage pour le modèle, qui utiliserait la variable "self" et je ne peux pas simplement définir self.pk None, donc cette solution a fonctionné comme un charme. J'ai pensé à la solution model_to_dict ci-dessous, mais elle nécessite une étape supplémentaire et elle aurait le même problème avec les relations directes, que je dois traiter manuellement de toute façon, donc cela n'a pas d'impact majeur pour moi.
Anderson Santos
32

Utilisez le code ci-dessous:

from django.forms import model_to_dict

instance = Some.objects.get(slug='something')

kwargs = model_to_dict(instance, exclude=['id'])
new_instance = Some.objects.create(**kwargs)
t_io
la source
8
model_to_dictprend un excludeparamètre, ce qui signifie que vous n'avez pas besoin de séparer pop:model_to_dict(instance, exclude=['id'])
georgebrock
20

Il y a un extrait de clone ici , que vous pouvez ajouter à votre modèle:

def clone(self):
  new_kwargs = dict([(fld.name, getattr(old, fld.name)) for fld in old._meta.fields if fld.name != old._meta.pk]);
  return self.__class__.objects.create(**new_kwargs)
Dominic Rodger
la source
@ user426975 - ah, eh bien (je l'ai retiré de ma réponse).
Dominic Rodger
Je ne sais pas s'il s'agit d'une version Django, mais la ifdoit maintenant être if fld.name != old._meta.pk.name, c'est-à-dire la namepropriété de l' _meta.pkinstance.
Chris
20

Comment faire cela a été ajouté aux documents officiels de Django dans Django1.4

https://docs.djangoproject.com/en/1.10/topics/db/queries/#copying-model-instances

La réponse officielle est similaire à la réponse de miah, mais les documents soulignent certaines difficultés avec l'héritage et les objets associés, vous devez donc probablement vous assurer de lire les documents.

Michael Bylstra
la source
lorsque vous ouvrez le lien, la page est introuvable
Amrit
Les documents n'existent plus pour Django 1.4. Je mettrai à jour la réponse pour pointer vers les derniers documents.
Michael Bylstra
1
@MichaelBylstra Une bonne façon d'avoir des liens persistants est d'utiliser à la stableplace du numéro de version dans l'URL, comme ceci: docs.djangoproject.com/en/stable/topics/db/queries/…
Flimm
8

J'ai rencontré quelques accrochages avec la réponse acceptée. Voici ma solution.

import copy

def clone(instance):
    cloned = copy.copy(instance) # don't alter original instance
    cloned.pk = None
    try:
        delattr(cloned, '_prefetched_objects_cache')
    except AttributeError:
        pass
    return cloned

Remarque: cela utilise des solutions qui ne sont pas officiellement approuvées dans les documents Django, et elles pourraient cesser de fonctionner dans les futures versions. J'ai testé cela en 1.9.13.

La première amélioration est qu'elle vous permet de continuer à utiliser l'instance d'origine, en utilisant copy.copy . Même si vous n'avez pas l'intention de réutiliser l'instance, il peut être plus sûr de faire cette étape si l'instance que vous clonez a été passée comme argument à une fonction. Sinon, l'appelant aura de manière inattendue une instance différente lorsque la fonction reviendra.

copy.copysemble produire une copie superficielle d'une instance de modèle Django de la manière souhaitée. C'est l'une des choses que je n'ai pas trouvées documentées, mais cela fonctionne en décapant et en décollant, donc c'est probablement bien pris en charge.

Deuxièmement, la réponse approuvée laissera tous les résultats prélus attachés à la nouvelle instance. Ces résultats ne doivent pas être associés à la nouvelle instance, sauf si vous copiez explicitement les relations to-many. Si vous parcourez les relations prélues, vous obtiendrez des résultats qui ne correspondent pas à la base de données. La rupture du code de travail lorsque vous ajoutez une prélecture peut être une mauvaise surprise.

La suppression _prefetched_objects_cacheest un moyen rapide et sale de supprimer tous les préfixes. Les accès ultérieurs à plusieurs fonctionnent comme s'il n'y avait jamais eu de prélecture. L'utilisation d'une propriété non documentée qui commence par un trait de soulignement pose probablement des problèmes de compatibilité, mais cela fonctionne pour l'instant.

L'étoile du matin
la source
J'ai pu faire fonctionner cela, mais il semble que cela ait déjà changé en 1.11, car j'avais une propriété appelée _[model_name]_cache, qui, une fois supprimée, j'ai pu attribuer un nouvel ID pour ce modèle associé, puis appeler save(). Il pourrait encore y avoir des effets secondaires que je n'ai pas encore déterminés.
trpt4him
C'est une information extrêmement importante si vous effectuez le clonage dans une fonction sur la classe / mixin, car cela gâcherait autrement 'self' et vous vous confondriez.
Andreas Bergström
5

mettre pk sur None est meilleur, Sinse Django peut créer correctement un pk pour vous

object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()
Ardine
la source
3

C'est encore une autre façon de cloner l'instance de modèle:

d = Foo.objects.filter(pk=1).values().first()   
d.update({'id': None})
duplicate = Foo.objects.create(**d)
Ahtisham
la source
0

Pour cloner un modèle avec plusieurs niveaux d'héritage, c'est-à-dire> = 2, ou ModelC ci-dessous

class ModelA(models.Model):
    info1 = models.CharField(max_length=64)

class ModelB(ModelA):
    info2 = models.CharField(max_length=64)

class ModelC(ModelB):
    info3 = models.CharField(max_length=64)

Veuillez renvoyer la question ici .

David Cheung
la source
Ah oui, mais cette question n'a pas de réponse acceptée! Marche à suivre!
Bobort
0

Essaye ça

original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()
Pulkit Pahwa
la source