¿Cómo clono un objeto de instancia de modelo Django y lo guardo en la base de datos?

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

En la base de datos, quiero agregar otro objeto que es una copia del objeto anterior.

Supongamos que mi mesa tiene una fila. Quiero insertar el objeto de la primera fila en otra fila con una clave primaria diferente. ¿Cómo puedo hacer eso?

usuario426795
fuente

Respuestas:

438

Simplemente cambie la clave principal de su objeto y ejecute save ().

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

Si desea una clave generada automáticamente, establezca la nueva clave en Ninguna.

Más información sobre ACTUALIZAR / INSERTAR aquí .

Documentos oficiales sobre la copia de instancias de modelos: https://docs.djangoproject.com/en/2.2/topics/db/queries/#copying-model-instances

miah
fuente
2
Vale la pena señalar que esto cita a Django 1.2, ahora estamos a la altura de Django 1.4. No he probado si esto funciona o no, pero no use esta respuesta sin estar seguro de que funciona para usted.
Joe
77
Funciona bien en 1.4.1 Esta es probablemente una de esas cosas que continuarán funcionando durante mucho tiempo.
viernes
8
Tuve que configurar ambos obj.pky obj.idhacer que esto funcione en Django 1.4
Petr Peller
3
@PetrPeller: los documentos sugieren que se debe a que está utilizando la herencia del modelo.
Dominic Rodger
12
Nota: las cosas pueden ser un poco más complejas si hay claves externas, one2one y m2m involucradas (es decir, puede haber escenarios de "copia profunda" más complejos)
Ben Roberts
135

La documentación de Django para consultas de bases de datos incluye una sección sobre cómo copiar instancias de modelos . Suponiendo que sus claves primarias se generan automáticamente, obtiene el objeto que desea copiar, establece la clave primaria Noney guarda el objeto nuevamente:

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

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

En este fragmento, el primero save()crea el objeto original y el segundo save()crea la copia.

Si sigue leyendo la documentación, también hay ejemplos sobre cómo manejar dos casos más complejos: (1) copiar un objeto que es una instancia de una subclase de modelo, y (2) también copiar objetos relacionados, incluidos objetos en muchos -muchas relaciones.


Nota sobre la respuesta de miah: la configuración de pk Nonese menciona en la respuesta de miah, aunque no se presenta al frente y al centro. Entonces mi respuesta sirve principalmente para enfatizar ese método como la forma recomendada de Django para hacerlo.

Nota histórica: esto no se explicó en los documentos de Django hasta la versión 1.4. Sin embargo, ha sido posible desde antes de 1.4.

Posible funcionalidad futura: el cambio de documentos antes mencionado se realizó en este ticket . En el hilo de comentarios del ticket, también hubo una discusión sobre la adición de una copyfunción incorporada para las clases de modelo, pero que yo sepa, decidieron no abordar ese problema todavía. Por lo tanto, esta forma de copia "manual" probablemente tendrá que funcionar por ahora.

S. Kirby
fuente
46

Ten cuidado aquí. Esto puede ser extremadamente costoso si está en algún tipo de bucle y está recuperando objetos uno por uno. Si no desea la llamada a la base de datos, simplemente haga lo siguiente:

from copy import deepcopy

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

Hace lo mismo que algunas de estas otras respuestas, pero no hace que la base de datos llame para recuperar un objeto. Esto también es útil si desea hacer una copia de un objeto que aún no existe en la base de datos.

Troy Grosfield
fuente
1
Esto funciona muy bien si tiene un objeto, puede copiar en profundidad el objeto original antes de hacer cambios, hacer cambios en el nuevo objeto y guardarlo. Luego puede hacer algunas comprobaciones de condición y, dependiendo de si pasan, es decir, si el objeto está en otra tabla que está comprobando, puede establecer new_instance.id = original_instance.id y guardar :) ¡Gracias!
radtek
2
Esto no funciona si el modelo tiene múltiples niveles de herencia.
David Cheung el
1
en mi caso, quería crear un método de clonación para el modelo, que usaría la variable "self" y no puedo simplemente configurar self.pk None, por lo que esta solución funcionó de maravilla. Pensé en la solución model_to_dict a continuación, pero requiere un paso adicional y tendría el mismo problema con las relaciones directas, que tengo que tratar manualmente de todos modos para que no tenga un gran impacto para mí.
Anderson Santos
32

Use el siguiente código:

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
fuente
8
model_to_dicttoma un excludeparámetro, lo que significa que no necesita el separado pop:model_to_dict(instance, exclude=['id'])
georgebrock
20

Hay un fragmento de clon de aquí , que se puede añadir a su modelo que hace esto:

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
fuente
@ user426975 - ah, bueno (lo he eliminado de mi respuesta).
Dominic Rodger
No estoy seguro de si se trata de una versión de Django, pero ifahora debe ser if fld.name != old._meta.pk.name, es decir, la namepropiedad de la _meta.pkinstancia.
Chris
20

Cómo hacer esto se agregó a los documentos oficiales de Django en Django1.4

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

La respuesta oficial es similar a la respuesta de miah, pero los documentos señalan algunas dificultades con la herencia y los objetos relacionados, por lo que probablemente debería asegurarse de leer los documentos.

Michael Bylstra
fuente
cuando abres el enlace dice que no se encontró la página
Amrit
Los documentos ya no existen para Django 1.4. Actualizaré la respuesta para señalar los últimos documentos.
Michael Bylstra el
1
@MichaelBylstra Una buena manera de tener enlaces de hoja perenne es usar en stablelugar del número de versión en la URL, como este: docs.djangoproject.com/en/stable/topics/db/queries/…
Flimm
8

Me he encontrado con un par de trampas con la respuesta aceptada. Aquí está mi solución.

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

Nota: esto utiliza soluciones que no están oficialmente autorizadas en los documentos de Django, y pueden dejar de funcionar en futuras versiones. Probé esto en 1.9.13.

La primera mejora es que le permite continuar utilizando la instancia original, utilizando copy.copy . Incluso si no tiene la intención de reutilizar la instancia, puede ser más seguro realizar este paso si la instancia que está clonando se pasó como argumento a una función. De lo contrario, la persona que llama inesperadamente tendrá una instancia diferente cuando la función regrese.

copy.copyparece producir una copia superficial de una instancia de modelo Django de la manera deseada. Esta es una de las cosas que no encontré documentada, pero funciona encurtiendo y desenredando, por lo que probablemente tenga un buen soporte.

En segundo lugar, la respuesta aprobada dejará los resultados obtenidos previamente adjuntos a la nueva instancia. Esos resultados no deben asociarse con la nueva instancia, a menos que copie explícitamente las relaciones de muchos. Si recorre las relaciones captadas previamente, obtendrá resultados que no coinciden con la base de datos. Romper el código de trabajo cuando agrega una captación previa puede ser una desagradable sorpresa.

Eliminar _prefetched_objects_cachees una forma rápida y sucia de eliminar todas las captaciones previas. Los accesos posteriores a muchos funcionan como si nunca hubiera una captación previa. El uso de una propiedad no documentada que comienza con un guión bajo probablemente requiera problemas de compatibilidad, pero funciona por ahora.

estrella de la mañana
fuente
Pude hacer que esto funcionara, pero parece que ya ha cambiado en 1.11, ya que tenía una propiedad llamada _[model_name]_cache, que, una vez eliminada, pude asignar una nueva ID para ese modelo relacionado, y luego llamar save(). Todavía podría haber efectos secundarios que aún no he determinado.
trpt4him
Esta es información extremadamente importante si está haciendo la clonación en una función en la clase / mixin, ya que de lo contrario se arruinará y se confundirá.
Andreas Bergström
5

establecer pk en None es mejor, sinse Django puede crear correctamente un pk para usted

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

Esta es otra forma de clonar la instancia del modelo:

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

Para clonar un modelo con múltiples niveles de herencia, es decir,> = 2, o ModelC a continuación

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)

Por favor, consulte la pregunta aquí .

David Cheung
fuente
Ah sí, ¡pero esa pregunta no tiene una respuesta aceptada! ¡Camino a seguir!
Bobort
0

Prueba esto

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