dabapps / django-rest-framework-serialization-spec

DEPRECATED, see https://github.com/dabapps/django-readers instead
MIT License
11 stars 0 forks source link

Add a proper mechanism for plugins to manipulate the data tree without returning a value #42

Open pmg103 opened 4 years ago

pmg103 commented 4 years ago

Currently a plugin's get_value() method can be used to update values onto child objects which only the parent has the context to calculate.

This is a useful mechanism but unfortunately a dummy key is required since any plugin that runs must return a value into the data output tree.

Example:

class PopulateActivityRaterHasStarted(SerializationSpecPlugin):
    serialization_spec = [
        {'_activity_product_version_items': Aliased('activity_product_version_items', [
            {'activity_product_version_sessions': [
                'user',
                'rater_for'
            ]}
        ])}
    ]

    def get_value(self, instance):
        sessions = {
            session.user_id: session.rater_for_id
            for apv in instance._activity_product_version_items
            for session in apv.activity_product_version_sessions.all()
        }

        for activity_user in instance.self_users:
            for rater in activity_user.raters.all():
                rater.has_started = sessions.get(activity_user.user_id) == rater.rater_id

        return None

# Works in conjunction with PopulateActivityRaterHasStarted above
class ActivityRaterHasStarted(SerializationSpecPlugin):
    def get_value(self, instance):
        return instance.has_started

class MyActivityList(SerializationSpecMixin, generics.ListAPIView):
    queryset = Activity.objects.all()

    serialization_spec = [
        # ...
        {'UNUSED': PopulateActivityRaterHasStarted()},
        {'users': [
            {'raters': [
                {'has_started': ActivityRaterHasStarted()}
            ]}
        ]},
    ]

We need to provide some kind of mechanism to allow manipulations of the tree without the need to output a value which must be given a dummy key

pmg103 commented 4 years ago

Maybe some way of 'wrapping' the sub-spec with the code that will apply the transformation:

class PopulateActivityRaterHasStarted(SerializationSpecTransformer):
    serialization_spec = [
        {'activity_product_version_items': [
            {'activity_product_version_sessions': [
                'user',
                'rater_for'
            ]}
        ]}
    ]

    def apply(self, instance):
        sessions = {
            session.user_id: session.rater_for_id
            for apv in instance.activity_product_version_items.all()
            for session in apv.activity_product_version_sessions.all()
        }

        for activity_user in instance.self_users:
            for rater in activity_user.raters.all():
                rater.has_started = sessions.get(activity_user.user_id) == rater.rater_id

class MyActivityList(SerializationSpecMixin, generics.ListAPIView):
    queryset = Activity.objects.all()

    serialization_spec = Transform(PopulateActivityRaterHasStarted(), [
        # ...
        {'users': [
            {'raters': [
                ... ,
                {'has_started': Populated()},
            ]}
        ]},
    ])
pmg103 commented 4 years ago

It would be tidier if you could just list inner fields in the normal way:

    serialization_spec = With(PopulateActivityRaterHasStarted(), [
        # ...
        {'users': [
            {'raters': [
                ...
                'has_started',
            ]}
        ]},
    ])

In the current implementation, has_started would be included in only_fields which would fail. Perhaps that should just silently ignore missing fields and assume it will have been provided by serialization time, or error at that point.