Como clonar um objeto de instância do modelo Django e salvá-lo no banco de dados?

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

No banco de dados, desejo adicionar outro objeto, que é uma cópia do objeto acima.

Suponha que minha tabela tenha uma linha. Quero inserir o primeiro objeto de linha em outra linha com uma chave primária diferente. Como eu posso fazer isso?

user426795
fonte

Respostas:

438

Apenas altere a chave primária do seu objeto e execute save ().

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

Se você deseja uma chave gerada automaticamente, defina a nova chave como Nenhuma.

Mais sobre UPDATE / INSERT aqui .

Documentos oficiais sobre a cópia de instâncias de modelo: https://docs.djangoproject.com/en/2.2/topics/db/queries/#copying-model-instances

miah
fonte
2
Vale a pena notar que isso cita o Django 1.2, agora estamos no Django 1.4. Não testou se isso funciona ou não, mas não use essa resposta sem ter certeza de que funciona para você.
Joe
7
Funciona bem no 1.4.1 Essa é provavelmente uma daquelas coisas que continuará funcionando por um longo tempo.
frnhr
8
Eu tive que definir os dois obj.pke obj.idfazer isso funcionar no Django 1.4
Petr Peller
3
@PetrPeller - os documentos sugerem que você está usando a herança do modelo.
precisa saber é o seguinte
12
Nota: as coisas podem ser um pouco mais complexa se existem chaves estrangeiras, One2One de e M2M está envolvido (ou seja, pode haver mais complexos cenários "cópia profunda")
Ben Roberts
135

A documentação do Django para consultas ao banco de dados inclui uma seção sobre como copiar instâncias de modelo . Supondo que suas chaves primárias sejam geradas automaticamente, você obtém o objeto que deseja copiar, define a chave primária Nonee salva o objeto novamente:

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

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

Nesse trecho, o primeiro save()cria o objeto original e o segundo save()cria a cópia.

Se você continuar lendo a documentação, também haverá exemplos de como lidar com dois casos mais complexos: (1) copiar um objeto que é uma instância de uma subclasse de modelo e (2) copiar objetos relacionados, incluindo objetos de muitos para muitas relações.


Nota sobre a resposta de miah: Definir o pk como Noneé mencionado na resposta de miah, embora não seja apresentado na frente e no centro. Portanto, minha resposta serve principalmente para enfatizar esse método como a maneira recomendada pelo Django de fazer isso.

Nota histórica: Isso não foi explicado nos documentos do Django até a versão 1.4. Isso foi possível desde antes da versão 1.4, no entanto.

Possível funcionalidade futura: a alteração dos documentos acima foi feita neste ticket . No tópico de comentários do ticket, houve também uma discussão sobre a adição de uma copyfunção interna para as classes de modelo, mas até onde eu sei, eles decidiram não resolver esse problema ainda. Portanto, essa maneira "manual" de copiar provavelmente terá que ser feita por enquanto.

S. Kirby
fonte
46

Tenha cuidado aqui. Isso pode ser extremamente caro se você estiver em um loop de algum tipo e estiver recuperando objetos um por um. Se você não deseja a chamada para o banco de dados, basta:

from copy import deepcopy

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

Ele faz o mesmo que algumas dessas outras respostas, mas não faz a chamada ao banco de dados para recuperar um objeto. Isso também é útil se você deseja fazer uma cópia de um objeto que ainda não existe no banco de dados.

Troy Grosfield
fonte
1
Isso funciona muito bem se você tiver um objeto, poderá copiar em profundidade o objeto original antes de fazer alterações, fazer alterações no novo objeto e salvá-lo. Depois, você pode fazer algumas verificações de condições e, dependendo de se elas passarem, ou seja, o objeto está em outra tabela que você está verificando, você pode definir o new_instance.id = original_instance.id e salvar :) Obrigado!
Radtek 25/09
2
Isso não funciona se o modelo tiver vários níveis de herança.
David Cheung
1
no meu caso, eu queria criar um método clone para o modelo, que usaria a variável "self" e não posso simplesmente definir o self.pk None, então essa solução funcionou como um encanto. Pensei na solução model_to_dict abaixo, mas ela requer uma etapa extra e teria o mesmo problema com as relações de passagem, que tenho que lidar manualmente de qualquer maneira, para que não tenha grande impacto para mim.
Anderson Santos
32

Use o código abaixo:

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
fonte
8
model_to_dictleva um excludeparâmetro, que significa que você não precisa do separado pop:model_to_dict(instance, exclude=['id'])
georgebrock
20

Há um trecho de clone aqui , que você pode adicionar ao seu modelo que faz isso:

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
fonte
@ user426975 - ah, tudo bem (removi-o da minha resposta).
Dominic Rodger
Não tenho certeza se isso é uma versão da Django, mas o ifagora precisa ser if fld.name != old._meta.pk.name, ou seja, a namepropriedade da _meta.pkinstância.
Chris
20

Como fazer isso foi adicionado aos documentos oficiais do Django no Django1.4

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

A resposta oficial é semelhante à resposta de miah, mas os documentos apontam algumas dificuldades com herança e objetos relacionados, portanto, você deve ter certeza de ler os documentos.

Michael Bylstra
fonte
quando você abre o link, ele diz que a página não foi encontrada
Amrit
Os documentos não existem mais para o Django 1.4. Atualizarei a resposta para apontar para os documentos mais recentes.
precisa saber é o seguinte
1
@MichaelBylstra Uma boa maneira de ter links sempre verdes é usar em stablevez do número da versão no URL, assim: docs.djangoproject.com/en/stable/topics/db/queries/…
Flimm
8

Encontrei algumas dicas com a resposta aceita. Aqui está a minha solução.

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: isso usa soluções que não são oficialmente sancionadas nos documentos do Django e podem deixar de funcionar em versões futuras. Eu testei isso em 1.9.13.

A primeira melhoria é que ela permite que você continue usando a instância original, usando copy.copy. Mesmo que você não pretenda reutilizar a instância, pode ser mais seguro executar esta etapa se a instância que você está clonando foi passada como argumento para uma função. Caso contrário, o chamador inesperadamente terá uma instância diferente quando a função retornar.

copy.copyparece produzir uma cópia superficial de uma instância do modelo Django da maneira desejada. Essa é uma das coisas que não encontrei documentada, mas funciona com decapagem e remoção, portanto provavelmente é bem suportada.

Em segundo lugar, a resposta aprovada deixará quaisquer resultados pré-buscados anexados à nova instância. Esses resultados não devem ser associados à nova instância, a menos que você copie explicitamente os relacionamentos entre muitos. Se você percorrer os relacionamentos pré-buscados, obterá resultados que não correspondem ao banco de dados. Quebrar o código de funcionamento quando você adiciona uma pré-busca pode ser uma surpresa desagradável.

A exclusão _prefetched_objects_cacheé uma maneira rápida e suja de remover todos os prefets. Os acessos subsequentes a muitos funcionam como se nunca houvesse uma pré-busca. O uso de uma propriedade não documentada que começa com um sublinhado provavelmente está solicitando problemas de compatibilidade, mas funciona por enquanto.

estrela da Manhã
fonte
Consegui fazer isso funcionar, mas parece que ele já foi alterado na 1.11, pois eu tinha uma propriedade chamada _[model_name]_cache, que, uma vez excluída, pude atribuir um novo ID para esse modelo relacionado e depois ligar save(). Ainda pode haver efeitos colaterais que ainda não foram determinados.
trpt4him
Esta é uma informação extremamente importante se você estiver clonando em uma função na classe / mixin, pois, de outra forma, atrapalhará o 'eu' e você ficará confuso.
Andreas Bergström
5

definir pk como None é melhor, o sinse Django pode criar corretamente um pk para você

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

Esta é outra maneira de clonar a instância do modelo:

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

Para clonar um modelo com vários níveis de herança, ou seja,> = 2 ou ModelC abaixo

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 a pergunta aqui .

David Cheung
fonte
Ah, sim, mas essa pergunta não tem uma resposta aceita! Caminho a percorrer!
Bobort
0

Tente isto

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