How to use update method in viewsets in Django Rest Framework and perform some task(like sending mail) along with it?

Viewed 909

I am working on a simple performance management system with react on frontend and django on the backend. They are supervisors who can give reviews to supervisees and supervisees can respond. I want all employees to receive email when they receive reviews from their supervisors and all the supervisors to receive email when their reviews are responded. For reviews and responses I am using two different serializers but same model.

Serializer for Response is:

class ResponseSerializer(serializers.ModelSerializer):
    supervisor_name = serializers.SerializerMethodField('get_supervisor_name')
    supervisor_email = serializers.SerializerMethodField('get_supervisor_email')
    supervisee_name = serializers.SerializerMethodField('get_supervisee_name')
    supervisee_email = serializers.SerializerMethodField('get_supervisee_email')

    class Meta:
        model = Review
        fields = (
            'id', 'review_text', 'response_text', 'date_of_review', 'date_of_response', 'supervisor', 'supervisor_name',
            'supervisor_email', 'supervisee', 'supervisee_name', 'supervisee_email')
        read_only_fields = ('review_text', 'date_of_review', 'supervisor', 'supervisee')

    def get_supervisor_name(self, obj):
        return obj.supervisor.first_name + " " + obj.supervisor.last_name

    def get_supervisor_email(self, obj):
        return obj.supervisor.email

    def get_supervisee_name(self, obj):
        return obj.supervisee.first_name + " " + obj.supervisee.last_name

    def get_supervisee_email(self, obj):
        return obj.supervisee.email

For sending mail I am using send_mail method from django.core And I am using Viewsets for Reviews and Responses.

Now Response operation will always be an update operation because Response will always be used to update existing Review object in which response_text field will be updated.

class ResponseViewSet(viewsets.ModelViewSet):
    queryset = Review.objects.all()
    permission_classes = [
        # permissions.IsAuthenticated,
        permissions.AllowAny,
    ]
    serializer_class = ResponseSerializer

    def update(self, request, *args, **kwargs):
        serializer = ResponseSerializer(data=request.data)

        if serializer.is_valid():
            supervisor = serializer.data["supervisor_name"]
            supervisee = serializer.data["supervisee_name"]
            query = serializer.save()
            mail_text = "Hi {}\n\nYou got a response for your 1:1 from {}.\n\nClick below to see the response:\n\n{}".format(
                supervisor,
                supervisee,
                "https://example.com/#/pms/reviewsBySupervisor",
            )
            try:
                if not settings.DEFAULT_EMAIL_RECIPIENTS:
                    settings.DEFAULT_EMAIL_RECIPIENTS.append(
                        str(serializer.data["supervisor_email"])
                    )
                send_mail(
                    subject="New Response Received",
                    message=mail_text,
                    from_email=settings.DEFAULT_FROM_EMAIL,
                    recipient_list=settings.DEFAULT_EMAIL_RECIPIENTS,
                    fail_silently=False,
                )
            except (SMTPRecipientsRefused, SMTPSenderRefused):
                LOGGER.exception("There was a problem submitting the form.")
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

So, the problem that I am facing is that when I try to send mail with update method in ResponseViewset as shown above. I get the following error:

Internal Server Error: /UMS/api/responses/38/
Traceback (most recent call last):
  File "/home/shishir/Projects/performance_management/performance_management/reviews/serializers.py", line 77, in get_supervisor_name
    return obj.supervisor.first_name + " " + obj.supervisor.last_name
AttributeError: 'NoneType' object has no attribute 'first_name'

So, what is happening is due to some reason, all the fields of that particular review are getting set to null as soon as I try to update and hence getting NoneType object has no attribute. I have checked in database table(MySQL), all the fields are getting set to null. Can anyone tell me why is this happening ? Where am I going wrong ? And what is the correct way to do it?

1 Answers

Finally I found my solution by changing the method update to partial_update. Apparently update method updates all the field while in above case I am attempting the field called response_text in the Review model which are setting other fields to null if they could be. Also after doing that I had to change the request from PUT to PATCH in frontend. Also I had to do some other minor coding changes like removing supervisor and supervisee fields from read_only_fields from ResponseSerializer. Updated code for ResponseViewset is shown below:

class ResponseViewSet(viewsets.ModelViewSet):
    queryset = Review.objects.all()
    permission_classes = [
        permissions.IsAuthenticated,
        #permissions.AllowAny,
    ]
    serializer_class = ResponseSerializer

    def partial_update(self, request,*args, **kwargs):
        obj = self.get_object()
        serializer = self.serializer_class(obj, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            mail_text = "Hi {},\n\nYou got a response for your 1:1 from {}.\n\nClick below to see the response:\n\n{}".format(
                serializer.data["supervisor_name"],
                serializer.data["supervisee_name"],
                get_product_link("UMS/reviewsForDR"),
            )
            try:
                if not settings.DEFAULT_EMAIL_RECIPIENTS:
                    settings.DEFAULT_EMAIL_RECIPIENTS.append(
                        # supervisor_email
                        serializer.data["supervisor_email"]
                    )

                send_mail(
                    subject="New Response Received",
                    message=mail_text,
                    from_email=settings.DEFAULT_FROM_EMAIL,
                    recipient_list=settings.DEFAULT_EMAIL_RECIPIENTS,
                    fail_silently=False,
                )
                settings.DEFAULT_EMAIL_RECIPIENTS = []
            except (SMTPRecipientsRefused, SMTPSenderRefused):
                LOGGER.exception("There was a problem submitting the form.")
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Related