Làm cách nào để sao chép một đối tượng mô hình Django và lưu nó vào cơ sở dữ liệu?


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

Trong cơ sở dữ liệu, tôi muốn thêm một đối tượng khác là bản sao của đối tượng ở trên.

Giả sử bảng của tôi có một hàng. Tôi muốn chèn đối tượng hàng đầu tiên vào một hàng khác với khóa chính khác. Làm thế nào tôi có thể làm điều đó?

Câu trả lời:


438

Chỉ cần thay đổi khóa chính của đối tượng của bạn và chạy save ().

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

Nếu bạn muốn khóa được tạo tự động, hãy đặt khóa mới thành Không có.

Thông tin thêm về CẬP NHẬT / XÁC NHẬN tại đây .

Tài liệu chính thức về sao chép mô hình: https://docs.djangoproject.com/en/2.2/topics/db/queries/#copying-model-instances


2
Đáng chú ý là điều này trích dẫn Django 1.2, giờ chúng tôi đã lên đến Django 1.4. Không được kiểm tra xem điều này có hiệu quả hay không, nhưng đừng sử dụng câu trả lời này mà không chắc chắn rằng nó có hiệu quả với bạn không.
Joe

7
Hoạt động tốt trong 1.4.1 Đây có lẽ là một trong những điều sẽ tiếp tục hoạt động trong một thời gian dài.
frnhr

8
Tôi đã phải thiết lập cả hai obj.pkobj.idđể thực hiện công việc này trong Django 1.4
Petr Peller

3
@PetrPeller - các tài liệu đề xuất rằng vì bạn đang sử dụng kế thừa mô hình.
Đaminh Rodger

12
Lưu ý: mọi thứ có thể phức tạp hơn một chút nếu có khóa ngoại, liên quan đến one2one và m2m (nghĩa là có thể có các kịch bản "sao chép sâu" phức tạp hơn)
Ben Roberts

135

Tài liệu Django cho các truy vấn cơ sở dữ liệu bao gồm một phần về sao chép mô hình . Giả sử các khóa chính của bạn được tự động tạo, bạn có được đối tượng bạn muốn sao chép, đặt khóa chính thành Nonevà lưu lại đối tượng:

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

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

Trong đoạn trích này, cái đầu tiên save()tạo đối tượng ban đầu và cái thứ hai save()tạo bản sao.

Nếu bạn tiếp tục đọc tài liệu, cũng có các ví dụ về cách xử lý hai trường hợp phức tạp hơn: (1) sao chép một đối tượng là một thể hiện của lớp con mô hình và (2) cũng sao chép các đối tượng liên quan, bao gồm các đối tượng trong nhiều quan hệ nhiều người.


Lưu ý về câu trả lời của miah: Đặt pk thành None được đề cập trong câu trả lời của miah, mặc dù nó không được trình bày trước và trung tâm. Vì vậy, câu trả lời của tôi chủ yếu phục vụ để nhấn mạnh phương pháp đó như là cách mà Django khuyên dùng để thực hiện.

Ghi chú lịch sử: Điều này không được giải thích trong các tài liệu Django cho đến phiên bản 1.4. Nó đã có thể kể từ trước 1.4, mặc dù.

Chức năng trong tương lai có thể: Thay đổi tài liệu nói trên đã được thực hiện trong vé này . Về chủ đề bình luận của vé, cũng có một số cuộc thảo luận về việc thêm copychức năng tích hợp cho các lớp mô hình, nhưng theo tôi biết họ đã quyết định không giải quyết vấn đề đó. Vì vậy, cách sao chép "thủ công" này có thể sẽ phải làm ngay bây giờ.


46

Hãy cẩn thận ở đây. Điều này có thể cực kỳ tốn kém nếu bạn đang ở trong một vòng lặp nào đó và bạn đang truy xuất từng đối tượng một. Nếu bạn không muốn gọi đến cơ sở dữ liệu, chỉ cần làm:

from copy import deepcopy

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

Nó thực hiện tương tự như một số câu trả lời khác, nhưng nó không thực hiện cuộc gọi cơ sở dữ liệu để lấy một đối tượng. Điều này cũng hữu ích nếu bạn muốn tạo một bản sao của một đối tượng chưa tồn tại trong cơ sở dữ liệu.


1
Điều này hoạt động rất tốt nếu bạn có một đối tượng, bạn có thể sao chép sâu đối tượng ban đầu trước khi thay đổi thực hiện thay đổi đối tượng mới và lưu nó. Sau đó, bạn có thể thực hiện một số điều kiện kiểm tra và tùy thuộc vào việc chúng có vượt qua hay không, tức là đối tượng nằm trong một bảng khác mà bạn đang kiểm tra, bạn có thể đặt new_instance.id = original_instance.id và lưu :) Cảm ơn!
radtek

2
Điều này không hoạt động nếu mô hình có nhiều mức kế thừa.
David Cheung

1
trong trường hợp của tôi, tôi muốn tạo một phương thức nhân bản cho mô hình, nó sẽ sử dụng biến "tự" và tôi không thể chỉ đơn giản là đặt self.pk Không có, vì vậy giải pháp này hoạt động như một cơ duyên. Tôi đã nghĩ về giải pháp model_to_dict bên dưới, nhưng nó đòi hỏi một bước bổ sung và nó sẽ có cùng một vấn đề với các mối quan hệ thông qua, dù sao tôi cũng phải giải quyết bằng tay nên nó không có tác động lớn đối với tôi.
Anderson Santos

32

Sử dụng mã dưới đây:

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_dictlấy một excludetham số, có nghĩa là bạn không cần riêng pop:model_to_dict(instance, exclude=['id'])
georgebrock 16/07/2015

20

Có một đoạn mã nhân bản ở đây , mà bạn có thể thêm vào mô hình của mình để thực hiện điều này:

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 - à, ồ (tôi đã xóa nó khỏi câu trả lời của tôi).
Đaminh Rodger

Không chắc đây có phải là phiên bản Django không, nhưng ifbây giờ cần phải if fld.name != old._meta.pk.namenametài sản của _meta.pkthể hiện.
Chris

20

Cách thực hiện việc này đã được thêm vào tài liệu Django chính thức trong Django1.4

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

Câu trả lời chính thức tương tự như câu trả lời của miah, nhưng các tài liệu chỉ ra một số khó khăn với tính kế thừa và các đối tượng liên quan, vì vậy bạn có thể nên chắc chắn rằng mình đã đọc tài liệu.


Khi bạn mở liên kết, nó báo không tìm thấy trang
Amrit

Các tài liệu không còn tồn tại cho Django 1.4. Tôi sẽ cập nhật câu trả lời để chỉ đến các tài liệu mới nhất.
Michael Bylstra

1
@MichaelBylstra Một cách tốt để có các liên kết thường xanh là sử dụng stablethay vì số phiên bản trong URL, như thế này: docs.djangoproject.com/en/ sóng / topics / db / query / khăn
Flimm

8

Tôi đã gặp một vài vấn đề với câu trả lời được chấp nhận. Đây là giải pháp của tôi.

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

Lưu ý: điều này sử dụng các giải pháp không được xử phạt chính thức trong các tài liệu Django và chúng có thể ngừng hoạt động trong các phiên bản trong tương lai. Tôi đã thử nghiệm điều này trong 1.9.13.

Cải tiến đầu tiên là nó cho phép bạn tiếp tục sử dụng thể hiện ban đầu, bằng cách sử dụng copy.copy . Ngay cả khi bạn không có ý định sử dụng lại cá thể, việc thực hiện bước này sẽ an toàn hơn nếu trường hợp bạn nhân bản được truyền dưới dạng đối số cho hàm. Nếu không, người gọi sẽ bất ngờ có một phiên bản khác khi hàm trả về.

copy.copydường như tạo ra một bản sao nông của một mô hình Django theo cách mong muốn. Đây là một trong những điều tôi không tìm thấy tài liệu, nhưng nó hoạt động bằng cách tẩy và tháo, vì vậy nó có thể được hỗ trợ tốt.

Thứ hai, câu trả lời được phê duyệt sẽ để lại bất kỳ kết quả tìm nạp nào được đính kèm với trường hợp mới. Những kết quả đó không nên được liên kết với trường hợp mới, trừ khi bạn sao chép rõ ràng các mối quan hệ với nhiều người. Nếu bạn duyệt qua các mối quan hệ được tìm nạp trước, bạn sẽ nhận được kết quả không khớp với cơ sở dữ liệu. Phá vỡ mã làm việc khi bạn thêm một prefetch có thể là một bất ngờ khó chịu.

Xóa _prefetched_objects_cachelà một cách nhanh chóng và bẩn để loại bỏ tất cả các tìm nạp trước. Sau đó, nhiều truy cập hoạt động như thể không bao giờ có một lượt tải trước. Sử dụng một tài sản không có giấy tờ bắt đầu bằng dấu gạch dưới có thể yêu cầu sự cố tương thích, nhưng hiện tại nó hoạt động.


Tôi đã có thể làm cho nó hoạt động, nhưng có vẻ như nó có thể đã thay đổi trong 1.11, vì tôi có một thuộc tính được gọi _[model_name]_cache, sau khi xóa, tôi có thể gán ID mới cho mô hình liên quan đó, sau đó gọi save(). Vẫn có thể có tác dụng phụ tôi chưa xác định được.
trpt4him

Đây là thông tin cực kỳ quan trọng nếu bạn đang thực hiện nhân bản trong một chức năng trên lớp / mixin, vì nếu không nó sẽ làm rối tung 'bản thân' và bạn sẽ bị lẫn lộn.
Andreas Bergström

5

Đặt pk thành Không có gì tốt hơn, Django sinse có thể tạo pk chính xác cho bạn

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

3

Đây là một cách khác để nhân bản thể hiện mô hình:

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

0

Để sao chép một mô hình có nhiều mức kế thừa, tức là> = 2 hoặc ModelC bên dưới

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)

Vui lòng tham khảo câu hỏi ở đây .


À đúng, nhưng câu hỏi đó không có câu trả lời được chấp nhận! Con đường để đi!
Bobort

0

Thử cái này

original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.