Bagaimana cara mengkloning objek instance model Django dan menyimpannya ke database?

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

Dalam database, saya ingin menambahkan objek lain yang merupakan salinan dari objek di atas.

Misalkan meja saya memiliki satu baris. Saya ingin memasukkan objek baris pertama ke baris lain dengan kunci primer yang berbeda. Bagaimana saya bisa melakukan itu?

pengguna426795
sumber

Jawaban:

438

Cukup ganti kunci utama objek Anda dan jalankan save ().

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

Jika Anda ingin kunci yang dibuat secara otomatis, atur kunci baru ke Tidak ada.

Lebih lanjut tentang PEMBARUAN / MASUKKAN di sini .

Dokumen resmi tentang penyalinan contoh model: https://docs.djangoproject.com/en/2.2/topics/db/queries/#copying-model-instances

miah
sumber
2
Perlu dicatat bahwa ini mengutip Django 1.2, kita sekarang sampai Django 1.4. Belum pernah diuji apakah ini berfungsi atau tidak, tetapi jangan gunakan jawaban ini tanpa yakin itu bekerja untuk Anda.
Joe
7
Bekerja dengan baik di 1.4.1 Ini mungkin salah satu hal yang akan terus bekerja untuk waktu yang lama.
frnhr
8
Saya harus mengatur keduanya obj.pkdan obj.idmembuat ini bekerja di Django 1.4
Petr Peller
3
@PetrPeller - dokumen menyarankan itu karena Anda menggunakan warisan model.
Dominic Rodger
12
Catatan: hal-hal mungkin sedikit lebih rumit jika ada kunci asing, satu2 orang dan m2m yang terlibat (yaitu, mungkin ada skenario "salinan dalam" yang lebih kompleks)
Ben Roberts
135

Dokumentasi Django untuk permintaan basis data mencakup bagian tentang menyalin model instance . Dengan asumsi kunci primer Anda dibuat secara otomatis, Anda mendapatkan objek yang ingin Anda salin, atur kunci utama None, dan simpan objek itu lagi:

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

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

Dalam cuplikan ini, yang pertama save()membuat objek asli, dan yang kedua save()membuat salinan.

Jika Anda terus membaca dokumentasi, ada juga contoh tentang bagaimana menangani dua kasus yang lebih kompleks: (1) menyalin objek yang merupakan turunan dari model subkelas, dan (2) juga menyalin objek terkait, termasuk objek dalam banyak -banyak hubungan.


Catatan tentang jawaban miah: Mengatur pk ke Nonedisebutkan dalam jawaban miah, meskipun itu tidak disajikan di depan dan tengah. Jadi jawaban saya terutama untuk menekankan metode itu sebagai cara yang direkomendasikan Django untuk melakukannya.

Catatan sejarah: Ini tidak dijelaskan dalam dokumen Django sampai versi 1.4. Itu sudah mungkin sejak sebelum 1.4.

Kemungkinan fungsionalitas di masa mendatang: Perubahan dokumen yang disebutkan di atas dibuat dalam tiket ini . Di utas komentar tiket, ada juga beberapa diskusi tentang menambahkan copyfungsi bawaan untuk kelas model, tetapi sejauh yang saya tahu mereka memutuskan untuk tidak mengatasi masalah itu. Jadi cara menyalin "manual" ini mungkin harus dilakukan untuk saat ini.

S. Kirby
sumber
46

Hati-hati di sini. Ini bisa sangat mahal jika Anda berada dalam satu lingkaran dan Anda mengambil objek satu per satu. Jika Anda tidak ingin panggilan ke database, lakukan saja:

from copy import deepcopy

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

Itu melakukan hal yang sama dengan beberapa jawaban lainnya, tetapi tidak membuat panggilan database untuk mengambil suatu objek. Ini juga berguna jika Anda ingin membuat salinan objek yang belum ada di database.

Troy Grosfield
sumber
1
Ini bekerja sangat baik jika Anda memiliki objek, Anda bisa menyalin objek asli sebelum melakukan perubahan membuat perubahan pada objek baru dan menyimpannya. Kemudian Anda dapat melakukan pengecekan kondisi dan tergantung pada apakah mereka lulus, yaitu objek berada di tabel lain yang Anda periksa, Anda dapat mengatur new_instance.id = original_instance.id dan menyimpan :) Terima kasih!
radtek
2
Ini tidak berfungsi jika model memiliki beberapa tingkat warisan.
David Cheung
1
pada kasus saya, saya ingin membuat metode kloning untuk model, yang akan menggunakan variabel "mandiri" dan saya tidak bisa begitu saja mengatur self.pk Tidak ada, jadi solusi ini bekerja seperti pesona. Saya memikirkan solusi model_to_dict di bawah ini, tetapi membutuhkan langkah ekstra dan akan memiliki masalah yang sama dengan hubungan through, yang saya harus berurusan secara manual pula sehingga tidak memiliki dampak besar bagi saya.
Anderson Santos
32

Gunakan kode di bawah ini:

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
sumber
8
model_to_dictmengambil excludeparameter, yang berarti Anda tidak perlu terpisah pop:model_to_dict(instance, exclude=['id'])
georgebrock
20

Ada potongan klon di sini , yang dapat Anda tambahkan ke model Anda yang melakukan ini:

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
sumber
@ user426975 - ah, oh well (saya sudah menghapusnya dari jawaban saya).
Dominic Rodger
Tidak yakin apakah ini versi hal Django, tetapi ifsekarang perlu if fld.name != old._meta.pk.name, yaitu, nameproperti _meta.pkinstance.
Chris
20

Cara melakukan ini ditambahkan ke dokumen resmi Django di Django1.4

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

Jawaban resmi mirip dengan jawaban miah, tetapi dokumen menunjukkan beberapa kesulitan dengan warisan dan objek terkait, jadi Anda mungkin harus memastikan Anda membaca dokumen.

Michael Bylstra
sumber
ketika Anda membuka tautan dikatakan halaman tidak ditemukan
Amrit
Dokumen tidak ada lagi untuk Django 1.4. Saya akan memperbarui jawaban untuk menunjuk ke dokumen terbaru.
Michael Bylstra
1
@MichaelBylstra Cara yang baik untuk memiliki tautan hijau adalah dengan menggunakan stablealih-alih nomor versi di URL, seperti ini: docs.djangoproject.com/en/stable/topics/db/queries/…
Flimm
8

Saya telah bertemu dengan beberapa gotcha dengan jawaban yang diterima. Ini solusinya.

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

Catatan: ini menggunakan solusi yang tidak disetujui secara resmi di dokumen Django, dan mereka mungkin berhenti bekerja di versi mendatang. Saya menguji ini di 1.9.13.

Peningkatan pertama adalah memungkinkan Anda untuk terus menggunakan contoh asli, dengan menggunakan copy.copy. Bahkan jika Anda tidak berniat untuk menggunakan kembali instance, itu bisa lebih aman untuk melakukan langkah ini jika instance yang Anda kloning dilewatkan sebagai argumen untuk suatu fungsi. Jika tidak, penelepon secara tak terduga akan memiliki instance yang berbeda ketika fungsi kembali.

copy.copytampaknya menghasilkan salinan dangkal contoh model Django dengan cara yang diinginkan. Ini adalah salah satu hal yang saya tidak menemukan didokumentasikan, tetapi bekerja dengan pengawetan dan pembongkaran, sehingga mungkin didukung dengan baik.

Kedua, jawaban yang disetujui akan meninggalkan hasil prefetch yang melekat pada instance baru. Hasil-hasil itu tidak boleh dikaitkan dengan instance baru, kecuali jika Anda secara eksplisit menyalin ke-banyak hubungan. Jika Anda melintasi hubungan yang diambil sebelumnya, Anda akan mendapatkan hasil yang tidak cocok dengan database. Melanggar kode yang berfungsi saat Anda menambahkan prefetch bisa menjadi kejutan yang tidak menyenangkan.

Menghapus _prefetched_objects_cacheadalah cara cepat dan kotor untuk menghapus semua prefetch. Akses ke-banyak berikutnya berfungsi seolah-olah tidak pernah ada prefetch. Menggunakan properti tidak berdokumen yang dimulai dengan garis bawah mungkin meminta masalah kompatibilitas, tetapi berfungsi untuk saat ini.

Bintang fajar
sumber
Saya bisa membuatnya berfungsi, tetapi sepertinya itu mungkin sudah berubah di 1.11, karena saya memiliki properti yang dipanggil _[model_name]_cache, yang, setelah dihapus, saya dapat menetapkan ID baru untuk model terkait itu, lalu menelepon save(). Mungkin masih ada efek samping yang belum saya tentukan.
trpt4him
Ini adalah info yang sangat penting jika Anda melakukan kloning dalam suatu fungsi di kelas / mixin, karena itu akan mengacaukan 'diri' dan Anda akan bingung.
Andreas Bergström
5

pengaturan pk ke Tidak ada yang lebih baik, sinse Django dapat dengan benar membuat pk untuk Anda

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

Ini adalah cara lain untuk mengkloning instance model:

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

Untuk mengkloning model dengan beberapa tingkat warisan, yaitu> = 2, atau ModelC di bawah ini

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)

Silakan merujuk pertanyaan di sini .

David Cheung
sumber
Ah ya, tapi pertanyaan itu tidak memiliki jawaban yang diterima! Jalan untuk pergi!
Bobort
0

Coba ini

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