I've currently got an endpoint that accepts GET requests, to retrieve users within a date range (there's no real need to explain the 'why'). The specific endpoint looks like this: GET /users/?fromdate={yyyy-mm-dd}&todate={yyyy-mm-dd}
For example, I can have a request such as: GET /users/?fromdate=2017-01-01&todate=2017-04-01
Without going too much into detail on the urls.py and views.py, I've specified the router register and path to accept requests to this endpoint (which works fine):
urls.py:
router = routers.DefaultRouter()
router.register(r"user", views.UserViewSet, basename='user')
urlpatterns = [
path('user/', views.UserViewSet.as_view({'get': 'user'}), name='user')
]
I am now trying to run a unit test just to send a request to this endpoint
Part of my test_user.py class looks like this:
def test_user_list(self):
response = self.client.get(reverse('user', kwargs={'fromdate': '2017-01-01', 'todate': '2017-04-01'})), format='json')
self.assertEqual(response.status_code, 200)
However, when I run the unit test, I get an error:
Reverse for 'user' with keyword arguments '{'fromdate': '2017-01-01',
'todate': '2017-04-01'}' not found. 1 pattern(s) tried: ['/user/$']
I think the error is due to the fact that my path (while it works manually through a REST client like Postman) doesn't specify the arguments to expect path('user/', views.UserViewSet.as_view({'get': 'user'}), name='user')
So how do I specify the arguments to satisfy a 'fromdate' and 'todate' as above? I have had a look at the documentation but I couldn't find anything other than urls such as /articles/2005/03/ for example.
kwargs, when passed to reverse(), become URL args, e.g. user/arg1/arg2/
If you want to pass URL vars, the kwargs should be passed to .get() instead, as data:
response = self.client.get(reverse('user'), {'fromdate': '2017-01-01', 'todate': '2017-04-01'})
Related
I've currently got an API endpoint that works as I would expect it when I send a request to it manually via Postman. The way I do so is running:
GET /user/?fromid=1&toid=100
I am now trying to setup unit tests using factory-boy but I'm not able to get it to work due to an error.
So far, the important files with regards to the unit tests that I have are:
urls.py
from django.urls import include, path
from rest_framework import routers
from rest_framework.schemas import get_schema_view
import views
router = routers.DefaultRouter()
router.register(r"user", views.UserViewSet, basename='user')
urlpatterns = [
path('user', views.UserViewSet.as_view({'get': 'user'}), name='user')
]
test_user.py
import factory
from django.test import Client, TestCase
from django.urls import reverse
from factory import DjangoModelFactory, Faker
from models.user import User
class UserFactory(DjangoModelFactory):
class Meta:
model = User
class UserViewSetTest(TestCase):
def setUp(self):
client = Client()
def test_user_list(self):
response = self.client.get(reverse('user'), format='json')
self.assertEqual(response.status_code, 200)
When running pytestusing the above, I get an error:
AttributeError: 'UserViewSet' object has no attribute 'user'
I have a feeling that the parameters in the URL aren't allowing me to call it as is from my unit test, but I'm not sure.
As I mentioned, I've been able to send a request via Postman to the API so I'm not sure it's useful to show my complete code (i.e. the ViewSet, models and serializers). For what it's worth, the viewset currently just returns a list of users in Json format.
EDIT:
I've been able to recreate the error manually on Postman. If I create a request without specifying the URL parameters, like the following:
GET /user
I get the same error manually
AttributeError: 'UserViewSet' object has no attribute 'user'
As a result, I have tried to update my reverse command within the unit test to include the URL parameters, as such:
response = self.client.get(reverse('user'), kwargs={'fromid':1, 'toid': 100}, format='json')
But I'm still getting the same original error.
EDIT2:
I think part of the problem is with the way I'm calling reverse. The kwargs parameter wasn't in the actual reverse function. I've modified it to be as such:
response = self.client.get(reverse('user', kwargs={'fromid':1, 'toid': 100}), format='json')
Now my error is to do with the url path (I believe) as now I'm getting a new error which is:
Reverse for 'user' with keyword arguments '{'fromid':1, 'toid': 100}'
not found. 1 pattern(s) tried: ['/user$']
My (hopefully final) question is how do I specify multiple parameters in my path to accept the kwargs? My endpoint is currently accepted to parameters (fromid and toid) in the URL, so I need to specify that in the path elements below. How can I do so?
router.register(r"user", views.UserViewSet, basename='user')
urlpatterns = [
path('user', views.UserViewSet.as_view({'get': 'user'}), name='user')
]
drf routers will use a name like
basename+'-list'
for generated url with list actions. so your code should be:
response = self.client.get(reverse('user-list', kwargs={'fromid':1, 'toid': 100}), format='json')
I an trying to make url router in Django which supports following URLs :
http://localhost:8000/location/configuration
http://localhost:8000/location/d3d710fcfc1391b0a8182239881b8bf7/configuration
url(r'^locations/configuration$',
location_config.as_view(), name="location-config"),
url(r'^locations/(?P<location_key>[\w]+)/configuration$',
location_config.as_view(), name="location-config-uri")
Whenever I tried to hit http://localhost:8000/location/configuration, it picked up the second URL routing format instead of picking up first one.
Error:
TypeError at /locations/configuration/ get() missing 1 required
positional argument: 'location_key'
Can anyone help me here what goes wrong with the url routing format?
Nope, it does pick the first pattern which has no arguments, however you're using the same view in both patterns and location_config view has required argument location_key which is not provided when first pattern matches the URL. That's what error message is saying.
So write another view which will not require location_key argument or alter this view definition: add default to the parameter
def location_config(request, location_key=None):
....
now it is not a "required positional argument".
django Will look for a pk when you are using a detail view by default. you have to override it by using get_object()
in your case
def get_object(self, queryset=None):
location_key = self.kwargs.get('location_key')
obj = Model.objects.get(id=location_key)
return obj
I want to redirect URL without slug, to the one with slug, at the urls.py level.
My endpoints looks as follows:
(r'/invoices/<:(-?\d+)>/print/', PrintHandler, 'ShortPrintHandler')
(r'/invoices/<:(-?\d+)>/<:([\w-]*)>/print/', PrintHandler, 'FullPrintHandler')
Is there any way I can pass first, decimal, argument from short URL to the long one, on redirect? Generating URLs without slug is already covered at handler level.
Tried to handle it with
RedirectRoute(r'/invoices/<:(-?\d+)>/print/', PrintHandler, redirect_to_name='FullPrintHandler')
But an error was thrown:
KeyError: 'Missing argument "1" to build URI.'
You can't do that with just a RedirectRoute; you need to get the slug value from somewhere.
You'll need to write a standard route, and in the handler you should get the object from the datastore and return a redirect to the full path using the slug.
Something like (untested):
class RedirectToFullPath(webapp2.RequestHandler):
def get(self, invoice_id):
invoice = Invoice.get_by_id(invoice_id)
self.redirect_to('FullPrintHandler', invoice_id, invoice.slug)
While using the Routes library I want to redirect certain URLs. The documentation says it can be achieved like this:
map.redirect("/legacyapp/archives/{url:.*}", "/archives/{url}")
And I am indeed able to redirect to a URL this way. However I am unable to map/parse the URL arguments from the request to the redirect. My code looks like this:
app.mapper.redirect( "/repository/status_for_installed_repository{url:.*}", "/api/repositories/check_updates/{url}" )
and if the app is passed this:
curl -L 'FQDN/repository/status_for_installed_repository?owner=qqqqqq&changeset_revision=e5f6ced3e91f&name=asdsadsadas'
it redirects me to
GET /api/repositories/check_updates
but I cannot find a way how to obtain the values of owner name and changeset_revision.
I expect this to be a common use case as generally you do not want to lose arguments when redirecting?
Any help is much appreciated. Thanks.
I ended up implementing it as follows:
def _map_redirects( app ):
"""
Add redirect to the Routes mapper and forward the received query string.
Subsequently when the redirect is triggered in Routes middleware the request
will not even reach the webapp.
"""
def forward_qs(environ, result):
qs_dict = urlparse.parse_qs(environ['QUERY_STRING'])
for qs in qs_dict:
result[ qs ] = qs_dict[ qs ]
return True
app.mapper.redirect( "/repository/status_for_installed_repository", "/api/repositories/check_updates/", _redirect_code="301 Moved Permanently", conditions=dict( function=forward_qs ) )
return app
can i substitute the 2 url routes
urlpatterns = patterns('',
url(r'service/geticons', 'core.service.geticons'),
url(r'service/getnearby', 'core.service.getnearby'),
by a single, more generic, route that routes all requests to the function in the service module with the name of the last url segment?
thinking about something like
url(r'service/#f', 'core.service.#f')
or must i do such dispatch in the service module in django?
Sure, you could collect the path and point it to a view that returns the function.
url(r'service/(?P<function>\w+)/$', 'core.service.function_router')
def function_router(request, function):
return globals()[function](request)
But, it's probably better just to explicitly set the urls.