Как мне клонировать объект экземпляра модели Django и сохранить его в базе данных? - PullRequest
228 голосов
/ 19 января 2011
Foo.objects.get(pk="foo")
<Foo: test>

В базе данных я хочу добавить еще один объект, который является копией объекта выше.

Предположим, в моей таблице одна строка.Я хочу вставить объект первой строки в другую строку с другим первичным ключом.Как я могу это сделать?

Ответы [ 11 ]

384 голосов
/ 19 января 2011

Просто измените первичный ключ вашего объекта и запустите save ().

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

Если вы хотите автоматически сгенерированный ключ, установите новый ключ на None.

Подробнее об UPDATE/ INSERT здесь .

124 голосов
/ 13 апреля 2012

Документация 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 для классов моделей, но, насколько я знаю, они решили пока не решать эту проблему.Таким образом, этот «ручной» способ копирования, вероятно, придется сделать пока.

41 голосов
/ 24 мая 2013

Будь осторожен здесь.Это может быть очень дорого, если вы находитесь в каком-то цикле и извлекаете объекты один за другим.Если вы не хотите вызывать базу данных, просто выполните:

from copy import deepcopy

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

Он делает то же самое, что и некоторые из этих других ответов, но не выполняет вызов базы данных для извлечения объекта.Это также полезно, если вы хотите сделать копию объекта, который еще не существует в базе данных.

23 голосов
/ 16 июня 2014

Используйте следующий код:

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)
20 голосов
/ 19 января 2011

Здесь есть фрагмент клона здесь , который вы можете добавить к своей модели, которая делает это:

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)
19 голосов
/ 17 октября 2012

Как это было добавлено в официальные документы Django в Django1.4

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

Официальный ответ аналогичен ответу miah, но документы указывают на некоторые трудности с наследованием и связанными объектами, поэтому вам, вероятно, следует обязательно прочитать документы.

4 голосов
/ 12 сентября 2014

установка pk на None лучше, sinse Django может правильно создать pk для вас

object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()
3 голосов
/ 27 апреля 2018

Я столкнулся с парой ошибок с принятым ответом. Вот мое решение.

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 - это быстрый и грязный способ удалить все предварительные выборки. Последующие обращения к множеству обращений работают так, как будто предварительной выборки никогда не было. Использование недокументированного свойства, начинающегося со знака подчеркивания, вероятно, вызывает проблему совместимости, но пока работает.

0 голосов
/ 10 февраля 2019

Это еще один способ клонирования экземпляра модели:

d = Foo.objects.filter(pk=1).values().first()   
d.update({'id': None})
duplicate = Foo.objects.create(**d)
0 голосов
/ 22 декабря 2016

Попробуйте это

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