I have a Django app with APIs provided by Tastypie and all works fine on my local and live servers, however I get an error when I try to write a test to call a specific API .
Here is a CURL request (to my local Django server running on Apache):
curl --dump-header - -H "Accept: application/json" -H "Content-Type: application/json" -H "Authorization: ApiKey user:025b513834656753db4bd7b32d66ea2e50ea08f3" -X POST --data '{"digest":"123456789123456789"}' "http://localhost/python/api/v1/tracker/"
and this is the (expected) response:
HTTP/1.1 404 NOT FOUND
Date: Mon, 01 Jul 2013 18:15:53 GMT
Server: Apache/2.2.22 (Ubuntu)
Vary: Accept-Language,Cookie
Content-Language: en-us
Transfer-Encoding: chunked
Content-Type: application/json
{
"error_message": "",
"traceback": "Traceback (most recent call last):\n\n File \"/home/alex/data/development/home_alexlittle_net/venv/lib/python2.7/site-packages/tastypie/resources.py\", line 217, in wrapper\n response = callback(request, *args, **kwargs)\n\n File \"/home/alex/data/development/home_alexlittle_net/venv/lib/python2.7/site-packages/tastypie/resources.py\", line 459, in dispatch_list\n return self.dispatch('list', request, **kwargs)\n\n File \"/home/alex/data/development/home_alexlittle_net/venv/lib/python2.7/site-packages/tastypie/resources.py\", line 491, in dispatch\n response = method(request, **kwargs)\n\n File \"/home/alex/data/development/home_alexlittle_net/venv/lib/python2.7/site-packages/tastypie/resources.py\", line 1357, in post_list\n updated_bundle = self.obj_create(bundle, **self.remove_api_resource_names(kwargs))\n\n File \"/home/alex/data/development/home_alexlittle_net/venv/lib/python2.7/site-packages/tastypie/resources.py\", line 2150, in obj_create\n return self.save(bundle)\n\n File \"/home/alex/data/development/home_alexlittle_net/venv/lib/python2.7/site-packages/tastypie/resources.py\", line 2281, in save\n self.is_valid(bundle)\n\n File \"/home/alex/data/development/django-oppia/oppia/api/resources.py\", line 205, in is_valid\n raise NotFound\n\nNotFound\n"
However, when I run the following test resource case:
class TrackerResourceTest(ResourceTestCase):
fixtures = ['user.json', 'oppia.json']
def setUp(self):
super(TrackerResourceTest, self).setUp()
self.username = 'user'
user = User.objects.get(username=self.username)
api_key = ApiKey.objects.get(user = user)
self.api_key = api_key.key
self.url = '/api/v1/tracker/'
def get_credentials(self):
return self.create_apikey(username=self.username, api_key=self.api_key)
def test_post_digest_not_found(self):
data = {
'digest': '123456789123456789',
}
resp = self.api_client.post(self.url, format='json', data=data, authentication=self.get_credentials())
self.assertHttpNotFound(resp)
I get the following error:
======================================================================
ERROR: test_post_digest_not_found (oppia.tests.TrackerResourceTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/alex/data/development/django-oppia/oppia/tests.py", line 459, in test_post_digest_not_found
resp = self.api_client.post(self.url, format='json', data=data, authentication=self.get_credentials())
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/tastypie/test.py", line 96, in post
return self.client.post(uri, **kwargs)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/client.py", line 463, in post
response = super(Client, self).post(path, data=data, content_type=content_type, **extra)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/client.py", line 297, in post
return self.request(**r)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/client.py", line 406, in request
response = self.handler(environ)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/client.py", line 111, in __call__
response = self.get_response(request)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/core/handlers/base.py", line 178, in get_response
response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/core/handlers/base.py", line 224, in handle_uncaught_exception
return callback(request, **param_dict)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/utils/decorators.py", line 91, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/views/defaults.py", line 41, in server_error
return http.HttpResponseServerError(template.render(Context({})))
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 140, in render
return self._render(context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/utils.py", line 65, in instrumented_test_render
return self.nodelist.render(context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 830, in render
bit = self.render_node(node, context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/debug.py", line 74, in render_node
return node.render(context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/loader_tags.py", line 124, in render
return compiled_parent._render(context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/utils.py", line 65, in instrumented_test_render
return self.nodelist.render(context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 830, in render
bit = self.render_node(node, context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/debug.py", line 74, in render_node
return node.render(context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/loader_tags.py", line 156, in render
return self.render_template(self.template, context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/loader_tags.py", line 138, in render_template
output = template.render(context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 140, in render
return self._render(context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/utils.py", line 65, in instrumented_test_render
return self.nodelist.render(context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 830, in render
bit = self.render_node(node, context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/debug.py", line 74, in render_node
return node.render(context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/templatetags/i18n.py", line 50, in render
langs = self.languages.resolve(context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 728, in resolve
value = self._resolve_lookup(context)
File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 771, in _resolve_lookup
(bit, current)) # missing attribute
VariableDoesNotExist: Failed lookup for key [LANGUAGES] in u"[{'False': False, 'None': None, 'True': True}, {}, {}]"
So it seems that it's not picking up the fact that there has been a NotFound exception raised. The NotFound exception is raised by my API Resource is_valid method:
def is_valid(self, bundle, request=None):
exists = False
try:
activity = Activity.objects.get(digest=bundle.obj.digest)
exists = True
except Activity.DoesNotExist:
pass
try:
media = Media.objects.get(digest=bundle.obj.digest)
exists = True
except Media.DoesNotExist:
pass
if not exists:
raise NotFound()
I'm struggling to figure out why I get such a cryptic error message - seems to be looking at the Django templates (?), but since this is with the TastyPie API, this part of the app doesn't use the templating. For info I get the same response from the curl request on my local server whether I have the DEBUG setting set as True or False (as I'd expect).
I'm not clear if this is because there is something wrong in how I've written either the test case or my API resource?
Any help/pointers much appreciated - I'd really like to get the tests working properly.
I've now figured out where I was going wrong... I had made a couple of mistakes:
Firstly the is_valid wasn't in a separate Validation class, it is was a method on the TrackerResource class. I've now moved it to being in it's own Validation class, and referenced this as validation = TrackerValidation() in my TrackerResource.
Secondly, I had the is_valid returning a NotFound error, rather than allowing it to return a set of error messages - this now means that if incorrect data is sent it flags up a 400 Bad Request, rather than the 404 Not Found.
My TrackerValidation class now looks like:
class TrackerValidation(Validation):
def is_valid(self, bundle, request=None):
exists = False
errors = {}
try:
activity = Activity.objects.get(digest=bundle.obj.digest)
exists = True
except Activity.DoesNotExist:
pass
try:
media = Media.objects.get(digest=bundle.obj.digest)
exists = True
except Media.DoesNotExist:
pass
if not exists:
errors['digest'] = [_(u'Digest not found')]
return errors
With this I now get consistent results between curl requests to my local site, and in the testing framework.