serializers.py 3.01 KB
Newer Older
Agate's avatar
Agate committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14
from rest_framework import serializers


class ActionSerializer(serializers.Serializer):
    """
    A special serializer that can operate on a list of objects
    and apply actions on it.
    """

    action = serializers.CharField(required=True)
    objects = serializers.JSONField(required=True)
    filters = serializers.DictField(required=False)
    actions = None
    filterset_class = None
15 16 17
    # those are actions identifier where we don't want to allow the "all"
    # selector because it's to dangerous. Like object deletion.
    dangerous_actions = []
Agate's avatar
Agate committed
18 19

    def __init__(self, *args, **kwargs):
Agate's avatar
Agate committed
20
        self.queryset = kwargs.pop("queryset")
Agate's avatar
Agate committed
21 22
        if self.actions is None:
            raise ValueError(
Agate's avatar
Agate committed
23 24
                "You must declare a list of actions on " "the serializer class"
            )
Agate's avatar
Agate committed
25 26

        for action in self.actions:
Agate's avatar
Agate committed
27 28 29
            handler_name = "handle_{}".format(action)
            assert hasattr(self, handler_name), "{} miss a {} method".format(
                self.__class__.__name__, handler_name
Agate's avatar
Agate committed
30 31 32 33 34 35
            )
        super().__init__(self, *args, **kwargs)

    def validate_action(self, value):
        if value not in self.actions:
            raise serializers.ValidationError(
Agate's avatar
Agate committed
36 37
                "{} is not a valid action. Pick one of {}.".format(
                    value, ", ".join(self.actions)
Agate's avatar
Agate committed
38 39 40 41 42
                )
            )
        return value

    def validate_objects(self, value):
Agate's avatar
Agate committed
43 44
        if value == "all":
            return self.queryset.all().order_by("id")
Agate's avatar
Agate committed
45
        if type(value) in [list, tuple]:
Agate's avatar
Agate committed
46
            return self.queryset.filter(pk__in=value).order_by("id")
Agate's avatar
Agate committed
47 48

        raise serializers.ValidationError(
Agate's avatar
Agate committed
49 50 51
            "{} is not a valid value for objects. You must provide either a "
            'list of identifiers or the string "all".'.format(value)
        )
Agate's avatar
Agate committed
52 53

    def validate(self, data):
Agate's avatar
Agate committed
54 55
        dangerous = data["action"] in self.dangerous_actions
        if dangerous and self.initial_data["objects"] == "all":
56
            raise serializers.ValidationError(
Agate's avatar
Agate committed
57 58 59
                "This action is to dangerous to be applied to all objects"
            )
        if self.filterset_class and "filters" in data:
60
            qs_filterset = self.filterset_class(
Agate's avatar
Agate committed
61 62
                data["filters"], queryset=data["objects"]
            )
63 64 65
            try:
                assert qs_filterset.form.is_valid()
            except (AssertionError, TypeError):
Agate's avatar
Agate committed
66 67
                raise serializers.ValidationError("Invalid filters")
            data["objects"] = qs_filterset.qs
Agate's avatar
Agate committed
68

Agate's avatar
Agate committed
69 70 71
        data["count"] = data["objects"].count()
        if data["count"] < 1:
            raise serializers.ValidationError("No object matching your request")
Agate's avatar
Agate committed
72 73 74
        return data

    def save(self):
Agate's avatar
Agate committed
75
        handler_name = "handle_{}".format(self.validated_data["action"])
Agate's avatar
Agate committed
76
        handler = getattr(self, handler_name)
Agate's avatar
Agate committed
77
        result = handler(self.validated_data["objects"])
Agate's avatar
Agate committed
78
        payload = {
Agate's avatar
Agate committed
79 80 81
            "updated": self.validated_data["count"],
            "action": self.validated_data["action"],
            "result": result,
Agate's avatar
Agate committed
82 83
        }
        return payload