[python] Duplizieren von Modellinstanzen und ihren verwandten Objekten in Django / Algorithmus zum rekursiven Duplizieren eines Objekts



Answers

Hier ist eine einfache Möglichkeit, Ihr Objekt zu kopieren.

Grundsätzlich gilt:

(1) setze die ID deines ursprünglichen Objekts auf None:

book_to_copy.id = Keine

(2) ändere das Attribut 'author' und speichere das Objekt:

book_to_copy.author = neuer_author

book_to_copy.save ()

(3) INSERT wurde anstelle von UPDATE durchgeführt

(Es geht nicht darum, den Autor auf der Seite zu ändern - ich stimme den Kommentaren bezüglich der Neustrukturierung der Modelle zu)

Question

Ich habe Modelle für Books , Chapters und Pages . Sie werden alle von einem 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)

Was ich tun möchte, ist ein vorhandenes Book duplizieren und seinen User zu jemand anderem zu aktualisieren. Die Falte ist, ich möchte auch alle verwandten Modell Instanzen zum Book kopieren - alles es ist Chapters und Pages auch!

Die Dinge werden wirklich schwierig, wenn man auf eine Page schaut - nicht nur, dass die neuen Pages ihr author aktualisiert haben müssen, sondern sie müssen auch auf die neuen Chapter !

Unterstützt Django eine Out-of-the-Box-Methode? Wie sieht ein generischer Algorithmus zum Duplizieren eines Modells aus?

Prost,

John

Aktualisieren:

Die oben angegebenen Klassen sind nur ein Beispiel, um das Problem zu illustrieren, das ich habe!







Einfache, nicht generische Methode

Vorgeschlagene Lösungen funktionierten nicht für mich, also ging ich einfach, nicht clever. Dies ist nur in einfachen Fällen nützlich.

Für ein Modell mit folgender Struktur

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

das funktioniert

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



Dies ist eine Bearbeitung von http://www.djangosnippets.org/snippets/1282/

Es ist nun kompatibel mit dem Collector, der CollectedObjects in 1.3 ersetzt hat.

Ich habe das nicht wirklich zu stark getestet, aber ich habe es mit einem Objekt mit etwa 20.000 Unterobjekten getestet, aber in nur drei Schichten mit Fremdschlüsseltiefe. Verwenden Sie auf eigene Gefahr natürlich.

Für den ambitionierten Typ, der diesen Beitrag liest, sollten Sie Collector ableiten (oder die gesamte Klasse kopieren, um diese Abhängigkeit von diesem unveröffentlichten Abschnitt der django-API zu entfernen) in eine Klasse wie "DuplicateCollector" schreiben und eine .duplicate-Methode schreiben, die funktioniert ähnlich wie bei der .delete-Methode. das würde dieses Problem auf eine reale Weise lösen.

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

EDIT: Entfernte eine Debugging "print" Anweisung.




Das obige CollectedObjects-Snippet funktioniert nicht mehr, kann aber mit der folgenden Änderung ausgeführt werden:

from django.contrib.admin.util import NestedObjects
from django.db import DEFAULT_DB_ALIAS

und

collector = NestedObjects(using=DEFAULT_DB_ALIAS)

anstelle von CollectorObjects




Related