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!