Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ViewSet and additional retrieve URL

I have a django model Donation that I expose as a ViewSet. Now I want to add an additional URL to a second model Shop where a related instance of Donation can be retrieved via the parameter order_id and custom actions can be executed.

# models.py
class Donation(models.Model):
  id = models.AutoField(primary_key=True)
  order_id = models.StringField(help_text='Only unique in combination with field `origin`')
  origin = models.ForeignKey('Shop', on_delete=models.PROTECT)

class Shop(models.Model):
  id = models.AutoField(primary_key=True)
  

# views.py
class DonationViewSet(mixins.CreateModelMixin,
                     mixins.RetrieveModelMixin,
                     mixins.ListModelMixin,
                     viewsets.GenericViewSet):

  def retrieve(self, request, *args, **kwargs):  
    if kwargs['pk'].isdigit():
      return super(DonationViewSet, self).retrieve(request, *args, **kwargs)
    else:
      shop_id = self.request.query_params.get('shop_id', None)
      order_id = self.request.query_params.get('order_id', None)
      
      if shop_id is not None and order_id is not None:
        instance = Donations.objects.filter(origin=shop_id, order_id=order_id).first()
        if instance is None:
          return Response(status=status.HTTP_404_NOT_FOUND)
        
        return Response(self.get_serializer(instance).data)

      return Response(status=status.HTTP_404_NOT_FOUND)

  @action(methods=['post'], detail=True)
  def custom_action(self, request, *args, **kwargs):
      pass

class ShopViewSet(viewsets.ModelViewSet):
  pass

# urls.py
router = routers.DefaultRouter()

router.register(r'donations', DonationViewSet)
router.register(r'shops', ShopViewSet)
router.register(r'shops/(?P<shop_id>[0-9]+)/donations/(?P<order_id>[0-9]+)', DonationViewSet)

My goal is to have http://localhost:8000/donations point at the entire DonationViewSet. Also I would like to lookup an individual donation, by its combination of shop_id and order_id like follows http://localhost:8000/shops/123/donations/1337/ and also executing the custom action like follows http://localhost:8000/shops/123/donations/1337/custom_action/. The problem I have is that the second url returns an entire queryset, not just a single instance of the model.

like image 653
finngu Avatar asked Mar 02 '23 13:03

finngu


1 Answers

You can also use drf-nested-routers, which will have something like this:

from rest_framework_nested import routers

from django.conf.urls import url

# urls.py
router = routers.SimpleRouter()
router.register(r'donations', DonationViewSet, basename='donations')
router.register(r'shops', ShopViewSet, basename='shops')

shop_donations_router = routers.NestedSimpleRouter(router, r'', lookup='shops')
shop_donations_router.register(
    r'donations', ShopViewSet, basename='shop-donations'
)

# views.py
class ShopViewSet(viewsets.ModelViewSet):
    def retrieve(self, request, pk=None, donations_pk=None):
        # pk for shops, donations_pk for donations

    @action(detail=True, methods=['PUT'])
    def custom_action(self, request, pk=None, donations_pk=None):
        # pk for shops, donations_pk for donations

This is not tested! But in addition to what you already have, this will support:

donations/
donations/1337/
shops/123/donations/1337/
shops/123/donations/1337/custom_action
like image 94
Brian Destura Avatar answered Mar 05 '23 15:03

Brian Destura