agateblue / django-dynamic-preferences

Dynamic global and instance settings for your django project
https://django-dynamic-preferences.readthedocs.org/
BSD 3-Clause "New" or "Revised" License
350 stars 87 forks source link

pre_delete handler broken for ModelMultipleChoicePreference #234

Closed jeriox closed 3 years ago

jeriox commented 3 years ago

This issue occurs with v1.10.1 within Windows and Ubuntu.

I have a ModelMultipleChoicePreference to a model A. After editing the preference and choosing several instances, the preference is represented in the database with a raw_value of let's say 1, 2, 3. When trying to delete the model instance with the primary key 3, a AttributeError: 'A' object has no attribute 'values_list' is raised in serializers.py:68. The pre-delete handler passes instance to the serializer while the ModelMultipleSerializer expects a QuerySet as input. This is due to the fact that the same pre-delete handler is used for ModelChoicePreference and ModelMultipleChoicePreference. This also leads to problems as the handler deletes related preferences. When deleting one instance of A, I expect this instance to be removed from the preferences, but I expect the other instances that I have selected (in my example 1 and 2) to remain as the preference.

I first came up with the following fix by writing a seperate handler for ModelMultipleChoicePreference:

def create_multiple_deletion_handler(preference):
    """
    Will generate a dynamic handler to purge related preference
    on instance deletion
    """
    def delete_related_preferences(sender, instance, *args, **kwargs):
        queryset = preference.registry.preference_model.objects\
                                      .filter(name=preference.name,
                                              section=preference.section)
        related_preferences = queryset.filter(
            raw_value__contains=instance.pk)
        related_preferences.update(
            raw_value=Replace("raw_value", Value(f",{instance.pk}"), Value(""))
        )
        related_preferences.update(
            raw_value=Replace("raw_value", Value(f"{instance.pk},"), Value(""))
        )
        related_preferences.update(
            raw_value=Replace("raw_value", Value(f"{instance.pk}"), Value(""))
        )
    return delete_related_preferences

This does not work because __contains matches a pk of 22 when instance has a pk of 2.

My new suggestion is to drop the handler for the ModelMultipleChoicePreference entirely. After deleting the A instance, its pk will remain in the raw_value field of the preference, but when loading the actual values it gets filtered out and on the next save it will be removed from the db.