web-dev-qa-db-fra.com

Comment cloner un objet d'instance de modèle Django et l'enregistrer 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 a une rangée. Je veux insérer le premier objet de la ligne dans une autre ligne avec une clé primaire différente. Comment puis je faire ça?

210
user426795

Il suffit de changer la clé primaire de votre objet et d’exécuter 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é sur Aucune.

Plus sur UPDATE/INSERT ici .

355
miah

La documentation de Django pour les requêtes de base de données comprend une section sur la copie d'instances de modèle . En supposant que vos clés primaires soient générées automatiquement, vous obtenez l'objet que vous souhaitez copier, définissez la clé primaire sur None et enregistrez-le à 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, la première save() crée l'objet d'origine et la seconde save() crée la copie.

Si vous continuez à lire la documentation, vous trouverez é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 de modèle et (2) copier également des objets connexes, y compris des objets de plusieurs -de nombreuses relations.


Note sur la réponse de miah: La réponse de miah mentionne le paramètre pk sur None, bien qu'il ne soit pas présenté au premier plan. Donc, ma réponse sert principalement à souligner cette méthode comme la méthode recommandée par Django.

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és futures possibles: La modification susmentionnée de la documentation a été effectuée dans ce ticket . Dans le fil de commentaire du ticket, des discussions ont également eu lieu sur l'ajout d'une fonction copy intégrée pour les classes de modèle, mais pour autant que je sache, ils ont décidé de ne pas encore s'attaquer à ce problème. Donc, cette manière "manuelle" de copier devra probablement être faite pour le moment.

118
S. Kirby

Soyez prudent ici. Cela peut être extrêmement coûteux si vous êtes dans une boucle et que vous récupérez des objets un à un. Si vous ne voulez pas appeler 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 ne demande pas à la base de données de récupérer un objet. Ceci est également utile si vous souhaitez copier un objet qui n'existe pas encore dans la base de données.

37
Troy Grosfield

Il y a un extrait de clone ici , que vous pouvez ajouter à votre modèle et 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)
19
Dominic Rodger

Comment faire cela a été ajouté à la documentation officielle de Django dans Django1.4

https://docs.djangoproject.com/fr/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 liées à l'héritage et aux objets associés. Vous devez donc probablement vous assurer de les lire.

19
Michael Bylstra

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)
18
t_io

définir pk sur Aucun est préférable, Sinse Django peut créer correctement un pk pour vous.

object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()
4
Ardine

J'ai rencontré un couple de marre 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: ceci utilise des solutions qui ne sont pas officiellement approuvées dans la documentation Django et risquent de ne plus fonctionner dans les versions futures. J'ai testé cela en 1.9.13.

La première amélioration est que cela 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 prudent d'effectuer cette étape si l'instance que vous clonez a été transmise en tant qu'argument à une fonction. Sinon, l'appelant aura inopinément une instance différente au retour de la fonction.

copy.copy semble 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ée documentée, mais cela fonctionne en décapant et décapant, elle est donc probablement bien prise en charge.

Deuxièmement, la réponse approuvée laissera tous les résultats prélus en amont attachés à la nouvelle instance. Ces résultats ne doivent pas être associés à la nouvelle instance, sauf si vous copiez explicitement les relations à plusieurs. Si vous parcourez les relations prélues à l'avance, vous obtiendrez des résultats qui ne correspondent pas à la base de données. Interrompre le code de travail lorsque vous ajoutez une prélecture peut être une mauvaise surprise.

La suppression de _prefetched_objects_cache est un moyen rapide et efficace d’éliminer tous les prélèvements. Les accès suivants à plusieurs fonctionnent comme s'il n'y avait jamais eu de prélecture. L'utilisation d'une propriété non documentée commençant par un trait de soulignement pose probablement un problème de compatibilité, mais cela fonctionne pour le moment.

3
morningstar

Essaye ça

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

C'est un autre moyen de cloner l'instance de modèle:

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

Pour cloner un modèle avec plusieurs niveaux d'héritage, à savoir> = 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 vous référer à la question ici .

0
David Cheung