Django Rest Framework - Calling .update() from an overriden .create() method inside ModelViewSet

489 Views Asked by At

I'm using Django 2.2.x and djangorestframework 3.11.1.

Inside a ModelViewSet I had to override the .create() method to customize the default behavior.

Sometimes I need to really create a new model instance upon receiving a POST http request, sometimes I need to .update() an existing instance but all I can receive are POST requests (the source is an external server I don't have control over). I know it's a bad idea to conflate create and update...

The problem is that whenever my update-instead-of-create logic kicks in, I get the following error:

AssertionError: Expected view EventViewSet to be called with a URL keyword argument named "pk". Fix your URL conf, or set the `.lookup_field` attribute on the view correctly

I tried two other ways to work around this issue:

  • directly call Event.objects.update() but I realized this is a no go for me, since I overrode the get_serializer() method to alter the raw json payload coming from the external server and I don't want to duplicate the customization logic here.
  • calling the django built-in .update_or_create() method but the filtering was a bit too complex and I still have the same problem of the first bullet

My view:

class EventViewSet(LoggingMixin, viewsets.ModelViewSet):
    """this is our main api. It allows an event to be created, retrieved, updated and
    deleted
    """
    def create(self, request, *args, **kwargs):
        # first off, we need to find if there is one event NOT closed for the same
        # customer, device name and ip address
        filters = Q(customer = request.data["Customer"])       & \
                 ~Q(status = "CLOSED")                         & \
                  Q(device_name = request.data["DisplayName"]) & \
                  Q(ip_address = request.data["Address"])
        query_set = Event.objects.filter(filters)

        if len(query_set) == 1:
        # it means there is already an event still NOT closed for that customer/device,
        # so we update the existing event
            # we need to specify which event we are updating
            kwargs["pk"] = str(query_set[0].pk)
            return super().update(request, *args, **kwargs)
        elif len(query_set) == 0:
            # it means there is no open event for that customer/device, hence we need to create it
            return super().create(request, *args, **kwargs)
        # per our business logic, we should never be here, because there should never be more
        # than 1 *open* event for the same customer/device.
        else:
            pass

I am not sure how to fix the AssertionError returned.

Thanks

1

There are 1 best solutions below

0
On BEST ANSWER

Found a solution.

I was very close...what I was missing was:

self.kwargs["pk"] = str(query_set[0].pk)

instead of kwargs["pk"] = str(query_set[0].pk)

As far as I understood, this piece of information is then used inside the ModelViewSet get_object() method to retrieve the actual instance to update.