Duplicate Django Model Instance and All Foreign Keys Pointing to It

后端 未结 4 477
自闭症患者
自闭症患者 2021-01-02 06:03

I want to create a method on a Django model, call it model.duplicate(), that duplicates the model instance, including all the foreign keys pointing to it. I kno

4条回答
  •  长情又很酷
    2021-01-02 06:16

    Here is a somewhat simple-minded solution. This does not depend on any undocumented Django APIs. It assumes that you want to duplicate a single parent record, along with its child, grandchild, etc. records. You pass in a whitelist of classes that should actually be duplicated, in the form of a list of names of the one-to-many relationships on each parent object that point to its child objects. This code assumes that, given the above whitelist, the entire tree is self-contained, with no external references to worry about.

    One more thing about this code: it is truly recursive, in that it calls itself for each new level of descendants.

    from collections import OrderedDict
    
    def duplicate_model_with_descendants(obj, whitelist, _new_parent_pk=None):
        kwargs = {}
        children_to_clone = OrderedDict()
        for field in obj._meta.get_fields():
            if field.name == "id":
                pass
            elif field.one_to_many:
                if field.name in whitelist:
                    these_children = list(getattr(obj, field.name).all())
                    if children_to_clone.has_key(field.name):
                        children_to_clone[field.name] |= these_children
                    else:
                        children_to_clone[field.name] = these_children
                else:
                    pass
            elif field.many_to_one:
                if _new_parent_pk:
                    kwargs[field.name + '_id'] = _new_parent_pk
            elif field.concrete:
                kwargs[field.name] = getattr(obj, field.name)
            else:
                pass
        new_instance = obj.__class__(**kwargs)
        new_instance.save()
        new_instance_pk = new_instance.pk
        for ky in children_to_clone.keys():
            child_collection = getattr(new_instance, ky)
            for child in children_to_clone[ky]:
                child_collection.add(duplicate_model_with_descendants(child, whitelist=whitelist, _new_parent_pk=new_instance_pk))
        return new_instance
    

    Example usage:

    from django.db import models
    
    class Book(models.Model)
    
    class Chapter(models.Model)
        book = models.ForeignKey(Book, related_name='chapters')
    
    class Page(models.Model)
        chapter = models.ForeignKey(Chapter, related_name='pages')
    
    WHITELIST = ['books', 'chapters', 'pages']
    original_record = models.Book.objects.get(pk=1)
    duplicate_record = duplicate_model_with_descendants(original_record, WHITELIST)
    

提交回复
热议问题