django-clone icon indicating copy to clipboard operation
django-clone copied to clipboard

[Feature] Pass origin object to signals

Open dacotagh opened this issue 1 year ago • 2 comments

Is this feature missing in the latest version?

  • [X] I'm using the latest release

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

  • [X] I agree to follow this project's Code of Conduct

dacotagh avatar Apr 03 '24 15:04 dacotagh

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

github-actions[bot] avatar Apr 03 '24 15:04 github-actions[bot]

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 pre_clone_* 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!

mfoulds avatar Sep 03 '24 13:09 mfoulds