Testing a query parameter in django restframework - django

I am testing a query parameter in djnago restframework to retrieve the detail of an object. The query works in the browser but not the test. I think I am not calling the response correctly with:
response = self.client.get(reverse('quote-requests:get-calculator-detail', kwargs={'calculator_code': self.calc1.calculator_code}))
the apps urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('api/v1/quote-requests/', include('quote_requests.urls')),
]
which includes the quotes.urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from quote_requests import views
router = DefaultRouter()
router.register('get-calculator', views.CalculatorCodeDetailViewSet, basename='get-calculator')
app_name = 'quote-requests'
urlpatterns = [
path('', include(router.urls)),
]
The viewset is:
class CalculatorCodeDetailViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = CalculatorCodeSerializer
lookup_field = ('calculator_code')
def get_queryset(self):
return (
Calculator.objects.filter(
calculator_code = self.request.query_params.get('calculator_code',)
)
)
The CalculatorCodeSerializer is:
class CalculatorCodeSerializer(serializers.ModelSerializer):
class Meta:
model = Calculator
fields = (
'name',
'calculator_code',
)
The test is:
def test_retrieving_calculator_detail_with_calculator_code(self):
''' test retrieving detail of a calculator '''
self.calc1 = Calculator.objects.create(
name = "Calculator 1000",
calculator_code = "HH1000",
)
response = self.client.get(reverse('quote-requests:get-calculator-detail', kwargs={'calculator_code': self.calc1.calculator_code}))
serializer = CalculatorCodeSerializer(self.calc1)
self.assertEqual(response.data, serializer.data)
This yields the error:
Traceback (most recent call last):
File "/app/quote_requests/tests/test_calculators_api.py", line 149, in test_retrieving_calculator_detail_with_calculator_code
self.assertEqual(response.data, serializer.data)
AssertionError: {'detail': ErrorDetail(string='Not found.',[14 chars]nd')} != {'name': 'Calculator 1000', 'calculator_cod[368 chars].25'}
When checking the browser link:
http://localhost:8000/api/v1/quote-requests/get-calculator/?calculator_code=HH1000
This works but test fails. Any help setting up the properly would be appreciated.

I think you are using the wrong endpoint name replace this "get-calculator-detail" with this "get-calculator"
def test_retrieving_calculator_detail_with_calculator_code(self):
# test retrieving detail of a calculator
self.calc1 = Calculator.objects.create(
name = "Calculator 1000",
calculator_code = "HH1000",
)
response = self.client.get(reverse('quote-requests:get-
calculator', kwargs={'calculator_code':
self.calc1.calculator_code}))
serializer = CalculatorCodeSerializer(self.calc1)
self.assertEqual(response.data, serializer.data)

With the help of others on Fiverr I have gotten the test to pass. I hesitate to call it answered but I am putting the detail here in case those who know a better way can comment.
TLDR - the response variable was not written correctly either the reverse does not do what I want or it cannot be done with reverse. Either way the change that worked was:
response = self.client.get('/api/v1/quote-requests/get-calculator/', {'calculator_code': self.calc1.calculator_code})
This does not return an iterable object. It returns an OrderDict which looks like this:
[{"name":"Calculator 1000","calculator_code":"HH1000","id":7,}]
I used print(response.content) to check this.
so you need to use the json function to pull the data out and match it to serializer.data - the response from the serializer.
response.data = response.json()[0]
this pulls the "[]" off and formats it like this:
{'name': 'Calculator 1000', 'calculator_code': 'HH1000', 'id': 7, }
The [0] on the end gets the object (I think) and returns it in json format.
The two fiverr users that helped me with this were:
https://www.fiverr.com/tailongk
and
https://www.fiverr.com/asipita

Related

Django url path converter not working in production

I'm using path converter in my django app like so:
# urls.py
from . import views
from django.urls import path
urlpatterns = [
path('articles/<str:collection>', views.ArticleView),
]
# views.py
#login_required
def ArticleView(request, collection):
print(collection)
if collection == "None":
articles_query = ArticleModel.objects.all()
...
This works fine in development for a url suck as : http://localhost:8000/articles/My Collection which gets encoded to http://localhost:8000/articles/My%20Collection, and is decoded properly in the ArticleView. However, in development, I have to edit the view like so to get it to work:
# views.py
import urllib.parse
#login_required
def ArticleView(request, collection):
collection = urllib.parse.unquote(collection)
print(collection)
if collection == "None":
articles_query = ArticleModel.objects.all()
...
Otherwise, the print(collection) shows My%20Collection and the whole logic in the rest of the view fails.
requirements.txt
asgiref==3.2.10
Django==3.1.1
django-crispy-forms==1.9.2
django-floppyforms==1.9.0
django-widget-tweaks==1.4.8
lxml==4.5.2
Pillow==7.2.0
python-pptx==0.6.18
pytz==2020.1
sqlparse==0.3.1
XlsxWriter==1.3.3
pymysql
What am I doing wrong here?
Thanks in advance!
The URL is being urlencoded which encodes spaces as %20. There are a number of other encodings. As you've discovered you need to decode that parameter in order to compare it to what you'd expect. As you've likely realized, if you have a value that actually wants The%20News and not The News, you have no recourse. To handle this people will create a slug field. Django has a model field for this in the framework.
This is typically a URL-friendly, unique value for the record.
Assuming you add a slug = models.SlugField() to ArticleModel, your urls and view can change into:
urlpatterns = [
# Define a path without a slug to identify the show all code path and avoid the None sentinel value.
path('articles', views.ArticleView, name='article-list'),
path('articles/<slug:slug>' views.ArticleView, name='article-slug-list'),
]
#login_required
def ArticleView(request, slug=None):
articles_query = ArticleModel.objects.all()
if slug:
articles_query = articles_query.filter(slug=slug)

Testing Django Rest Framework: how to test hyperlink relations?

I'm trying to create a true unit test for a customized DjangoRestFramework Hyperlinked related field. But I cannot seem to get around this error:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "relatedtestmodel-detail". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
And here is the unit test, stripped down to simplify the example:
from django.conf.urls import url
from django.test import TestCase, override_settings
from api_tests.models import APITestModel, RelatedTestModel
from api_tests.serializers import APITestModelSerializer
def dummy_view(request, pk):
pass
urlpatterns = [
url(r'/path/is/irrelevant/', dummy_view, name='relatedtestmodel-detail')
]
#override_settings(ROOT_URLCONF='tests.test_relations')
class HyperlinkedRelatedFieldTestCase(TestCase):
def setUp(self):
self.parent = APITestModel.objects.create()
self.child = RelatedTestModel.objects.create(parent=self.parent)
assert self.child.parent.id == self.parent.id
def test_to_internal_value_correct_error_message(self):
queryset = APITestModel.objects.all()
serializer = APITestModelSerializer(queryset, many=True, context={'request': None})
expected = [{'foo': 'bar'}]
self.assertEqual(serializer.data, expected)
I more or less lifted the test from https://github.com/encode/django-rest-framework/blob/master/tests/test_relations_hyperlink.py, because I figured who knows best how to unit test DRF than the makers of DRF? But as it stands, my test refuses to run. The error is raised during the test, when I attempt to access serializer.data for the assert.
Notice in particular that I override the settings with a custom urlpatterns (which is this same file, hence the urlpatterns at the top). So I don't understand why DRF thinks that url name doesn't exist - I can clearly see that my url conf in fact has ONLY that view name! I've even gone so far as to edit my actual URL conf and replace it with the single, named, dummy url pattern shown here, and removedthe settings override, just to be sure that it wasn't that the override_settings simply wasn't working, but even then I get the same error.
To my eye, the dummy url pattern is exactly the same as how DRF did it in their tests. Anyone have any ideas what is going on?
A bit more requested context:
api_tests.models:
from django.db import models
class APITestModel(models.Model):
pass
class RelatedTestModel(models.Model):
parent = models.ForeignKey(
APITestModel,
related_name='children',
related_query_name='child'
)
I do not have access to the traceback at this time, but I can confirm it did not pass through any of my code - it was all isolated to the DjangoRestFramework code, basically exclusively relations.py
Preamble
A few things this question is lacking
No definition of APITestModelSerializer
RelatedTestModel is not used in the test and therefore irrelevant for the example
No error stacktrace
No "useful" expected dict for the asserts
APITestModel has no fields so it can't be serialized (your test shouldn't even have run)
Minor things but still relevant
You are creating specific instances of APITestModel and RelatedTestModel in the setUp but in the test you serialize all instances of APITestModel
The line assert self.child.parent.id == self.parent.id should not be in the setUp. It should be in a separate test
My changes
I deleted all irrelevant information for this question mentioned above
I added an integer field to APITestModel
I changed the urlpatterns element from url(...) to path(...)
I added a regex to the relative path
The serializer is a subclass of HyperlinkedModelSerializer and includes fields "url" and "year"
My project and app urls.py files are the "stock" ones (not shown here) to emphasize that this test resolves the path in isolation.
Changed #override_settings(ROOT_URLCONF='tests.test_relations') to #override_settings(ROOT_URLCONF=__name__)
Code
models.py
from django.db import models
class APITestModel(models.Model):
year = models.IntegerField(null=False)
serializers.py
from rest_framework import serializers
from api.models import APITestModel
class APITestModelSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = APITestModel
fields = ["url", "year"]
test_serializers.py
from django.test import TestCase, override_settings
from django.urls import path
from api.models import APITestModel
from api.serializers import APITestModelSerializer
urlpatterns = [
path('whateveryouwant/<int:pk>/', lambda request: None, name='apitestmodel-detail'),
]
#override_settings(ROOT_URLCONF=__name__)
class HyperlinkedRelatedFieldTestCase(TestCase):
def setUp(self):
# Populate db with APITestModel instances
_ = APITestModel.objects.create(year=1960)
_ = APITestModel.objects.create(year=1961)
_ = APITestModel.objects.create(year=1962)
def test_to_internal_value_correct_error_message(self):
queryset = APITestModel.objects.all()
serializer = APITestModelSerializer(queryset, many=True, context={'request': None})
expected = [
{'url': '/whateveryouwant/1/', 'year': 1960},
{'url': '/whateveryouwant/2/', 'year': 1961},
{'url': '/whateveryouwant/3/', 'year': 1962},
]
self.assertEqual(serializer.data, expected)
The other files in the project are the default ones created automatically by django + djangorestframework.
For future readers, I created a github project with this working code and can be found here: https://github.com/Alechan/drf_test_hyperlink_relations

Django REST framework RetrieveAPIView gets empty "id" parameter and returns 404 error

I use Django 1.11 + Django REST Framework. I'm trying to get detailed data about distinct record using generic RetrieveAPIView from Django REST Framework and server returns "HTTP 404 Not Found" result, i.e. no data.
models.py file:
class TestPage( models.Model):
title = models.CharField( size = 120)
counter = models.IntegerField( default = 0)
def __unicode__(self):
return u'' + self.title
serializers.py file:
class TestPageSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'title', 'counter',)
model = models.TestPage
urls.py file:
urlpatterns = [
url( r'^admin/', admin.site.urls),
url( r'^api/', include( 'rest_framework.urls')),
url( r'^api/', include( 'testpages.urls')),
]
testpages/urls.py file:
urlpatterns = [
url( r'^$', views.TestPageList.as_view()),
url( r'^(?P<id>)\d+/$', views.TestPageDetail.as_view()),
]
and at last views.py file:
class TestPageList(generics.ListAPIView):
lookup_field = 'id'
queryset = TestPage.objects.all()
serializer_class = TestPageSerializer
class TestPageDetail(generics.RetrieveAPIView):
lookup_field = 'id'
queryset = TestPage.objects.all()
serializer_class = TestPageSerializer
# def get_object( self):
# print self.kwargs
If I request "http://127.0.0.1:8000/api/" for all records in the list, server returns all records. But if I want to get record by id using "http://127.0.0.1:8000/api/1/" for example, I get the empty result like in the photo below.
[![enter image description here][1]][1]
If I uncomment get_object()-function in last 2 lines, I can see {'id': u''} in terminal i.e. server gets empty parameter 'id'.
What wrong with this code?
The issue is your regular expression matching for id, you're putting \d+ outside of the matching group when it's actually what you want to match:
url(r'^(?P<id>\d+)/$', views.TestPageDetail.as_view())
By the way, to be good REST citizen, you shouldn't add the / at the end of a URL for a specific object.
Note: As mentioned by JPG, adding a name for the resource you're fetching (plural) would be proper RESTful:
url(r'^pages/(?P<id>\d+)$', views.TestPageDetail.as_view())
This way, you fetch page nr. 1 at /api/pages/1
The original problem was the closing parathesis (the regex expression), you were misplaced that.
urlpatterns = [
url(r'^$', views.TestPageList.as_view()),
url(r'^(?P<id>\d+)/$', views.TestPageDetail.as_view()),
^^^ here
]
Apart from that, You should change the urls patterns to a sensible form, as
#testpages/urls.py
urlpatterns = [
url(r'^test/$', views.TestPageList.as_view()),
url(r'^test/(?P<id>\d+)/$', views.TestPageDetail.as_view()),
]
then the list-api will be available on /api/test/ endpoint and the detail-api will be available on /api/test/{id}/

tastypie return empty resource_uri in get

This is the resource
class TagResource(ModelResource):
user = tastypie.fields.ForeignKey(UserResource,'user')
class Meta:
queryset = Tag.objects.all()
resource_name = 'tag'
authorization= Authorization()
object_class = Tag
filtering = {
'name' : ALL,
}
simple get request
http://localhost:8000/api/v1/tag/1/?format=json
returns with empty resource_uri
{"created": "2014-03-26T15:14:11.928068",
"id": 1, "name": "test",
"resource_uri": "", "user": ""}
Why is that ?
I tried
def hydrate_resource_uri(self, bundle):
return bundle.get_resource_uri()
It didn't work and i'm pretty sure it's not supposed to require special care.
What am i missing ?
I know this is old, but I know your problem, I just had it on mine, you have a "namespace" on the API URL include or on any URL includes further up in your URL tree.
I had the same problem, and it was because I forgot to register my resource in the urls.py.
Ensure you have something like this in your urls.py file:
myapi.register(TagResource())
I have created a blog for highlighting the same problem. link to my blog
Tastypie give couple of options to create a Model Resource.
ModelResource
NamespacedModelResource
When namespace is included in urls.py then Tastypie logic of generating resource_uri fails as it also expects the same namespace in the api urls. To overcome this problem either one has to remove the namespace from module level urls.py or implement namespace with Tastypie. First solution looks easy but it may break your application. The below code will help you use second approach.
Api.py
from tastypie.resources import NamespacedModelResource
class EntityResource(NamespacedModelResource):
class Meta:
queryset = Entity.objects.all()
allowed_methods = ['get']
resource_name = 'entity'
authentication = SessionAuthentication()
authorization = Authorization()
mymodule/url.py
from tastypie.api import NamespacedApi
from mymodule.api import EntityResource
v1_api = NamespacedApi(api_name='v1', urlconf_namespace='mymodule')
v1_api.register(EntityResource())
urlpatterns = patterns('',
url(r'^api/', include(v1_api.urls)),
)
Make sure you use same namespace for module and its api urls. The above code will surely generate proper resource_uri.
Try to remove the object_class, I think that if this is ModelResource, you don't need this.
This may be because, for some reason, the api_name is missing.
Try to add it in the resource meta.
For instance, if your resource uri is /api/v1/YourResourceName, try to add in your resource Meta:
api_name = 'v1'
Hope it helps.
Only if someone else get this problem caused by a namespace in urls.py. You have to use NamespacedModelResource instead of ModelResource:
from tastypie.resources import NamespacedModelResource
class TagResource(NamespacedModelResource):
user = tastypie.fields.ForeignKey(UserResource,'user')
class Meta:
queryset = Tag.objects.all()
resource_name = 'tag'
authorization= Authorization()
object_class = Tag
filtering = {
'name' : ALL,
}
and then into your module's urls.py
from tastypie.api import NamespacedApi
v1_api = NamespacedApi(api_name='v1', urlconf_namespace='your_module')
v1_api.register(TagResource())
urlpatterns = patterns(
'',
url(r'^api/', include(v1_api.urls)),
)
Check this post.

mocking a method on django model using post_save signal

So here's something I'm trying to figure out. I've got a method that is triggered by post_save
for this "Story" model. Works fine. What I need to do is figure out how to mock out the test, so I can fake the call and make assertions on my returns. I think I need to patch it somehow, but I've tried a couple different ways without much success. Best i can get is a object instance, but it ignores values I pass in.
I've commented in my test where my confusion lies. Any help would be welcome.
Here's my test:
from django.test import TestCase
from django.test.client import Client
from marketing.blog.models import Post, Tag
from unittest.mock import patch, Mock
class BlogTestCase(TestCase):
fixtures = [
'auth-test.json',
'blog-test.json',
]
def setUp(self):
self.client = Client()
def test_list(self):
# verify that we can load the list page
r = self.client.get('/blog/')
self.assertEqual(r.status_code, 200)
self.assertContains(r, "<h1>The Latest from Our Blog</h1>")
self.assertContains(r, 'Simple JavaScript Date Formatting')
self.assertContains(r, 'Page 1 of 2')
# loading a page out of range should redirect to last page
r = self.client.get('/blog/5/', follow=True)
self.assertEqual(r.redirect_chain, [
('http://testserver/blog/2/', 302)
])
self.assertContains(r, 'Page 2 of 2')
# verify that unpublished posts are not displayed
with patch('requests') as mock_requests:
# my futile attempt at mocking.
# creates <MagicMock> object but not able to call return_values
mock_requests.post.return_value = mock_response = Mock()
# this doesn't get to the magic mock object. Why?
mock_response.status_code = 201
p = Post.objects.get(id=5)
p.published = False
# post_save signal runs here and requests is called.
# Needs to be mocked.
p.save()
r = self.client.get('/blog/')
self.assertNotContains(r, 'Simple JavaScript Date Formatting')
Here's the model:
from django.db import models
from django.conf import settings
from django.db.models import signals
import requests
def update_console(sender, instance, raw, created, **kwargs):
# ignoring raw so that test fixture data can load without
# hitting this method.
if not raw:
update = instance
json_obj = {
'author': {
'alias': 'the_dude',
'token': 'the_dude'
},
'text': update.description,
}
headers = {'content-type': 'application/json'}
path = 'http://testserver.com:80/content/add/'
request = requests(path, 'POST',
json_obj, headers=headers,
)
if request.status_code < 299:
story_id = request.json().get('id')
if story_id:
# disconnect and reconnect signal so
# we don't enter recursion-land
signals.post_save.disconnect(
update_console,
sender = Story, )
update.story_id = story_id
update.save()
signals.post_save.connect(
update_console,
sender = Story, )
else:
raise AttributeError('Error Saving to console, '+ request.text)
class Story(models.Model):
"""Lets tell a story"""
story_id = models.CharField(
blank=True,
max_length=10,
help_text="This maps to the id of the post"
)
slug = models.SlugField(
unique=True,
help_text="This is used in URL and in code references.",
)
description = models.TextField(
help_text='2-3 short paragraphs about the story.',
)
def __str__(self):
return self.short_headline
# add/update this record as a custom update in console
signals.post_save.connect(update_console, sender = Story)
You need to patch requests in the module where it is actually used, i.e.
with patch('path.to.your.models.requests') as mock_requests:
mock_requests.return_value.status_code = 200
mock_requests.return_value.json.return_value = {'id': story_id'}
...
The documentation offers more detailed explanations on where to patch:
patch works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test.
The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined.
Here, you need to patch the name requests inside the models module, hence the need to provide its full path.