HackSoftware / Django-Styleguide

Django styleguide used in HackSoft projects

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Handling updates in the service

RTS340 opened this issue · comments

From looking through your guide I am trying to understand how you would handle updates within a service. From your example below:

class CourseUpdateApi(SomeAuthenticationMixin, APIView):
    class InputSerializer(serializers.Serializer):
        name = serializers.CharField(required=False)
        start_date = serializers.DateField(required=False)
        end_date = serializers.DateField(required=False)
    def post(self, request, course_id):
        serializer = self.InputSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        update_course(course_id=course_id, **serializer.validated_data)
        return Response(status=status.HTTP_200_OK)

You advocate using keyword-only arguments so I would assume the method definition would be as so:

def update_course(
    *,
    course_id=course_id,
    name:str,
    start_date:datetime,
    end_date:datetime,
) -> Course:

In your input serializer not all fields are required, so they may not be available when calling the course_update from the API using the serializer data. So I am assuming you are going to give them a default of None inside of your service as so:

def update_course(
    *,
    course_id=course_id,
    name:str = None,
    start_date:datetime = None,
    end_date:datetime = None,
) -> Course:

If this is the case how do you handle the update? Do you perform checks on each of the arguments inside the update_course to see if they are None, to then update only those with values?

Cheers!

Hello @RTS340 and thanks for the question.

Our styleguide needs updating around how to handle updates.

Here's an example update service, the way we usually write it:

@transaction.atomic
def some_object_update(
    *,
    some_object: SomeObject,
    user: User,
    data
) -> SomeObject:
    if some_object.owner != user:
        raise ValidationError('Cannot update some_object.')

    valid_fields = ["field1", "field2", "field3"]

    for field in valid_fields:
        if field in data:
            setattr(some_object, field, data[field])

    logo = data.get('logo')

    if logo:
        # For example, logo is an object that needs additional work
        logo.mark_as_uploaded()

        some_object.logo = logo

    some_object.full_clean()
    some_object.save()

    return some_object

On the API side:

class SomeObjectUpdateApi(SomeMixin, APIView):
    class InputSerializer(serializers.Serializer):
        field1 = serializers.CharField(required=False)
        field2 = serializers.CharField(required=False)
        field3 = serializers.CharField(required=False)

        logo = serializers.PrimaryKeyRelatedField(
            queryset=Image.objects.all(),
            required=False
        )

    def post(self, request, some_object_id):
        some_object = get_object_or_404(SomeObject, id=some_object_id)

        serializer = self.InputSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        some_object_update(
            some_object=some_object,
            user=request.user,
            data=serializer.validated_data
        )

        return Response()

Hope that answers your question.

It's a pattern that we commonly use & it's quite flexible.

Thanks for the response @RadoRado

We have actually implemented a generic update function as you have mentioned above which seems to work nicely.

We are also implementing a couple of additional dedicated update functions for single field operations we are performing multiple places throughout the code base. i.e.

some_object_update_status(
            some_object=some_object,
            user=request.user,
            status='test
        )

Cheers

@RTS340 makes sense 👍

Cheers!

Thank you both @RadoRado @RTS340! I've been meaning to post this very question for the past week, glad I found this!