Как клонировать объект экземпляра модели Django и сохранить его в базе данных?

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

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

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

10 ответов


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

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

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

подробнее об обновлении / вставке здесь.


документация 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: установка ПК в None упоминается в ответе miah, хотя он не представлен спереди и в центре. Поэтому мой ответ в основном служит для того, чтобы подчеркнуть этот метод как рекомендуемый Django способ сделать это.

историческое Примечание: это не было объяснено в документах Django до версия 1.4. Однако это было возможно еще до 1.4.

возможная будущая функциональность: вышеупомянутое изменение документов было сделано в этот билет. В потоке комментариев билета также обсуждалось добавление встроенного copy функция для классов моделей, но, насколько я знаю, они решили пока не решать эту проблему. Таким образом, этот "ручной" способ копирования, вероятно, придется сделать сейчас.


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

from copy import deepcopy

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

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


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

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

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


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

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)

использовать ниже код :

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)

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

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

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

во-вторых, утвержденный ответ оставит любые предварительные результаты, прикрепленные к новому экземпляру. Эти результаты не должны быть связанным с новым экземпляром, если вы явно не копируете отношения to-many. Если вы пройдете через предварительно выбранные отношения, вы получите результаты, которые не соответствуют базе данных. Нарушение рабочего кода при добавлении предварительной выборки может стать неприятным сюрпризом.

удаление _prefetched_objects_cache это быстрый и грязный способ удалить все префектуры. После-многие обращения работают так, как будто никогда не было предварительной выборки. Использование недокументированного свойства, начинающегося с подчеркивания, вероятно прошу проблем с совместимостью, но пока это работает.


клонировать модель с несколькими уровнями наследования, т. е. >= 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)

см. вопрос здесь.


попробуй такое

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