Comment cloner un objet Django model instance et le sauvegarder dans la base de données?

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 rangée. Je veux insérer l'objet de la première rangée dans une autre rangée avec une clé primaire différente. Comment puis-je le faire?

192
demandé sur Artem Likhvar 2011-01-19 12:30:36

10 réponses

il suffit de changer la clé primaire de votre objet et de lancer save().

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

si vous voulez une clé générée automatiquement, définissez la nouvelle clé à None.

pour en savoir plus sur UPDATE/INSERT here .

334
répondu miah 2016-12-22 08:24:37

la documentation de Django pour les requêtes de base de données inclut une section sur la copie des instances modèles . En supposant que vos clés primaires sont autogénérées, vous obtenez l'objet que vous voulez copier, définissez la clé primaire à None , et sauvegardez l'objet à nouveau:

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 original, et le second save() crée la copie.

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


Note sur la réponse de miah: le réglage du pk à 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 la méthode recommandée par Django pour faire.

note historique: Ce n'est pas expliqué dans les docs Django avant la version 1.4. Il a été possible depuis avant la 1.4.

fonctionnalité future Possible: le changement de docs susmentionné a été fait dans ce billet . Sur le fil de commentaires du ticket, il y a aussi eu une discussion sur l'ajout d'une fonction intégrée copy pour les classes modèles, mais pour autant que je sache, ils ont décidé de ne pas s'attaquer à ce problème encore. Si cette façon" manuelle " de copier devra probablement le faire pour l'instant.

111
répondu S. Kirby 2017-04-26 17:47:00

faites attention. Cela peut être extrêmement coûteux si vous êtes dans une sorte de boucle et vous récupérez des objets un par un. Si vous ne voulez pas l'appel à la base de données, il suffit de faire:

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 certains de ces autres réponses, mais ce n'est pas la base de données pour récupérer un objet. Ceci est également utile si vous voulez faire une copie d'un objet qui n'existe pas encore dans la base de données.

34
répondu Troy Grosfield 2013-10-31 10:26:20

Comment cela a été ajouté à l'agent de Django docs en 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 docs soulignent certaines difficultés avec l'héritage et les objets connexes, donc vous devriez probablement vous assurer de lire les docs.

19
répondu Michael Bylstra 2017-01-25 05:24:29

il y a un extrait de clone ici , que vous pouvez ajouter à votre modèle qui fait ceci:

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)
18
répondu Dominic Rodger 2011-01-19 12:27:14

utiliser 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)
17
répondu t_io 2015-07-20 06:57:13

mettre pk à None est mieux, sinse Django peut correctement créer un pk pour vous

object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()
4
répondu Ardine 2014-09-12 07:59:51

j'ai rencontré quelques gotchas 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

Note: cela utilise des solutions qui ne sont pas officiellement sanctionnées dans les docs Django, et ils peuvent cesser de fonctionner dans les versions futures. J'ai testé ça en 1.9.13.

la première amélioration est qu'il 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 le faire étape si l'instance que vous clonez a été passée comme argument à une fonction. Si ce n'est pas le cas, l'appelant aura inopinément une instance différente lorsque la fonction retourne.

copy.copy semble produire une copie superficielle d'un modèle D'instance Django de la manière souhaitée. C'est une des choses que je n'ai pas trouvé documentée, mais ça fonctionne par décapage et déballage, donc c'est probablement bien supporté.

Deuxièmement, la réponse approuvée laissera n'importe quel préfixe résultats associé à la nouvelle instance. Ces résultats ne devraient pas être associés à la nouvelle instance, à moins que vous ne copiez explicitement les relations à-many. Si vous parcourez les relations préfixées, vous obtiendrez des résultats qui ne correspondent pas à la base de données. Casser le code de travail quand vous ajoutez un préfetch peut être une mauvaise surprise.

Supprimer _prefetched_objects_cache est un moyen rapide et sale pour enlever tous les préfets. Après-beaucoup d'accès fonctionnent comme s'il n'y avait jamais eu de préfetch. Utiliser une propriété non documentée qui commence par un underscore est probablement demander des problèmes de compatibilité, mais cela fonctionne pour le moment.

2
répondu morningstar 2018-04-27 02:29:49

Pour cloner un modèle de l'héritage multiple niveaux, 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)

s'il vous Plaît se référer à la question ici .

0
répondu David Cheung 2017-05-23 12:02:46

Essayez cette

original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()
0
répondu Pulkit Pahwa 2016-12-22 10:07:13