0

I've got the following custom action in my view:

class OrderAPIViewSet(viewsets.ViewSet):

    def create(self, request):
        print("Here: working")

    @action(detail=True, methods=['post'])
    def add(self, request, *arg, **kwargs):
        print("HERE in custom action")
        order = self.get_object()
        print(order)

my app's urls.py is:

from rest_framework import routers
from .views import OrderAPIViewSet


router = routers.DefaultRouter()
router.register(r'orders', OrderAPIViewSet, basename='order')

urlpatterns = router.urls

So in my test when I try to access orders/post it works, but when I try to access orders/{pk}/add it fails. I mean, the reverse itself is failing:

ORDERS_LIST_URL = reverse('order-list')
ORDERS_ADD_URL = reverse('order-add')

class PublicOrderApiTests(TestCase):

   def test_sample_test(self):
       data = {}
       res = self.client.post(ORDERS_ADD_URL, data, format='json')

as I said before, I've got a separate test where I use ORDERS_LIST_URL like this:

res = self.client.post(ORDERS_LIST_URL, data, format='json')

but when running the test I'm getting the following error:

ImportError: Failed to import test module: orders.tests Traceback (most recent call last): File "/usr/local/lib/python3.7/unittest/loader.py", line 436, in _find_test_path module = self._get_module_from_name(name) File "/usr/local/lib/python3.7/unittest/loader.py", line 377, in _get_module_from_name import(name) File "/app/orders/tests.py", line 22, in ORDERS_ADD_URL = reverse('order-add') File "/usr/local/lib/python3.7/site-packages/django/urls/base.py", line 87, in reverse return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs)) File "/usr/local/lib/python3.7/site-packages/django/urls/resolvers.py", line 685, in _reverse_with_prefix raise NoReverseMatch(msg) django.urls.exceptions.NoReverseMatch: Reverse for 'order-add' with no arguments not found. 2 pattern(s) tried: ['orders/(?P[^/.]+)/add\.(?P[a-z0-9]+)/?$', 'orders/(?P[^/.]+)/add/$']

---------------------------------------------------------------------- Ran 1 test in 0.000s

FAILED (errors=1)

according to the documentation I shouldn't need to register this endpoint, the router is supposed to do it by itself. What am I missing?

Bond Jerome
  • 95
  • 2
  • 12
  • 3
    remove the usage of `reverse(...)` at the module level and bring it to your class level – JPG Mar 02 '21 at 03:59

1 Answers1

2

The first thing that you've missed is pk in your reverse. Since the add API needs a pk of your Order object, you need to pass it to reverse function. For example:

order_add_url = reverse('order-add', kwargs={'pk': 1})
print(order_add_url)  # which will print '/orders/1/add/'

So I think you should move this part to the body of PublicOrderApiTests's methods since you need a dynamic url per test object.

Another problem is that the ViewSet class does not support self.get_object() and if you want to use this method you should either have your own method or use rest framework GenericViewSet (i.e. from rest_framework.viewsets import GenericViewSet and inherit from this class instead of ViewSet) then you can access the get_object() method. You can also read more about generic views in rest framework docs.

Roham
  • 1,970
  • 2
  • 6
  • 16
  • yes, that was it, thanks. I moved URL consts into `PublicOrderApiTests`. Is there a way to dynamically populate the id or every time I want to call that endpoint I need to do `order_add_url = reverse('order-add', kwargs={'pk': 1})` in the method performing the call? if so, there's no need for the defined const `ORDERS_ADD_URL` right? – Bond Jerome Mar 02 '21 at 15:02
  • Yes you're right... an easy way to do that (dynamically test purpose) is to first create an `Order` object in your method (i.e. `order_instance = Order.objects.create(required_fields_here`) and then use reverse just after creating an object. For example `order_add_url = reverse('order-add', kwargs={'pk': order_instance.id})`. – Roham Mar 03 '21 at 05:10
  • one last question. I thought this method could be used as template to construct other urls but doesn't seem so. I wanted to create a remove endpoint, something like `/order/{order_id}/remove/{item_id}` so I created an `@action` for remove and in my test I defined `url=reverse('order-remove' kwargs={'pk':1,'order_id:'1'}` but it's getting me an error: `django.urls.exceptions.NoReverseMatch: Reverse for 'order-remove' with keyword arguments '{'pk': 12345, 'product_id': 1}' not found.` any idea why? – Bond Jerome Mar 06 '21 at 05:30