44

Books、およびのモデルがChaptersありPagesます。それらはすべて a によって書かれていUserます:

from django.db import models

class Book(models.Model)
    author = models.ForeignKey('auth.User')

class Chapter(models.Model)
    author = models.ForeignKey('auth.User')
    book = models.ForeignKey(Book)

class Page(models.Model)
    author = models.ForeignKey('auth.User')
    book = models.ForeignKey(Book)
    chapter = models.ForeignKey(Chapter)

私がやりたいのは、既存のものを複製し、それを他Bookの人に更新することUserです。問題は、関連するすべてのモデル インスタンスをに複製したいということBookです。ChaptersPages

a を見ると、物事は非常に複雑になりますPage。新しいフィールドを更新Pagesする必要があるだけでなく、新しいオブジェクトauthorを指す必要もあります。Chapter

Django はこれを行うための独創的な方法をサポートしていますか? モデルを複製するための一般的なアルゴリズムはどのようになりますか?

乾杯、

ジョン


アップデート:

上記のクラスは、私が抱えている問題を説明するための単なる例です!

4

17 に答える 17

18

CollectedObjects が削除されたため、これは Django 1.3 では機能しなくなりました。チェンジセット 14507を参照

ソリューションを Django Snippets に投稿しました。django.db.models.query.CollectedObjectオブジェクトの削除に使用されるコードに大きく基づいています。

from django.db.models.query import CollectedObjects
from django.db.models.fields.related import ForeignKey

def duplicate(obj, value, field):
    """
    Duplicate all related objects of `obj` setting
    `field` to `value`. If one of the duplicate
    objects has an FK to another duplicate object
    update that as well. Return the duplicate copy
    of `obj`.  
    """
    collected_objs = CollectedObjects()
    obj._collect_sub_objects(collected_objs)
    related_models = collected_objs.keys()
    root_obj = None
    # Traverse the related models in reverse deletion order.    
    for model in reversed(related_models):
        # Find all FKs on `model` that point to a `related_model`.
        fks = []
        for f in model._meta.fields:
            if isinstance(f, ForeignKey) and f.rel.to in related_models:
                fks.append(f)
        # Replace each `sub_obj` with a duplicate.
        sub_obj = collected_objs[model]
        for pk_val, obj in sub_obj.iteritems():
            for fk in fks:
                fk_value = getattr(obj, "%s_id" % fk.name)
                # If this FK has been duplicated then point to the duplicate.
                if fk_value in collected_objs[fk.rel.to]:
                    dupe_obj = collected_objs[fk.rel.to][fk_value]
                    setattr(obj, fk.name, dupe_obj)
            # Duplicate the object and save it.
            obj.id = None
            setattr(obj, field, value)
            obj.save()
            if root_obj is None:
                root_obj = obj
    return root_obj

django >= 2の場合、最小限の変更が必要です。したがって、出力は次のようになります。

def duplicate(obj, value=None, field=None, duplicate_order=None):
    """
    Duplicate all related objects of obj setting
    field to value. If one of the duplicate
    objects has an FK to another duplicate object
    update that as well. Return the duplicate copy
    of obj.
    duplicate_order is a list of models which specify how
    the duplicate objects are saved. For complex objects
    this can matter. Check to save if objects are being
    saved correctly and if not just pass in related objects
    in the order that they should be saved.
    """
    from django.db.models.deletion import Collector
    from django.db.models.fields.related import ForeignKey

    collector = Collector(using='default')
    collector.collect([obj])
    collector.sort()
    related_models = collector.data.keys()
    data_snapshot = {}
    for key in collector.data.keys():
        data_snapshot.update(
            {key: dict(zip([item.pk for item in collector.data[key]], [item for item in collector.data[key]]))})
    root_obj = None

    # Sometimes it's good enough just to save in reverse deletion order.
    if duplicate_order is None:
        duplicate_order = reversed(related_models)

    for model in duplicate_order:
        # Find all FKs on model that point to a related_model.
        fks = []
        for f in model._meta.fields:
            if isinstance(f, ForeignKey) and f.remote_field.related_model in related_models:
                fks.append(f)
        # Replace each `sub_obj` with a duplicate.
        if model not in collector.data:
            continue
        sub_objects = collector.data[model]
        for obj in sub_objects:
            for fk in fks:
                fk_value = getattr(obj, "%s_id" % fk.name)
                # If this FK has been duplicated then point to the duplicate.
                fk_rel_to = data_snapshot[fk.remote_field.related_model]
                if fk_value in fk_rel_to:
                    dupe_obj = fk_rel_to[fk_value]
                    setattr(obj, fk.name, dupe_obj)
            # Duplicate the object and save it.
            obj.id = None
            if field is not None:
                setattr(obj, field, value)
            obj.save()
            if root_obj is None:
                root_obj = obj
    return root_obj
于 2009-01-14T00:28:16.800 に答える
11

オブジェクトをコピーする簡単な方法を次に示します。

基本的:

(1) 元のオブジェクトの ID を None に設定します。

book_to_copy.id = なし

(2) 「作成者」属性を変更し、オブジェクトを保存します。

book_to_copy.author = new_author

book_to_copy.save()

(3) UPDATE の代わりに INSERT を実行

(ページ内の作成者の変更には対応していません。モデルの再構築に関するコメントに同意します)

于 2010-06-25T18:20:17.107 に答える
9

私はdjangoで試したことはありませんが、pythonのdeepcopyがうまくいくかもしれません

編集:

関数を実装すると、モデルのカスタム コピー動作を定義できます。

__copy__() and __deepcopy__()
于 2009-01-12T22:24:51.260 に答える
9

これはhttp://www.djangosnippets.org/snippets/1282/の編集です

1.3 で CollectedObjects を置き換えた Collector と互換性があります。

私はこれをあまり厳しくテストしませんでしたが、約 20,000 のサブオブジェクトを持つオブジェクトでテストしましたが、外部キーの深さは約 3 層のみでした。もちろん、自己責任で使用してください。

この投稿を読んでいる野心的な人は、「DuplicateCollector」のような名前のクラスに Collector をサブクラス化 (またはクラス全体をコピーして、django API のこの未公開セクションへの依存関係を削除) し、機能する .duplicate メソッドを作成することを検討する必要があります。 .delete メソッドと同様です。それはこの問題を実際に解決するでしょう。

from django.db.models.deletion import Collector
from django.db.models.fields.related import ForeignKey

def duplicate(obj, value=None, field=None, duplicate_order=None):
    """
    Duplicate all related objects of obj setting
    field to value. If one of the duplicate
    objects has an FK to another duplicate object
    update that as well. Return the duplicate copy
    of obj.
    duplicate_order is a list of models which specify how
    the duplicate objects are saved. For complex objects
    this can matter. Check to save if objects are being
    saved correctly and if not just pass in related objects
    in the order that they should be saved.
    """
    collector = Collector({})
    collector.collect([obj])
    collector.sort()
    related_models = collector.data.keys()
    data_snapshot =  {}
    for key in collector.data.keys():
        data_snapshot.update({ key: dict(zip([item.pk for item in collector.data[key]], [item for item in collector.data[key]])) })
    root_obj = None

    # Sometimes it's good enough just to save in reverse deletion order.
    if duplicate_order is None:
        duplicate_order = reversed(related_models)

    for model in duplicate_order:
        # Find all FKs on model that point to a related_model.
        fks = []
        for f in model._meta.fields:
            if isinstance(f, ForeignKey) and f.rel.to in related_models:
                fks.append(f)
        # Replace each `sub_obj` with a duplicate.
        if model not in collector.data:
            continue
        sub_objects = collector.data[model]
        for obj in sub_objects:
            for fk in fks:
                fk_value = getattr(obj, "%s_id" % fk.name)
                # If this FK has been duplicated then point to the duplicate.
                fk_rel_to = data_snapshot[fk.rel.to]
                if fk_value in fk_rel_to:
                    dupe_obj = fk_rel_to[fk_value]
                    setattr(obj, fk.name, dupe_obj)
            # Duplicate the object and save it.
            obj.id = None
            if field is not None:
                setattr(obj, field, value)
            obj.save()
            if root_obj is None:
                root_obj = obj
    return root_obj

編集:デバッグの「印刷」ステートメントを削除しました。

于 2011-05-19T19:51:02.983 に答える
4

Django 1.5では、これは私にとってはうまくいきます:

thing.id = None
thing.pk = None
thing.save()
于 2013-05-22T19:28:37.707 に答える
3

Djangoには、管理者を介してオブジェクトを複製する組み込みの方法があります-ここで回答されているように: Django管理インターフェイスでは、項目を複製する方法はありますか?

于 2012-04-24T23:06:54.700 に答える
3

構築中のデータベースにいくつかのコピーしかない場合は、管理インターフェイスの [戻る] ボタンを使用して、必要なフィールドを変更し、インスタンスを再度保存できることがわかりました。これは、たとえば、「ギムレット」と「ウォッカ ギムレット」カクテルを作成する必要がある場合に、名前と材料を置き換えるだけでうまくいきました。明らかに、これにはデータを少し先見する必要があり、django の copy/deepcopy をオーバーライドするほど強力ではありませんが、一部の人にとってはうまくいくかもしれません。

于 2010-06-22T13:28:48.780 に答える
2

単純な非一般的な方法

提案された解決策は私にはうまくいかなかったので、賢い方法ではなく単純な方法を選びました。これは、単純な場合にのみ役立ちます。

次の構造を持つモデルの場合

Book
 |__ CroppedFace
 |__ Photo
      |__ AwsReco
            |__ AwsLabel
            |__ AwsFace
                  |__ AwsEmotion

これは動作します

def duplicate_book(book: Book, new_user: MyUser):
    # AwsEmotion, AwsFace, AwsLabel, AwsReco, Photo, CroppedFace, Book

    old_cropped_faces = book.croppedface_set.all()
    old_photos = book.photo_set.all()

    book.pk = None
    book.user = new_user
    book.save()

    for cf in old_cropped_faces:
        cf.pk = None
        cf.book = book
        cf.save()

    for photo in old_photos:
        photo.pk = None
        photo.book = book
        photo.save()

        if hasattr(photo, 'awsreco'):
            reco = photo.awsreco
            old_aws_labels = reco.awslabel_set.all()
            old_aws_faces = reco.awsface_set.all()
            reco.pk = None
            reco.photo = photo
            reco.save()

            for label in old_aws_labels:
                label.pk = None
                label.reco = reco
                label.save()

            for face in old_aws_faces:
                old_aws_emotions = face.awsemotion_set.all()
                face.pk = None
                face.reco = reco
                face.save()

                for emotion in old_aws_emotions:
                    emotion.pk = None
                    emotion.aws_face = face
                    emotion.save()
    return book
于 2017-02-24T17:50:53.180 に答える