Wie klone ich ein Django-Modellinstanzobjekt und speichere es in der Datenbank?

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

In der Datenbank möchte ich ein weiteres Objekt hinzufügen, das eine Kopie des obigen Objekts ist.

Angenommen, meine Tabelle hat eine Zeile. Ich möchte das erste Zeilenobjekt mit einem anderen Primärschlüssel in eine andere Zeile einfügen. Wie kann ich das machen?

user426795
quelle

Antworten:

437

Ändern Sie einfach den Primärschlüssel Ihres Objekts und führen Sie save () aus.

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

Wenn Sie einen automatisch generierten Schlüssel wünschen, setzen Sie den neuen Schlüssel auf Keine.

Mehr zu UPDATE / INSERT hier .

Offizielle Dokumente zum Kopieren von Modellinstanzen: https://docs.djangoproject.com/de/2.2/topics/db/queries/#copying-model-instances

miah
quelle
2
Es ist erwähnenswert, dass dies Django 1.2 zitiert. Wir sind jetzt bei Django 1.4. Ich habe nicht getestet, ob dies funktioniert oder nicht, aber verwende diese Antwort nicht, ohne sicher zu sein, dass sie für dich funktioniert.
Joe
7
Funktioniert gut in 1.4.1 Dies ist wahrscheinlich eines der Dinge, die noch lange funktionieren werden.
Freitag,
8
Ich musste beides einstellen obj.pkund obj.iddiese Arbeit in Django 1.4
Petr Peller am
3
@PetrPeller - Die Dokumente legen nahe, dass Sie die Modellvererbung verwenden.
Dominic Rodger
12
Hinweis: Die Dinge können etwas komplexer sein, wenn Fremdschlüssel, one2one und m2m beteiligt sind (dh es kann komplexere "Deep Copy" -Szenarien geben)
Ben Roberts
135

Die Django-Dokumentation für Datenbankabfragen enthält einen Abschnitt zum Kopieren von Modellinstanzen . Angenommen, Ihre Primärschlüssel werden automatisch generiert, erhalten Sie das Objekt, das Sie kopieren möchten, setzen den Primärschlüssel auf Noneund speichern das Objekt erneut:

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

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

In diesem Snippet erstellt das erste save()das Originalobjekt und das zweite save()die Kopie.

Wenn Sie die Dokumentation weiterlesen, finden Sie auch Beispiele für die Behandlung von zwei komplexeren Fällen: (1) Kopieren eines Objekts, das eine Instanz einer Modellunterklasse ist, und (2) Kopieren verwandter Objekte, einschließlich Objekte in vielen Fällen -Viele Beziehungen.


Hinweis zur Antwort von miah: Das Setzen des pk auf Nonewird in der Antwort von miah erwähnt, obwohl es nicht vorne und in der Mitte dargestellt wird. Meine Antwort dient also hauptsächlich dazu, diese Methode als die von Django empfohlene Methode zu betonen.

Historischer Hinweis: Dies wurde in den Django-Dokumenten erst in Version 1.4 erklärt. Dies ist jedoch seit vor 1.4 möglich.

Mögliche zukünftige Funktionalität: Die oben genannten Dokumentänderungen wurden in diesem Ticket vorgenommen . Im Kommentarthread des Tickets gab es auch einige Diskussionen über das Hinzufügen einer integrierten copyFunktion für Modellklassen, aber soweit ich weiß, haben sie beschlossen, dieses Problem noch nicht anzugehen. Diese "manuelle" Art des Kopierens muss also wahrscheinlich erst einmal reichen.

S. Kirby
quelle
46

Sei hier vorsichtig. Dies kann extrem teuer sein, wenn Sie sich in einer Schleife befinden und Objekte einzeln abrufen. Wenn Sie die Datenbank nicht aufrufen möchten, gehen Sie einfach wie folgt vor:

from copy import deepcopy

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

Es macht dasselbe wie einige dieser anderen Antworten, führt jedoch keinen Datenbankaufruf zum Abrufen eines Objekts durch. Dies ist auch nützlich, wenn Sie eine Kopie eines Objekts erstellen möchten, das noch nicht in der Datenbank vorhanden ist.

Troy Grosfield
quelle
1
Dies funktioniert hervorragend, wenn Sie ein Objekt haben. Sie können das ursprüngliche Objekt tief kopieren, bevor Sie Änderungen vornehmen. Nehmen Sie Änderungen am neuen Objekt vor und speichern Sie es. Dann können Sie eine Bedingungsprüfung durchführen und abhängig davon, ob sie bestanden wurden, dh das Objekt sich in einer anderen Tabelle befindet, die Sie prüfen, können Sie die Datei new_instance.id = original_instance.id festlegen und speichern :) Danke!
Radtek
2
Dies funktioniert nicht, wenn das Modell mehrere Vererbungsstufen hat.
David Cheung
1
In meinem Fall wollte ich eine Klonmethode für das Modell erstellen, die die Variable "self" verwendet, und ich kann nicht einfach die Variable self.pk None festlegen. Diese Lösung funktionierte also wie ein Zauber. Ich habe über die model_to_dict-Lösung unten nachgedacht, aber es erfordert einen zusätzlichen Schritt und es würde das gleiche Problem mit den Durchgangsbeziehungen geben, die ich ohnehin manuell behandeln muss, damit es für mich keine größeren Auswirkungen hat.
Anderson Santos
32

Verwenden Sie den folgenden Code:

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
quelle
8
model_to_dictnimmt einen excludeParameter, was bedeutet, dass Sie den separaten nicht benötigenpop :model_to_dict(instance, exclude=['id'])
Georgebrock
20

Es gibt einen Klon Schnipsel hier , was Sie zu Ihrem Modell hinzufügen können , die dies tut:

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
quelle
@ user426975 - ah, na ja (ich habe es aus meiner Antwort entfernt).
Dominic Rodger
Ich bin mir nicht sicher, ob es sich um eine Django-Version handelt, aber das muss ifjetzt if fld.name != old._meta.pk.namedie nameEigenschaft der _meta.pkInstanz sein.
Chris
20

Wie das geht, wurde den offiziellen Django-Dokumenten in Django1.4 hinzugefügt

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

Die offizielle Antwort ähnelt der Antwort von miah, aber die Dokumente weisen auf einige Schwierigkeiten bei der Vererbung und verwandten Objekten hin. Sie sollten daher wahrscheinlich sicherstellen, dass Sie die Dokumente lesen.

Michael Bylstra
quelle
Wenn Sie den Link öffnen, heißt es, dass die Seite nicht gefunden wurde
Amrit
Die Dokumente existieren für Django 1.4 nicht mehr. Ich werde die Antwort aktualisieren, um auf die neuesten Dokumente zu verweisen.
Michael Bylstra
1
@ MichaelBylstra Eine gute Möglichkeit, immergrüne Links zu haben, besteht darin, stableanstelle der Versionsnummer in der URL Folgendes zu verwenden
Flimm
8

Ich bin auf ein paar Fallstricke mit der akzeptierten Antwort gestoßen. Hier ist meine Lösung.

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

Hinweis: Hierbei werden Lösungen verwendet, die in den Django-Dokumenten nicht offiziell genehmigt wurden und in zukünftigen Versionen möglicherweise nicht mehr funktionieren. Ich habe dies in 1.9.13 getestet.

Die erste Verbesserung besteht darin, dass Sie die ursprüngliche Instanz weiterhin verwenden können, indem Sie verwenden copy.copy . Selbst wenn Sie nicht beabsichtigen, die Instanz wiederzuverwenden, kann es sicherer sein, diesen Schritt auszuführen, wenn die zu klonende Instanz als Argument an eine Funktion übergeben wurde. Wenn nicht, hat der Aufrufer unerwartet eine andere Instanz, wenn die Funktion zurückkehrt.

copy.copyscheint eine flache Kopie einer Django-Modellinstanz auf die gewünschte Weise zu erzeugen. Dies ist eines der Dinge, die ich nicht dokumentiert gefunden habe, aber es funktioniert durch Beizen und Entpicken, daher wird es wahrscheinlich gut unterstützt.

Zweitens werden bei der genehmigten Antwort alle vorab abgerufenen Ergebnisse an die neue Instanz angehängt. Diese Ergebnisse sollten nicht mit der neuen Instanz verknüpft werden, es sei denn, Sie kopieren die zu vielen Beziehungen explizit. Wenn Sie die vorab abgerufenen Beziehungen durchlaufen, erhalten Sie Ergebnisse, die nicht mit der Datenbank übereinstimmen. Das Brechen des Arbeitscodes beim Hinzufügen eines Prefetch kann eine böse Überraschung sein.

Das Löschen _prefetched_objects_cacheist eine schnelle und schmutzige Methode, um alle Prefetches zu entfernen. Nachfolgende zu viele Zugriffe funktionieren so, als ob es nie einen Prefetch gegeben hätte. Die Verwendung einer undokumentierten Eigenschaft, die mit einem Unterstrich beginnt, führt wahrscheinlich zu Kompatibilitätsproblemen, funktioniert jedoch vorerst.

Morgen Stern
quelle
Ich konnte dies zum Laufen bringen, aber es sieht so aus, als hätte es sich bereits in 1.11 geändert, da ich eine Eigenschaft namens hatte _[model_name]_cache, die ich nach dem Löschen eine neue ID für das zugehörige Modell zuweisen und dann aufrufen konnte save(). Es könnte immer noch Nebenwirkungen geben, die ich noch nicht festgestellt habe.
trpt4him
Dies ist eine äußerst wichtige Information, wenn Sie das Klonen in einer Funktion der Klasse / des Mixins durchführen, da dies sonst das 'Selbst' durcheinander bringt und Sie verwirrt werden.
Andreas Bergström
5

pk auf None zu setzen ist besser, da sinse Django ein pk korrekt für dich erstellen kann

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

Dies ist eine weitere Möglichkeit, die Modellinstanz zu klonen:

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

So klonen Sie ein Modell mit mehreren Vererbungsstufen, z. B.> = 2 oder ModelC unten

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)

Bitte beziehen Sie sich hier auf die Frage .

David Cheung
quelle
Ah ja, aber diese Frage hat keine akzeptierte Antwort! Gut gemacht!
Bobort
0

Versuche dies

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