I'm writing some simple unit tests for my class based views. I've completed the tests for urls in which I've used args = ['pk'] for urls that had pks. However, when doing similar tests to views, ones with pks ends with the error Field 'id' expected a number but got 'pk' '.
Here's the working URL test:
url = reverse('user', args=['pk'])
self.assertEquals(resolve(url).func.__name__,
GetUser.as_view().__name__)
Class based view test that gets the error:
class TestViews(TestCase):
def setUp(self):
self.client = Client()
self.single_user_url = reverse('user', args=['pk'])
def test_single_user_view(self):
response = self.client.get(self.single_user_url)
self.assertEquals(response.status_code, 200)
urls.py:
urlpatterns = [
path('', GetUsers.as_view(), name='users'),
path('create/', CreateUser.as_view(), name='create-user'),
path('user/<str:pk>/', GetUser.as_view(), name='user'),
path('update/<str:pk>/', UpdateUser.as_view(), name='update-user'),
]
path('user/<int:pk>/', GetUser.as_view(), name='user'),
path('update/<int:pk>/', UpdateUser.as_view(), name='update-user'),
Related
Hello everyone I'm looking to write unit test on my django app, in order to test the different API endpoint but it seems like i can't figure our the problem here is a snap code of what i have done so far :
urls.py :
path('translate/display/', DisplayTranslation.as_view(), name='display_translation'),
it's corresponding DRF view.py :
class DisplayTranslation(generics.ListAPIView):
queryset = Translation.objects.all()
serializer_class = TranslationSerializers
permission_classes = (IsAuthenticated,)
and here is what I have done so far on my unit test.py :
apiclient = APIClient()
class TranslationTestCases(APITestCase):
def setUp(self):
self.role = baker.make(Roles)
self.user = baker.make(Users, user_role=self.role)
self.token = reverse('token_obtain_pair', kwargs={'email': self.user.email, 'password': self.user.password})
self.translation = baker.make(Translation, _quantity=10)
def test_get_all_translations(self):
header = {'HTTP_AUTHORIZATION': 'Token {}'.format(self.token)}
response = self.client.get(reverse('display_translation'), {}, **header)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 10)
This is the error I'm getting when I run the test :
in _reverse_with_prefix raise NoReverseMatch(msg) django.urls.exceptions.NoReverseMatch: Reverse for 'token_obtain_pair' with keyword arguments '{'email': 'dZbQNWCjif#example.com', 'password': 'PfQzPqqVVLAdLZtJyVUxVjkGJEgRABYdHxMRhCGZJWxnZxpxEgUkgUKklENrWlBiYOCxhaDtJXdtXCtNdOJYtSWTzIrdvPnrmezXBNjEYYTpyZWjOLMnMIBMAfYnLwcC'}' not found. 1 pattern(s) tried: ['token/$']
Some more info, in the authentication on my Django app, I worked with DRF, rest_auth & SimpleJWT library.
What I could do to improve my code? or an alternative solution? I couldn't find a similar problem to mine.
You can basically use
#patch.object(DisplayTranslation, "permission_classes", [])
def test_get_all_translations(self):
...
https://docs.python.org/3/library/unittest.mock.html#patch-object
With Authentication:
urlpatterns = [
...
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
...
]
class TranslationTestCases(APITestCase):
def setUp(self):
self.api_client = APIClient()
self.role = baker.make(Roles)
self.user = baker.make(Users, user_role=self.role)
self.token_url = reverse('token_obtain_pair')
self.translation = baker.make(Translation, _quantity=10)
response = self.api_client.post(self.token_url, {"username": self.user.username, "password": self.user.password})
self.token = response.json()["access"]
def test_get_all_translations(self):
header = {'HTTP_AUTHORIZATION': 'Bearer {}'.format(self.token)}
...
https://django-rest-framework-simplejwt.readthedocs.io/en/latest/getting_started.html
I have a ModelAdmin subclass for my gradeScalesSettings model:
#admin.register(gradeScalesSetting)
class gradeScalesSettingAdmin(admin.ModelAdmin):
list_display = ('configuration_select', 'NumberOfGrades', 'Rounding','Precision', 'Status',)
change_list_template = 'admin/Homepage/view.html'
Actual result
After I click Grade Scale Settings:
How to connect it to my views.py?
This is what I want to code in my views.py:
def gradescales(request):
gradeScalesSettings = gradeScalesSetting.objects.all()
configurations = configuration.objects.all()
rounding = gradeScalesSetting.objects.all().values_list('Rounding', flat=True).distinct()
print(rounding)
return render(request, 'Homepage/gradescale.html', {"rounding": rounding,"gradeScalesSetting":gradeScalesSettings,"configurations":configurations})
When I tried this:
#admin.register(gradeScalesSetting)
class gradeScalesSettingAdmin(admin.ModelAdmin):
def new_NumberOfGrades(self, obj):
if obj.NumberOfGrades == 'Grade Scale Settings':
return '<a href="view.html" </a>' # this url will redirect to your
In my ModelAdmin subclass:
list_display = ('configuration_select', 'new_NumberOfGrades', 'Rounding','Precision', 'Status',)
Is there any way to connect it to my views.py?
Expected result
This is what I want to show in my view.html:
That is why I want to connect it to my views.py.
Override ModelAdmin.changelist_view to set extra_context.
#admin.register(gradeScalesSetting)
class gradeScalesSettingAdmin(admin.ModelAdmin):
list_display = ('configuration_select', 'NumberOfGrades', 'Rounding','Precision', 'Status',)
change_list_template = 'admin/Homepage/view.html'
def changelist_view(self, request, extra_context=None):
extra_context = extra_context or {}
gradeScalesSettings = gradeScalesSetting.objects.all()
configurations = configuration.objects.all()
rounding = gradeScalesSetting.objects.all().values_list('Rounding', flat=True).distinct()
extra_context.update({
"rounding": rounding,
"gradeScalesSetting": gradeScalesSettings,
"configurations": configurations,
})
return super().changelist_view(request, extra_context=extra_context)
You can alter routing for ModelAdmin by overriding get_urls:
class gradeScalesSettingAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super().get_urls()
pat = [i for i in urls if str(i.name).endswith('changelist')][0] # Find needed path object to replace
index = urls.index(pat)
urls[index] = path(pat.pattern._route, gradescales)
return urls
This way you can have full control of a view used for your admin page and even add additional pages (more than only changelist and edit views) as you want.
If you do not need additional default views like object edit page, you can simplify code above by replacing original urls instead of searching one needed and patching:
class gradeScalesSettingAdmin(admin.ModelAdmin):
def get_urls(self):
urls = [
path('', gradescales)
]
return urls
I defined a viewset using ModelViewSet as follow
I tried to redefine the GET method to do something like getting something from celery . but this part of code just won't work , it acts just like a standard API and didn't do what I wrote in the get_job_detail function.
How should I correctly define the "detail_route" function.
views.py
class JobViewSet(viewsets.ModelViewSet):
queryset = job.objects.all()
serializer_class = JobSerializer
#detail_route(methods=['get'])
def get_job_detail(self, request, pk=None):
# print('these part wont proceed')
job_item = self.get_object()
if job_item.isReady or job_item.isSuccessful:
return Response(self.serializer_class(job_item).data)
celeryjob = sometask.AsyncResult(pk)
celeryjob.get()
if celeryjob.state == 'SUCCESS':
job_item.state = celeryjob.state
job_item.result = celeryjob.result
job_item.isReady = True
job_item.isSuccessful = True
job_item.save()
if celeryjob.state == 'FAILURE':
job_item.state = celeryjob.state
job_item.result = celeryjob.result
job_item.isReady = True
job_item.isSuccessful = False
job_item.save()
return Response(self.serializer_class(job_item).data)
urls.py
from django.conf.urls import url, include
from apply_api import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'job',views.JobViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
]
now your correct url is: /job/<pk>/get_job_detail if you want just: /job/<pk> you don need to use #detail_route just rename your method to the def retrieve(self, request, *args, **kwargs): more details retrievemodelmixin one of the part class of the modelviewset
A project using Django, DRF, swagger.
url config below:
schema_view = get_swagger_view(title='Pastebin API')
urlpatterns = [
url(r'^$', schema_view),
url(r'store', include('store.urls')),
... # other urls using routers.SimplerRouter
]
and store/urls.py:
router = routers.SimpleRouter()
router.register(r'', views.StoreViewSet)
urlpatterns = router.urls
and views.StoreViewSet:
class StoreViewSet(BaseObject, GenericViewSet):
permition_class = ()
#list_route()
def detail(self, request):
return {}
#list_route(url_path='detail/export')
def detail_export(self, request):
return {}
after python manage.py runserver, visit http://127.0.0.1:8000/# and a TypeError occured:
File "/usr/local/share/.virtualenvs/dev-finance/lib/python2.7/site-packages/rest_framework_swagger/views.py", line 32, in get
schema = generator.get_schema(request=request)
File "/usr/local/share/.virtualenvs/dev-finance/lib/python2.7/site-packages/rest_framework/schemas.py", line 242, in get_schema
links = self.get_links(request)
File "/usr/local/share/.virtualenvs/dev-finance/lib/python2.7/site-packages/rest_framework/schemas.py", line 276, in get_links
insert_into(links, keys, link)
File "/usr/local/share/.virtualenvs/dev-finance/lib/python2.7/site-packages/rest_framework/schemas.py", line 79, in insert_into
target[keys[-1]] = value
TypeError: 'Link' object does not support item assignment
[ERROR] 2017-05-04 15:25:06,936 log_message(basehttp.py:131) "GET / HTTP/1.1" 500 20384
The error message shows, the Link object can't assign value, like dict does.
If I rename the method name from detail_export to details_export, everything goes fine again.
Guessing error occured when rest_framework's #list_route decorater
transfer the method's url to Link object.
why other methods just goes fine?
how can I solve this?
This is the bug in DRF. Probably, it will be fixed in 3.6.4. Now you can:
Delete url_path from list_route:
class StoreViewSet(BaseObject, GenericViewSet):
permition_class = ()
#list_route()
def detail(self, request):
return {}
#list_route()
def detail_export(self, request):
return {}
Or use a custom SchemaView with a custom SchemaGenerator:
# api/schema.py for example
from rest_framework.permissions import AllowAny
from rest_framework.renderers import CoreJSONRenderer
from rest_framework.response import Response
from rest_framework.schemas import SchemaGenerator
from rest_framework.views import APIView
from rest_framework_swagger import renderers
class CustomSchemaGenerator(SchemaGenerator):
def get_keys(self, subpath, method, view):
result = super().get_keys(subpath, method, view)
# here you can fix your path
if result == ['store', 'detail', 'detail_export']:
return ['store', 'detail_export']
return result
class SwaggerSchemaView(APIView):
_ignore_model_permissions = True
exclude_from_schema = True
permission_classes = [AllowAny]
renderer_classes = [
CoreJSONRenderer,
renderers.OpenAPIRenderer,
renderers.SwaggerUIRenderer
]
def get(self, request):
generator = CustomSchemaGenerator(title='API')
return Response(generator.get_schema(request=request))
# urls.py
from django.contrib.auth.decorators import login_required
from api.schema import SwaggerSchemaView
urlpatterns = [
# ...
url(r'^swagger-url/', login_required(SwaggerSchemaView.as_view()))
# ...
]
Here's a hack:
Open
your-environment-folder/lib/python3.5/site-packages/rest_framework/schemas.py
Find "insert_into" function
Replace this:
target[keys[-1]] = value
with this:
if type(target) != coreapi.Link:
target[keys[-1]] = value
def test_post_request_for_api_view(self):
data = {
"email": self.user.email,
}
url = self.reverse('users:the_api', self.user.pk)
response = self.json_post(data, url=url)
self.mock.assert_called_once_with(self.user)
data2 = json.loads(response.content.decode('utf-8'))
self.assertEqual(data2, {
'booking_order': ['ABCDEFGHIJKL'],
'transaction_total': '20000.00'
})
urls.py
urlpatterns = [
url(
regex=r'^$',
view=views.UserListView.as_view(),
name='list'
),
url(
regex=r'^~redirect/$',
view=views.UserRedirectView.as_view(),
name='redirect'
),
url(
regex=r'^(?P<username>[\w.#+-]+)/$',
view=views.UserDetailView.as_view(),
name='detail'
),
url(
regex=r'^~update/$',
view=views.UserUpdateView.as_view(),
name='update'
) ,
url(
regex=r'^the_api/$',
view = views.UserApiView.as_view(),
name='the_api'
),
]
views.py
class UserApiView(APIView):
authentication_classes = authentication.TokenAuthentication ###Am assuming you're authenticating via a token
def get(self, request):
"""
Get user based on username.
Am getting only the username since that's the only field used above.
:param request:
:param format:
:return:
"""
details = User.objects.all()
serializer = UserSerializer(details, many=True)
return Response(serializer.data )
def post(self, request, format=None):
"""
Create a new user instance
:param request:
:param format:
:return:
"""
serializer = UserSerializer(request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
As much as i understand how reverse works in Django, i believe for the above test to pass, i only need to have a the_api url in my urls.py.
However, the test does not pass even with that.
As such, i would love to know what the right thing to do is.
How do i get the test to run?
The error i keep getting is:
django.urls.exceptions.NoReverseMatch: Reverse for 'the_api' with arguments '(1,)' and keyword arguments '{}' not found. 1 pattern(s) tried: ['users:the_api/$']
Do you have accepted argument in your url? Looking at your code you should have following entry in your urls.py file:
url(r'the_api/(\d+)/$', your_view, name="the_api")
May be you should paste your url entry too and the corresponding view so that we can look deeper into the issue.