dynamoed

Untitled

May 21st, 2019
346
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.86 KB | None | 0 0
  1. def duplicate(object, callback=None):
  2.     """
  3.    Based on: https://stackoverflow.com/a/52761222/2066218
  4.  
  5.    Duplicate a model instance, making copies of all foreign keys pointing to it.
  6.    There are 3 steps that need to occur in order:
  7.  
  8.    1.  Enumerate the related child objects and m2m relations, saving in lists/dicts
  9.    2.  Copy the parent object per django docs (doesn't copy relations)
  10.    3a. Copy the child objects, relating to the copied parent object
  11.    3b. Re-create the m2m relations on the copied parent object
  12.  
  13.    The optional callback function is called once the item has been duplicated but before
  14.    it's saved. The new object is passed its only argument and it should return the object to be save.
  15.    It can be used e.g. to update the name of the duplicated object
  16.  
  17.    ```
  18.    def duplicate_name(object):
  19.        object.name += ' (Copy)'
  20.        return object
  21.  
  22.    duplicate(object, callback=duplicate_name)
  23.    ```
  24.    """
  25.     related_objects_to_copy = []
  26.     relations_to_set = {}
  27.  
  28.     # Iterate through all the fields in the parent object looking for related fields
  29.     fields = object._meta.get_fields()
  30.     for field in fields:
  31.         if field.one_to_many:
  32.             # One to many fields are backward relationships where many child
  33.             # objects are related to the parent. Enumerate them and save a list
  34.             # so we can copy them after duplicating our parent object.
  35.             print(f'Found a one-to-many field: {field.name}')
  36.  
  37.             # 'field' is a ManyToOneRel which is not iterable, we need to get
  38.             # the object attribute itself.
  39.             related_object_manager = getattr(object, field.get_accessor_name())
  40.             related_objects = list(related_object_manager.all())
  41.             if related_objects:
  42.                 print(f' - {len(related_objects)} related objects to copy')
  43.                 related_objects_to_copy += related_objects
  44.  
  45.         elif field.many_to_one:
  46.             # In testing, these relationships are preserved when the parent
  47.             # object is copied, so they don't need to be copied separately.
  48.             print(f'Found a many-to-one field: {field.name}')
  49.  
  50.         elif field.many_to_many and not hasattr(field, 'field'):
  51.             # Many to many fields are relationships where many parent objects
  52.             # can be related to many child objects. Because of this the child
  53.             # objects don't need to be copied when we copy the parent, we just
  54.             # need to re-create the relationship to them on the copied parent.
  55.             related_object_manager = getattr(object, field.name)
  56.  
  57.             if related_object_manager.through:
  58.                 # Many to many relations with a through table are handled as many to one relationships
  59.                 # between the object and the through table so we can skip this
  60.                 continue
  61.  
  62.             print(f'Found a many-to-many field: {field.name}')
  63.             relations = list(related_object_manager.all())
  64.             if relations:
  65.                 print(f' - {len(relations)} relations to set')
  66.                 relations_to_set[field.name] = relations
  67.  
  68.     # Duplicate the parent object
  69.     object.pk = None
  70.  
  71.     if callback and callable(callback):
  72.         object = callback(object)
  73.  
  74.     object.save()
  75.     print(f'Copied parent object ({str(object)})')
  76.  
  77.     # Copy the one-to-many child objects and relate them to the copied parent
  78.     for related_object in related_objects_to_copy:
  79.         # Iterate through the fields in the related object to find the one that
  80.         # relates to the parent model.
  81.         for related_object_field in related_object._meta.fields:
  82.             if related_object_field.related_model == object.__class__:
  83.                 # If the related_model on this field matches the parent
  84.                 # object's class, perform the copy of the child object and set
  85.                 # this field to the parent object, creating the new
  86.                 # child -> parent relationship.
  87.                 setattr(related_object, related_object_field.name, object)
  88.                 new_related_object = duplicate(related_object)
  89.                 new_related_object.save()
  90.  
  91.                 text = str(related_object)
  92.                 text = (text[:40] + '..') if len(text) > 40 else text
  93.                 print(f'|- Copied child object ({text})')
  94.  
  95.     # Set the many-to-many relations on the copied parent
  96.     for field_name, relations in relations_to_set.items():
  97.         # Get the field by name and set the relations, creating the new
  98.         # relationships.
  99.         field = getattr(object, field_name)
  100.         field.set(relations)
  101.         text_relations = []
  102.         for relation in relations:
  103.             text_relations.append(str(relation))
  104.         print(f'|- Set {len(relations)} many-to-many relations on {field_name} {text_relations}')
  105.  
  106.     return object
Add Comment
Please, Sign In to add comment