tj-django / django-clone

Controlled Django model instance replication.
https://tj-django.github.io/django-clone
MIT License
118 stars 24 forks source link

[Feature] Pass origin object to signals #876

Open dacotagh opened 5 months ago

dacotagh commented 5 months ago

Is this feature missing in the latest version?

Is your feature request related to a problem? Please describe.

Some copied objects have links to data out of the data base, linked by id or uuid. When object cloned, it has new id/uuid. To copy external data user need access to origin id/uuid of object.

Describe the solution you'd like?

I suppose to add origin parameter to pre_clone_save/post_clone_save signals.

To do this, in function make_clone in mixin.py we need to make these changes:

    -pre_clone_save.send(sender=self.__class__, instance=duplicate)
    +pre_clone_save.send(sender=self.__class__, instance=duplicate, origin=self)

...

    -post_clone_save.send(sender=self.__class__, instance=duplicate)
    +post_clone_save.send(sender=self.__class__, instance=duplicate, origin=self)

Describe alternatives you've considered?

No response

Anything else?

No response

Code of Conduct

github-actions[bot] commented 5 months ago

Thanks for reporting this issue, don't forget to star this project if you haven't already to help us reach a wider audience.

mfoulds commented 2 weeks ago

I found that I also need origin. I also need to know whether the new clone instance is a result of cloning the origin object directly, or if it resulted from cloning a o2o, o2m, m2o or m2m relationship by origin's parent (a kind of "cascade clone"). I couldn't see an obvious way to achieve this (except by inspecting the stack (eg inspect.stack()[1][3]).

My approach (see mixin.py in the _pass_kwargs_from_make_clone branch of my fork) is:

  1. Include **kwargs in method signature of make_clone()
  2. From each of the __duplicate_* methods that calls make_clone:
    1. Add the name of the parent_instance (ie self) and the name of the method as calling_function to kwargs
    2. Make sure to pass **kwargs to each call to make_clone
  3. In make_clone add kwargs['origin'] = self (to achieve @dacotagh 's objective)
  4. Pass the kwargs to the preclone* signals (eg pre_clone_save.send(sender=self.__class__, instance=duplicate, **kwargs)

The project that's using this fork of django-clone sets instance._state variables in the pre_clone_save signal (and unsets them in the post_clone_save signal), like this:

@receiver(pre_clone_save)
def set_new_clone_state_true(sender, instance, **kwargs):
    instance._state.new_clone = True
    instance._state.origin = kwargs.get("origin")
    instance._state.calling_function = kwargs.get("calling_function", None)
    instance._state.parent_instance = kwargs.get("parent_instance", None)
    instance._state.cascade_clone = (
        instance._state.parent_instance is not None and instance._state.calling_function is not None
    )

These can then be picked up in the relevant save() methods of the models that need them.

Another possible benefit of having **kwargs in the make_clone method signature and also passing them to the ???_clone_save methods is that users of the library could then possibly override make_clone and add additional kwargs before calling super().make_clone which would then propagate through signals.

I don't know if any of this is generally needed by anyone else, but it seemed pretty close to @dacotagh 's requirement here. I thought maybe using **kwargs would meet the immediate objective and also provide room for future needs.

PS Thanks @jackton1 very much for this terrific library!