ฉันจะโคลนวัตถุตัวอย่างของรุ่น Django และบันทึกลงในฐานข้อมูลได้อย่างไร


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

ในฐานข้อมูลฉันต้องการเพิ่มวัตถุอื่นซึ่งเป็นสำเนาของวัตถุด้านบน

สมมติว่าตารางของฉันมีหนึ่งแถว ฉันต้องการแทรกวัตถุแถวแรกในแถวอื่นด้วยคีย์หลักที่แตกต่างกัน ฉันจะทำสิ่งนั้นได้อย่างไร

คำตอบ:


438

เพียงเปลี่ยนคีย์หลักของวัตถุของคุณแล้วเรียกใช้ save ()

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

หากคุณต้องการคีย์ที่สร้างขึ้นอัตโนมัติตั้งค่าคีย์ใหม่เป็นไม่มี

เพิ่มเติมเกี่ยวกับการปรับปรุง / INSERT ที่นี่

เอกสารอย่างเป็นทางการเกี่ยวกับการคัดลอกตัวอย่างแบบจำลอง: https://docs.djangoproject.com/en/2.2/topics/db/queries/#copying-model-instances


2
เร็ว ๆ นี้ที่อ้างคำว่า Django 1.2 ตอนนี้เราขึ้นอยู่กับ Django 1.4 แล้ว ยังไม่ได้ทดสอบว่าใช้งานได้หรือไม่ แต่อย่าใช้คำตอบนี้โดยไม่แน่ใจว่าเหมาะกับคุณหรือไม่
Joe

7
ทำงานได้ดีใน 1.4.1 นี่อาจเป็นหนึ่งในสิ่งเหล่านั้นที่จะยังคงทำงานต่อไปอีกนาน
frnhr

8
ฉันต้องตั้งค่าทั้งสองobj.pkและobj.idเพื่อให้งานนี้ใน Django 1.4
Petr Peller

3
@PetrPeller - เอกสารแนะนำว่าเป็นเพราะคุณใช้การสืบทอดรุ่น
Dominic Rodger

12
หมายเหตุ: สิ่งต่าง ๆ อาจจะซับซ้อนกว่านี้เล็กน้อยหากมีกุญแจต่างประเทศที่เกี่ยวข้องหนึ่งเดียวและ m2m ของที่เกี่ยวข้อง (เช่นอาจมีสถานการณ์ที่ซับซ้อนมากขึ้น "สำเนาลึก")
Ben Roberts

135

เอกสาร Django สำหรับการสืบค้นฐานข้อมูลรวมถึงส่วนที่เกี่ยวกับการคัดลอกรูปแบบอินสแตนซ์ สมมติว่าคีย์หลักของคุณถูกสร้างอัตโนมัติคุณจะได้รับวัตถุที่คุณต้องการคัดลอกตั้งค่าคีย์หลักเป็นNoneและบันทึกวัตถุอีกครั้ง:

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

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

ในตัวอย่างนี้save()วัตถุแรกสร้างวัตถุต้นฉบับและชิ้นที่สองsave()สร้างสำเนา

หากคุณอ่านเอกสารต่อไปจะมีตัวอย่างเกี่ยวกับวิธีจัดการกับกรณีที่ซับซ้อนสองกรณี: (1) การคัดลอกวัตถุซึ่งเป็นตัวอย่างของคลาสย่อยโมเดลและ (2) คัดลอกวัตถุที่เกี่ยวข้องรวมถึงวัตถุในหลาย ๆ - ความสัมพันธ์มากมาย


หมายเหตุเกี่ยวกับคำตอบของ miah: การตั้งค่า pk จะNoneถูกกล่าวถึงในคำตอบของ miah แม้ว่าจะไม่ได้นำเสนอด้านหน้าและกึ่งกลาง ดังนั้นคำตอบของฉันส่วนใหญ่จะเน้นย้ำวิธีการนั้นเป็นวิธีที่ Django แนะนำให้ทำ

ประวัติบันทึกย่อ: สิ่งนี้ไม่ได้อธิบายไว้ในเอกสาร Django จนถึงรุ่น 1.4 มันเป็นไปได้ตั้งแต่ก่อน 1.4 แม้ว่า

ฟังก์ชั่นที่เป็นไปได้ในอนาคต: การเปลี่ยนแปลงเอกสารดังกล่าวเกิดขึ้นในตั๋วนี้ ในกระทู้ความคิดเห็นของตั๋วมีการหารือเกี่ยวกับการเพิ่มcopyฟังก์ชั่นในตัวสำหรับคลาสรุ่น แต่เท่าที่ฉันรู้ว่าพวกเขาตัดสินใจที่จะไม่แก้ไขปัญหานั้น ดังนั้นวิธีการทำสำเนาด้วยตนเองนี้อาจจะต้องทำตอนนี้


46

ระวังที่นี่ สิ่งนี้อาจมีราคาแพงมากหากคุณอยู่ในวงวนบางชนิดและคุณรับวัตถุทีละตัว หากคุณไม่ต้องการให้โทรไปยังฐานข้อมูลให้ทำดังนี้

from copy import deepcopy

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

มันทำสิ่งเดียวกันกับคำตอบอื่น ๆ บางส่วน แต่ไม่ได้เรียกฐานข้อมูลเพื่อเรียกวัตถุ สิ่งนี้มีประโยชน์หากคุณต้องการทำสำเนาของวัตถุที่ยังไม่มีอยู่ในฐานข้อมูล


1
วิธีนี้ใช้งานได้ดีถ้าคุณมีวัตถุคุณสามารถคัดลอกวัตถุต้นฉบับได้ลึกก่อนทำการเปลี่ยนแปลงทำการเปลี่ยนแปลงวัตถุใหม่และบันทึก จากนั้นคุณสามารถทำการตรวจสอบเงื่อนไขบางอย่างและขึ้นอยู่กับว่าพวกเขาผ่านเช่นวัตถุอยู่ในตารางอื่นที่คุณกำลังตรวจสอบคุณสามารถตั้งค่า new_instance.id = original_instance.id และบันทึก :) ขอบคุณ!
radtek

2
สิ่งนี้ไม่ทำงานหากแบบจำลองมีหลายระดับการสืบทอด
David Cheung

1
ในกรณีของฉันฉันต้องการสร้างวิธีการโคลนสำหรับแบบจำลองซึ่งจะใช้ตัวแปร "ตัวเอง" และฉันก็ไม่สามารถตั้งค่าตัวเองได้ pk None ดังนั้นวิธีนี้จึงทำงานได้อย่างมีเสน่ห์ ฉันคิดเกี่ยวกับโซลูชัน model_to_dict ด้านล่าง แต่ต้องใช้ขั้นตอนเพิ่มเติมและมันจะมีปัญหาเดียวกันกับความสัมพันธ์แบบผ่านซึ่งฉันต้องจัดการด้วยตนเองอยู่แล้วดังนั้นจึงไม่มีผลกระทบที่สำคัญสำหรับฉัน
Anderson Santos

32

ใช้รหัสด้านล่าง:

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_dictรับexcludeพารามิเตอร์ซึ่งหมายความว่าคุณไม่จำเป็นต้องแยกจากpopกัน:model_to_dict(instance, exclude=['id'])
georgebrock

20

มีตัวอย่างโค้ดที่นี่ซึ่งคุณสามารถเพิ่มลงในโมเดลของคุณซึ่งทำสิ่งนี้:

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 - อ่าดี (ฉันลบมันออกจากคำตอบของฉัน)
Dominic Rodger

ไม่แน่ใจว่านี่เป็นรุ่น Django หรือไม่ แต่ifตอนนี้ต้องเป็นif fld.name != old._meta.pk.nameเช่นnameคุณสมบัติของ_meta.pkอินสแตนซ์
Chris

20

วิธีการทำเช่นนี้ถูกบันทึกอยู่ในเอกสารทางการของ Django ใน Django1.4

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

คำตอบอย่างเป็นทางการคล้ายกับคำตอบของ miah แต่เอกสารชี้ให้เห็นปัญหาบางอย่างเกี่ยวกับการสืบทอดและวัตถุที่เกี่ยวข้องดังนั้นคุณควรแน่ใจว่าคุณอ่านเอกสาร


เมื่อคุณเปิดลิงก์จะมีข้อความระบุว่าไม่พบหน้าเว็บ
Amrit

ไม่มีเอกสารสำหรับ Django 1.4 อีกต่อไป ฉันจะอัปเดตคำตอบให้ชี้ไปที่เอกสารล่าสุด
Michael Bylstra

1
@MichaelBylstra วิธีที่ดีที่จะมีการเชื่อมโยงป่าดิบคือการใช้stableแทนหมายเลขรุ่นใน URL เช่นนี้docs.djangoproject.com/en/stable/topics/db/queries/...
Flimm

8

ฉันเจอคู่ gotchas กับคำตอบที่ยอมรับได้ นี่คือทางออกของฉัน

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

หมายเหตุ: วิธีนี้ใช้โซลูชันที่ไม่ได้รับอนุญาตอย่างเป็นทางการในเอกสาร Django และอาจหยุดทำงานในเวอร์ชันต่อไป ฉันทดสอบสิ่งนี้ใน 1.9.13

copy.copyการปรับปรุงที่แรกก็คือว่ามันช่วยให้คุณเพื่อดำเนินการต่อโดยใช้อินสแตนซ์เดิมโดยใช้ แม้ว่าคุณจะไม่ต้องการนำอินสแตนซ์นั้นมาใช้ซ้ำอีกครั้งคุณสามารถทำขั้นตอนนี้ได้อย่างปลอดภัยยิ่งขึ้นหากอินสแตนซ์ที่คุณโคลนถูกส่งผ่านเป็นอาร์กิวเมนต์ของฟังก์ชัน มิฉะนั้นผู้เรียกจะมีอินสแตนซ์อื่นโดยไม่คาดคิดเมื่อฟังก์ชันส่งคืน

copy.copyดูเหมือนว่าจะสร้างสำเนาของรุ่นตัวอย่าง Django ตื้น ๆ ในแบบที่ต้องการ นี่คือหนึ่งในสิ่งที่ฉันไม่พบเอกสาร แต่มันทำงานได้โดยการดองและไม่ได้ปักหลักดังนั้นมันอาจได้รับการสนับสนุนอย่างดี

ประการที่สองคำตอบที่ได้รับการอนุมัติจะออกจากผลการดึงข้อมูลล่วงหน้าใด ๆ ที่แนบมากับอินสแตนซ์ใหม่ ผลลัพธ์เหล่านั้นไม่ควรเชื่อมโยงกับอินสแตนซ์ใหม่เว้นแต่คุณจะคัดลอกความสัมพันธ์ไปยังหลาย ๆ อย่างชัดเจน หากคุณสำรวจความสัมพันธ์ที่ดึงมาล่วงหน้าคุณจะได้รับผลลัพธ์ที่ไม่ตรงกับฐานข้อมูล การทำลายรหัสการทำงานเมื่อคุณเพิ่มการดึงข้อมูลล่วงหน้าอาจเป็นสิ่งที่น่าประหลาดใจ

ลบ _prefetched_objects_cacheเป็นวิธีที่รวดเร็วและสกปรกในการแยกไฟล์ prefetch ทั้งหมดออก การเข้าถึงหลายครั้งที่ตามมาจะทำงานราวกับว่าไม่มีการดึงข้อมูลล่วงหน้า การใช้คุณสมบัติที่ไม่มีเอกสารที่ขึ้นต้นด้วยเครื่องหมายขีดล่างอาจเป็นการถามถึงปัญหาความเข้ากันได้ แต่มันได้ผลในตอนนี้


ผมสามารถที่จะได้รับนี้ในการทำงาน แต่ดูเหมือนว่ามันอาจจะมีการเปลี่ยนแปลงอยู่แล้วใน 1.11 ในขณะที่ผมมีคุณสมบัติที่เรียกว่า_[model_name]_cacheซึ่งเมื่อลบแล้วฉันก็สามารถที่จะกำหนด ID save()ใหม่สำหรับรูปแบบที่เกี่ยวข้องที่โทรแล้ว อาจมีผลข้างเคียงที่ฉันยังไม่ได้ตัดสินใจ
trpt4him

นี่เป็นข้อมูลที่สำคัญอย่างยิ่งหากคุณทำการโคลนนิ่งในฟังก์ชั่นในคลาส / มิกซ์อินเพราะมันจะทำให้ตัวเองสับสนและคุณจะสับสน
Andreas Bergström

5

การตั้งค่า pk เป็นไม่มีดีกว่า sinse Django สามารถสร้าง pk ให้คุณได้อย่างถูกต้อง

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

3

นี่เป็นอีกวิธีในการโคลนอินสแตนซ์โมเดล:

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

0

หากต้องการโคลนโมเดลที่มีหลายระดับการสืบทอดเช่น> = 2 หรือ 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)

โปรดดูคำถามที่นี่


อ่าใช่ แต่คำถามนั้นไม่มีคำตอบที่ยอมรับ! ทางที่จะไป!
Bobort

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.