Djangoモデルインスタンスオブジェクトを複製してデータベースに保存するにはどうすればよいですか?


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

データベースに、上記のオブジェクトのコピーである別のオブジェクトを追加します。

テーブルに1行あるとします。最初の行オブジェクトを別の主キーを持つ別の行に挿入したい。どうやってやるの?

回答:


438

オブジェクトの主キーを変更して、save()を実行するだけです。

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

自動生成キーが必要な場合は、新しいキーを[なし]に設定します。

UPDATE / 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で正常に動作しますこれはおそらく、長い間動作し続けるものの1つです。
frnhr '28年

8
私は両方を設定し、Django 1.4でこれを機能させる必要がobj.pkありobj.idました
Petr Peller 2014年

3
@PetrPeller- ドキュメントによると、モデルの継承を使用しているためです。
ドミニクロジャー2014年

12
注:one2oneと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()作成し、2番目のオブジェクトがコピーを作成します。

ドキュメントを読み続けると、2つのより複雑なケースを処理する方法の例もあります:(1)モデルサブクラスのインスタンスであるオブジェクトのコピー、および(2)多対多のオブジェクトを含む関連オブジェクトのコピー-多くの関係。


miahの回答に関する注意:pkをに設定することNoneはmiahの回答に記載されていますが、正面と中央には示されていません。だから私の答えは、主にその方法をDjangoが推奨する方法として強調するのに役立ちます。

歴史的注記:これはバージョン1.4までDjangoのドキュメントでは説明されていませんでした。しかし、それは1.4以前から可能でした。

可能な将来の機能:前述のドキュメントの変更は、このチケットで行われました。チケットのコメントスレッドで、copyモデルクラスの組み込み関数を追加することについてもいくつかの議論がありましたが、私が知る限り、彼らはまだその問題に取り組むことはしないことに決めました。したがって、この「手動」のコピー方法はおそらく今のところ必要があります。


46

ここで注意してください。何らかのループにあり、オブジェクトを1つずつ取得している場合、これは非常にコストがかかる可能性があります。データベースへの呼び出しが不要な場合は、次のようにします。

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、2014

2
モデルに複数の継承レベルがある場合、これは機能しません。
David Cheung、

1
私の場合、「self」変数を使用するモデルのクローンメソッドを作成したかったので、単純にself.pk Noneを設定することはできないため、このソリューションは魅力的に機能しました。以下のmodel_to_dictソリューションについて考えましたが、追加の手順が必要であり、スルーリレーションでも同じ問題が発生するため、手動で対処する必要があるため、大きな影響はありません。
アンダーソンサントス

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あなたが別のを必要としない手段、パラメータをpopmodel_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-ああ、まあ(私は私の答えからそれを削除しました)。
ドミニクロジャー

これがDjangoバージョンのものかどうかはわかりませんが、if今はif fld.name != old._meta.pk.name、つまりインスタンスのnameプロパティである必要があり_meta.pkます。
Chris

20

これを行う方法は、Django1.4の公式Djangoドキュメントに追加されました

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

公式の答えはミアの答えに似ていますが、ドキュメントは継承と関連オブジェクトのいくつかの困難を指摘しているので、おそらくドキュメントを必ず読んでください。


あなたがリンクを開くときには、ページが見つからないと言う
Amrit

Django 1.4のドキュメントは存在しません。最新のドキュメントを指すように回答を更新します。
Michael Bylstra 2017年

1
常緑のリンクを持っている@MichaelBylstra A良いの方法が使用することですstable。このように、URLにバージョン番号の代わりに:docs.djangoproject.com/en/stable/topics/db/queries/...
Flimm

8

私は受け入れられた答えでいくつかの落とし穴に遭遇しました。これが私の解決策です。

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.copyDjangoモデルインスタンスの浅いコピーを希望どおりに生成するようです。これはドキュメントに記載されていなかったものの1つですが、酸洗いと酸洗いによって機能するため、おそらく十分にサポートされています。

次に、承認された回答は、プリフェッチされた結果を新しいインスタンスに添付したままにします。多対多の関係を明示的にコピーしない限り、これらの結果を新しいインスタンスに関連付けることはできません。プリフェッチされた関係をトラバースすると、データベースと一致しない結果が得られます。プリフェッチを追加するときに作業コードを壊すのは、意外なことです。

削除_prefetched_objects_cacheは、すべてのプリフェッチを取り除く簡単な方法です。後続の多くのアクセスは、プリフェッチがなかったかのように機能します。アンダースコアで始まる文書化されていないプロパティを使用すると、互換性の問題が発生する可能性がありますが、今のところ機能します。


これを機能させることができましたが、と呼ばれるプロパティがあったため、1.11ですでに変更されている可能性があります_[model_name]_cache。これは、削除されると、その関連モデルに新しいIDを割り当てて、を呼び出すことができましたsave()。まだ決定していない副作用があるかもしれません。
trpt4him

クラス/ミックスインの関数でクローンを作成している場合、これは非常に重要な情報です。そうしないと、「自己」が台無しになり、混乱することになります。
AndreasBergström19年

5

pkをNoneに設定することをお勧めします。sinseDjangoはpkを正しく作成できます

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

3

これは、モデルインスタンスのクローンを作成するもう1つの方法です。

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

0

これを試して

original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.