Come posso clonare un oggetto istanza del modello Django e salvarlo nel database?


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

Nel database, voglio aggiungere un altro oggetto che è una copia dell'oggetto sopra.

Supponiamo che il mio tavolo abbia una riga. Voglio inserire l'oggetto della prima riga in un'altra riga con una chiave primaria diversa. Come lo posso fare?

Risposte:


438

Basta cambiare la chiave primaria dell'oggetto ed eseguire save ().

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

Se si desidera la chiave generata automaticamente, impostare la nuova chiave su Nessuno.

Maggiori informazioni su UPDATE / INSERT qui .

Documenti ufficiali sulla copia delle istanze del modello: https://docs.djangoproject.com/en/2.2/topics/db/queries/#copying-model-instances


2
Vale la pena notare che questo cita Django 1.2, ora siamo su Django 1.4. Non ho testato se funziona o meno, ma non usare questa risposta senza essere sicuro che funzioni per te.
Joe,

7
Funziona bene in 1.4.1 Questa è probabilmente una di quelle cose che continueranno a funzionare per molto tempo.
dal

8
Ho dovuto impostare entrambi obj.pke obj.idfare questo lavoro in Django 1.4
Petr Peller il

3
@PetrPeller: i documenti suggeriscono che stai utilizzando l'ereditarietà del modello.
Dominic Rodger,

12
Nota: le cose potrebbero essere un po 'più complesse se ci sono chiavi esterne, one2one e m2m coinvolti (cioè potrebbero esserci scenari di "copia profonda" più complessi)
Ben Roberts,

135

La documentazione di Django per le query del database include una sezione sulla copia delle istanze del modello . Supponendo che le tue chiavi primarie siano generate automaticamente, ottieni l'oggetto che vuoi copiare, imposta la chiave primaria Nonee salva nuovamente l'oggetto:

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

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

In questo frammento, il primo save()crea l'oggetto originale e il secondo save()crea la copia.

Se continui a leggere la documentazione, ci sono anche esempi su come gestire due casi più complessi: (1) la copia di un oggetto che è un'istanza di una sottoclasse del modello e (2) anche la copia di oggetti correlati, inclusi gli oggetti in molti -molte relazioni.


Nota sulla risposta di miah: L'impostazione di pk su Noneè menzionata nella risposta di miah, sebbene non sia presentata in primo piano. Quindi la mia risposta serve principalmente a enfatizzare quel metodo come il modo raccomandato da Django per farlo.

Nota storica: questo non è stato spiegato nei documenti Django fino alla versione 1.4. Tuttavia, era possibile da prima dell'1.4.

Possibili funzionalità future: la modifica di documenti sopra menzionata è stata apportata in questo ticket . Sul thread dei commenti del ticket, si è discusso anche dell'aggiunta di una copyfunzione integrata per le classi di modelli, ma per quanto ne so hanno deciso di non affrontare ancora questo problema. Quindi, per ora, questo modo di copiare "manuale" dovrà probabilmente funzionare.


46

Stai attento qui. Questo può essere estremamente costoso se sei in un ciclo di qualche tipo e stai recuperando oggetti uno per uno. Se non vuoi la chiamata al database, fai semplicemente:

from copy import deepcopy

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

Fa la stessa cosa di alcune di queste altre risposte, ma non effettua la chiamata al database per recuperare un oggetto. Ciò è utile anche se si desidera creare una copia di un oggetto che non esiste ancora nel database.


1
Funziona alla grande se si dispone di un oggetto, è possibile copiare in profondità l'oggetto originale prima di apportare modifiche apportare modifiche al nuovo oggetto e salvarlo. Quindi è possibile eseguire alcune verifiche delle condizioni e, a seconda che passino, ovvero l'oggetto si trova in un'altra tabella che si sta verificando, è possibile impostare new_instance.id = original_instance.id e salvare :) Grazie!
Radtek,

2
Questo non funziona se il modello ha più livelli di ereditarietà.
David Cheung,

1
nel mio caso volevo creare un metodo clone per il modello, che avrebbe usato la variabile "self" e non potevo semplicemente impostare self.pk None, quindi questa soluzione ha funzionato come un incantesimo. Ho pensato alla soluzione model_to_dict di seguito, ma richiede un ulteriore passaggio e avrebbe lo stesso problema con le relazioni passanti, che devo comunque trattare manualmente, quindi non ha alcun impatto rilevante per me.
Anderson Santos,

32

Usa il codice qui sotto:

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)

8
model_to_dictaccetta un excludeparametro, il che significa che non è necessario il separato pop:model_to_dict(instance, exclude=['id'])
georgebrock

20

C'è uno snippet di clone qui , che puoi aggiungere al tuo modello e che fa questo:

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)

@ user426975 - ah, vabbè (l'ho rimosso dalla mia risposta).
Dominic Rodger,

Non sono sicuro che si tratti di una versione della versione di Django, ma ifora deve essere if fld.name != old._meta.pk.namela nameproprietà _meta.pkdell'istanza.
Chris

20

Come fare questo è stato aggiunto ai documenti ufficiali di Django in Django1.4

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

La risposta ufficiale è simile alla risposta di miah, ma i documenti indicano alcune difficoltà con l'ereditarietà e gli oggetti correlati, quindi dovresti probabilmente assicurarti di leggere i documenti.


quando apri il link dice che la pagina non è stata trovata
Amrit,

I documenti non esistono più per Django 1.4. Aggiornerò la risposta per indicare gli ultimi documenti.
Michael Bylstra, il

1
@MichaelBylstra Un buon modo per avere collegamenti sempreverdi è usare al stableposto del numero di versione nell'URL, in questo modo: docs.djangoproject.com/en/stable/topics/db/queries/…
Flimm

8

Ho incontrato un paio di gotcha con la risposta accettata. Ecco la mia soluzione

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: questo utilizza soluzioni che non sono ufficialmente sanzionate nei documenti Django e potrebbero smettere di funzionare nelle versioni future. Ho provato questo in 1.9.13.

Il primo miglioramento è che ti consente di continuare a usare l'istanza originale, usando copy.copy. Anche se non intendi riutilizzare l'istanza, può essere più sicuro eseguire questo passaggio se l'istanza che stai clonando è stata passata come argomento a una funzione. In caso contrario, il chiamante avrà inaspettatamente un'istanza diversa quando viene restituita la funzione.

copy.copysembra produrre una copia superficiale di un'istanza del modello Django nel modo desiderato. Questa è una delle cose che non ho trovato documentato, ma funziona decapando e scartando, quindi probabilmente è ben supportato.

In secondo luogo, la risposta approvata lascerà tutti i risultati precaricati allegati alla nuova istanza. Tali risultati non dovrebbero essere associati alla nuova istanza, a meno che non si copino esplicitamente le relazioni a molti. Se attraversi le relazioni precaricate, otterrai risultati che non corrispondono al database. Rompere il codice di lavoro quando aggiungi un prefetch può essere una brutta sorpresa.

L'eliminazione _prefetched_objects_cacheè un modo rapido e sporco per eliminare tutti i prefetch. I successivi accessi a molti funzionano come se non ci fosse mai un prefetch. L'uso di una proprietà non documentata che inizia con un carattere di sottolineatura probabilmente richiede problemi di compatibilità, ma per ora funziona.


Sono stato in grado di farlo funzionare, ma sembra che potrebbe essere già cambiato in 1.11, poiché avevo una proprietà chiamata _[model_name]_cache, che, una volta eliminata, sono stata in grado di assegnare un nuovo ID per quel modello correlato, quindi chiamare save(). Potrebbero esserci ancora effetti collaterali che non ho ancora determinato.
trpt4him,

Questa è un'informazione estremamente importante se stai eseguendo la clonazione in una funzione della classe / mixin, poiché altrimenti confonderà 'sé' e ti confonderai.
Andreas Bergström,

5

impostando pk su Nessuno è meglio, peccato che Django possa creare correttamente un pk per te

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

3

Questo è ancora un altro modo di clonare l'istanza del modello:

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

0

Per clonare un modello con più livelli di ereditarietà, ovvero> = 2 o ModelC di seguito

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)

Si prega di fare riferimento alla domanda qui .


Ah sì, ma quella domanda non ha una risposta accettata! Ben fatto!
Bobort,

0

Prova questo

original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.