Bir Django model örneği nesnesini klonlayıp veritabanına nasıl kaydederim?


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

Veritabanında, yukarıdaki nesnenin bir kopyası olan başka bir nesne eklemek istiyorum.

Diyelim ki masamın bir satırı var. İlk satır nesnesini farklı bir birincil anahtarla başka bir satıra eklemek istiyorum. Bunu nasıl yapabilirim?

Yanıtlar:


438

Sadece nesnenizin birincil anahtarını değiştirin ve save () komutunu çalıştırın.

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

Otomatik oluşturulan anahtar istiyorsanız, yeni anahtarı Yok olarak ayarlayın.

UPDATE / INSERT hakkında daha fazla bilgi burada .

Model örneklerinin kopyalanması ile ilgili resmi dokümanlar: https://docs.djangoproject.com/en/2.2/topics/db/queries/#copying-model-instances


2
Bunun Django 1.2'den alıntı yaptığını belirtmekle birlikte, şimdi Django 1.4'e geldik. Bunun işe yarayıp yaramadığını test etmediniz, ancak bu yanıtı sizin için çalıştığından emin olmadan kullanmayın.
Joe

7
1.4.1'de iyi çalışıyor Bu muhtemelen uzun süre çalışmaya devam edecek şeylerden biri.
frnhr

8
İkisini de ayarlamak obj.pkve obj.idbu işi Django 1.4'te yapmak zorunda kaldım
Petr Peller

3
@PetrPeller - dokümanlar bunun nedeni model mirasını kullandığınızdır.
Dominic Rodger

12
Not: yabancı anahtarlar, one2one ve m2m'ler varsa işler biraz daha karmaşık olabilir (yani, daha karmaşık "derin kopya" senaryoları olabilir)
Ben Roberts

135

Veritabanı sorguları için Django belgeleri, model örneklerini kopyalama hakkında bir bölüm içerir . Birincil anahtarlarınızın otomatik olarak oluşturulduğunu varsayarsak, kopyalamak istediğiniz nesneyi alırsınız, birincil anahtarı olarak ayarlayın Noneve nesneyi tekrar kaydedin:

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

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

Bu snippet'te birincisi save()orijinal nesneyi, ikincisi save()de kopyayı oluşturur.

Belgeleri okumaya devam ederseniz, iki daha karmaşık vakanın nasıl ele alınacağına dair örnekler de vardır: (1) bir model alt sınıfının örneği olan bir nesneyi kopyalama ve (2) ayrıca çok amaçlı nesneler de dahil olmak üzere ilgili nesneleri kopyalama -çeşitli ilişkiler.


Miah'ın cevabı hakkında not: NoneÖn ve ortada sunulmasa da, pk'yi miah'ın cevabında belirtmek gerekir. Bu yüzden cevabım esas olarak bu yöntemi Django tarafından önerilen bir yöntem olarak vurgulamaya hizmet ediyor.

Tarihsel not: 1.4 sürümüne kadar bu Django belgelerinde açıklanmadı. Yine de 1.4'ten beri mümkün.

Gelecekteki olası işlevsellik: Yukarıda belirtilen doküman değişikliği bu bilette yapıldı . Biletin yorum dizisinde, copymodel sınıfları için yerleşik bir işlev eklemeyle ilgili bazı tartışmalar da vardı , ancak bildiğim kadarıyla bu sorunu çözmemeye karar verdiler. Bu nedenle, bu "manuel" kopyalama yolunun şimdilik yapması gerekecek.


46

Burada dikkatli ol. Bir tür döngüde bulunuyorsanız ve nesneleri teker teker alıyorsanız, bu çok pahalı olabilir. Veritabanına çağrı yapılmasını istemiyorsanız şunları yapın:

from copy import deepcopy

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

Bu diğer cevapların bazılarıyla aynı şeyi yapar, ancak bir nesneyi almak için veritabanı çağrısı yapmaz. Bu, henüz veritabanında bulunmayan bir nesnenin kopyasını oluşturmak istiyorsanız da kullanışlıdır.


1
Bu, bir nesneniz varsa harika çalışır, değişiklik yapmadan önce yeni nesneyi derinlemesine kopyalayabilir ve yeni nesnede değişiklikler yapabilir ve kaydedebilirsiniz. Daha sonra bazı durum kontrolleri yapabilir ve geçip geçmediklerine bağlı olarak, yani nesne kontrol ettiğiniz başka bir tablodaysa, new_instance.id = original_instance.id ayarlayabilir ve kaydedebilirsiniz :) Teşekkürler!
radtek

2
Modelde birden fazla kalıtım düzeyi varsa bu çalışmaz.
David Cheung

1
Benim durumumda model için "self" değişkenini kullanacak bir klon yöntemi oluşturmak istedim ve ben sadece self.pk'yi ayarlayamıyorum None, bu yüzden bu çözüm bir cazibe gibi çalıştı. Aşağıdaki model_to_dict çözümü hakkında düşündüm, ancak ekstra bir adım gerektirir ve benim için önemli bir etkisi yoktur, bu yüzden yine de elle uğraşmak zorunda aracılığıyla ilişkiler ile aynı sorunu olurdu.
Anderson Santos

32

Aşağıdaki kodu kullanın:

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_dictbir excludeparametre alır , yani ayrı ihtiyacınız yoktur pop:model_to_dict(instance, exclude=['id'])
georgebrock

20

Burada , bunu yapan modelinize ekleyebileceğiniz bir klon snippet'i var :

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, oh iyi (cevabımdan kaldırdım).
Dominic Rodger

Bu bir Django sürüm şey olup olmadığından emin değilim, ancak ifşimdi if fld.name != old._meta.pk.nameörneğin nameözelliğinin olması gerekir _meta.pk.
Chris

20

Bunun nasıl yapılacağı Django'daki resmi Django belgelerine eklendi.

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

Resmi cevap miah'ın cevabına benzer, ancak dokümanlar miras ve ilgili nesnelerle ilgili bazı zorluklara işaret ediyor, bu yüzden muhtemelen dokümanları okuduğunuzdan emin olmalısınız.


bağlantıyı açtığınızda sayfa bulunamadı diyor
Amrit

Dokümanlar artık Django 1.4 için mevcut değil. Cevabı en son dokümanları gösterecek şekilde güncelleyeceğim.
Michael Bylstra

1
@MichaelBylstra URL'deki stablesürüm numarası yerine her zaman yeşil bağlantılara sahip olmanın iyi bir yolu şöyledir
Flimm

8

Kabul edilen cevapla bir kaç tane karşılaştım. İşte benim çözümüm.

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

Not: Bu, Django belgelerinde resmi olarak onaylanmayan çözümleri kullanır ve gelecekteki sürümlerde çalışmayı durdurabilir. Bunu 1.9.13'te test ettim.

İlk gelişme, kullanarak orijinal örneği kullanmaya devam etmenize izin vermesidir copy.copy. Örneği yeniden kullanmak istemeseniz bile, klonladığınız örnek bir işleve argüman olarak iletildiyse bu adımı uygulamak daha güvenli olabilir. Değilse, işlev döndüğünde arayanın beklenmedik şekilde farklı bir örneği olur.

copy.copyDjango model örneğinin sığ bir kopyasını istenen şekilde üretiyor gibi görünüyor. Bu, belgelenmiş bulamadığım şeylerden biri, ancak dekapaj ve unpickling ile çalışıyor, bu yüzden muhtemelen iyi destekleniyor.

İkinci olarak, onaylanan cevap yeni örneğe önceden getirilmiş sonuçları bırakacaktır. Çok sayıda ilişkiyi açıkça kopyalamadığınız sürece bu sonuçlar yeni örnekle ilişkilendirilmemelidir. Önceden getirilmiş ilişkiler arasında geçiş yaparsanız, veritabanıyla eşleşmeyen sonuçlar alırsınız. Bir ön getirme eklediğinizde çalışma kodunu kırmak kötü bir sürpriz olabilir.

Silme _prefetched_objects_cache, tüm ön getirmeleri ortadan kaldırmanın hızlı ve kirli bir yoludur. Birçok erişimin ardından, önceden getirme yokmuş gibi çalışır. Alt çizgiyle başlayan belgelenmemiş bir özellik kullanmak, muhtemelen uyumluluk sorunu ister, ancak şimdilik çalışıyor.


Bunu çalıştırabildim, ancak 1.11'de zaten değişmiş gibi görünüyor, çünkü bir _[model_name]_cachekez silindim, ilgili model için yeni bir kimlik atayabiliyorum, sonra aradım save(). Henüz belirlemediğim yan etkiler olabilir.
trpt4him

Sınıf / mixin üzerinde bir işlevde klonlama yapıyorsanız, bu son derece önemli bir bilgidir, aksi takdirde 'kendini' mahveder ve kafanız karışır.
Andreas Bergström

5

pk'yi None olarak ayarlamak daha iyidir, sinse Django sizin için doğru bir pk oluşturabilir

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

3

Bu, model örneğini klonlamanın başka bir yoludur:

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

0

Birden fazla kalıtım düzeyi olan bir modeli klonlamak için, yani> = 2 veya aşağıda ModelC

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)

Sorusunu bakın burada .


Ah evet, ama bu sorunun kabul edilmiş bir cevabı yok! Gitme zamanı!
Bobort

0

Bunu dene

original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.