5

Context

For a specific use case I need to be able to update a single field of my Visitor model using a GET request instead of a PATCH request.

My relevant Visitor model looks like this:

# models.py

class Visitor(models.Model):
    visitor_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
    customers = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='customer_visitors')
    audiences = models.ManyToManyField(Audience, related_name='audience_visitors')
    cid = models.CharField(max_length=255, unique=True)
    uid = models.CharField(max_length=255)
    cup = JSONField(null=True)

    def __str__(self):
        return self.cid

    class Meta:
        db_table = 'visitor'

I am using a straightforward serializer like this:

# serializers.py

class VisitorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Visitor
        fields = ('customers', 'cid', 'uid', 'cup')

I am able to update just the cup field for a specific Visitor which is looked up using the unique cid field with a PATCH like this:

# views.py

class VisitorViewSet(viewsets.ModelViewSet):
    serializer_class = VisitorSerializer
    queryset = Visitor.objects.all()
    lookup_field = 'cid'

    def list(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.serializer_class(instance, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)

Problem

The problem is that I am unable to update the cup field of a Visitor based on a given unique cid field using a GET request.

What I tried

As this answer by Uri Shalit suggested, I tried to override get_serializer() inside my VisitorViewSet and tried to use it in list() like this:

# views.py

class VisitorViewSet(viewsets.ModelViewSet):
    serializer_class = VisitorSerializer
    queryset = Visitor.objects.all()
    lookup_field = 'cid'

    def get_serializer(self, *args, **kwargs):
        kwargs['partial'] = True
        return super(VisitorViewSet, self).get_serializer(*args, **kwargs)

    def list(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)

However, updating just the cup field of a specific Visitor based on the cid field works with a PATCH request but does not update said field with a GET request. There is no error either.

Expected behaviour

Making a GET request which contains cid to identify a Visitor and cup with data that needs to be updated for the given Visitor. I know it breaks REST principles but for this use case I need to do this partial update using a GET request instead of a PATCH request.

Any help or pointers in the right direction would be much appreciated!

Stevy
  • 3,228
  • 7
  • 22
  • 38
  • 1
    I don't think you should use a GET request to update because GET request can be called many time(example most online crawler crawl using a GET request with all url name). I think you should have a PUT request with id in param `/data/1` and pass in the update field in the body – Linh Nguyen Jun 12 '20 at 10:32
  • 2
    This use case requires it to be a GET request. I am able to achieve what I want using either a PUT or PATCH request but it has to be a GET request as it is for tracking. – Stevy Jun 12 '20 at 12:03
  • Do you want to update a single visitor instance or a list of instances? And does the data you want to use to update come from the frontend or backend? If it's from the front then you can only pass it as a url parameter as GET requests have no body. Answers to these questions are necessary to be able to help you – Ken4scholars Jun 15 '20 at 15:16
  • I want to update a single visitor instance. The data comes from the frontend as a GET request with data passed as URL parameters – Stevy Jun 15 '20 at 16:02

3 Answers3

2

I recommend using an api_view to accomplish what you want. api_view is an annotation provided by the rest framework so it should be available already in your case.

@api_view(["GET"])
def update_function(request):
    query_params = request.GET  # Getting the parameters from request
    cid =  query_params["cid"]
    cup =  query_params["cup"]

    visitor = Visitor.objects.get(cid = cid)
    visitor["cup"] = cup

    serializer = VisitorSerializer(data = visitor, partial=True)
    if serializer.is_valid():
        serializer.save()
    else:
        print(serializer.errors)

However I am not sure about the syntax but the approch is sufficient for your problem. Make sure to add the function to urls.py and have a look to the documentation to get better information than mine Api Views. But dont expect it to have information about you specific problem. In your case you have to understand the api_view concept and adapt it for your needs.

  • it is not a modelviewset - so it is not really answering the Q – Ohad the Lad Jun 22 '20 at 09:03
  • Who said it has to be a modelviewset? apiviews are well suited fot the problem and hes already using restframework, so there are no additional imports needed. –  Jun 22 '20 at 11:34
  • I dont get your point. But you also don't seem to be interested in a discourse. –  Jun 22 '20 at 14:34
2

Add a classmethod in your model.

class Visitor(models.Model):
    visitor_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
    customers = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='customer_visitors')
    audiences = models.ManyToManyField(Audience, related_name='audience_visitors')
    cid = models.CharField(max_length=255, unique=True)
    uid = models.CharField(max_length=255)
    cup = JSONField(null=True)

    def __str__(self):
        return self.cid

    class Meta:
        db_table = 'visitor'


    @classmethod
    def update_cup(cls, cid, cup_new):
        instance = cls.objects.get(cid=cid)
        instance.cup = new_cup
        instance.save()

In ModelViewSet override the get_queryset method, see below:

IDK how u calc new_cup I guess u get it as a queryparam

def get_queryset(self):
    queryset = Visitor.objects.all()

    cup_new = self.request.query_params.get('cup_new', None)
    cid = self.request.query_params.get('cid', None)

    [obj.update_cup(obj.cid, cup_new) for obj in queryset if obj.cid == cid]
    return queryset
Stevy
  • 3,228
  • 7
  • 22
  • 38
Ohad the Lad
  • 1,889
  • 1
  • 15
  • 24
2

I think I don't understand the problem fully. Why can't you simply override the method get_object() in your view and do custom logic in it to update the object?

def get_object(self):
    obj = super().get_object()
    serializer = self.get_serializer(obj, data=self.request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save()
    return obj
physicalattraction
  • 6,485
  • 10
  • 63
  • 122