Django Testing - access session in RequestFactory - django

I am using RequestFactory in a Django test, and I can't find the right way to access the session variable, and I'm getting the following error when I try
self.factory._session["zip_id"] or self.factory.session["zip_id"].
======================================================================
ERROR: test_middleware (dj_geo.tests.IPToZipMiddleWareTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "c:\dj_site_test\dj_geo\tests.py", line 36, in test_middleware
assert self.factory._session["zip_id"] != None
AttributeError: 'RequestFactory' object has no attribute '_session'
----------------------------------------------------------------------
#override_settings(MIDDLEWARE_CLASSES=(
'dj_geo.middleware.IPToZipMiddleWare'
))
class IPToZipMiddleWareTest(TestCase):
def test_middleware(self):
Zipcode.syncdb()
assert Zipcode.objects.all().count() > 0
self.factory = RequestFactory()
self.request = self.factory.get('/', {}, **{'REMOTE_ADDR':'108.31.178.99'})
assert self.factory._session["zip_id"] != None
assert self.factory._session["zip_id"] != ""

Save session information to request using your middleware:
from django.contrib.sessions.middleware import SessionMiddleware
request = RequestFactory().get('/')
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()

You can use SessionMiddleware, indeed. However, its constructor requires a callback, as any middleware. The callback is provided by Django run-time in order to keep processing the middleware chain or to execute the view as soon as the chain reaches the end. Since we are not interested in view execution, for this case, you may do the following:
from django.contrib.sessions.middleware import SessionMiddleware
request = RequestFactory().get('/')
middleware = SessionMiddleware(lambda x: None)
middleware.process_request(request)
request.session.save()
By processing the request, session field will be added to it and you can keep going with your testing.

You may need to use the SessionMiddleware to process your request, then save it to store the session. You can refer to this article. I also don't think it's a good idea to access the protected attributes of the factory directly, like this self.factory._session["zip_id"], it will just get you into more problems. Goodluck!

You need to use Client for this instead of RequestFactory
self.factory = Client()

Related

How to pass extra keyword arguments when using Django RequestFactory?

I'm trying to write a simple unit test for a view but I'm having trouble passing extra keyword arguments to the view when I'm using RequestFactory to set up the request.
To start, here's the urlpattern:
# app/urls.py
# Example URL: localhost:8000/run/user/1/foo
urlpatterns = [
url(r'^user/(?P<uid>\d+)/(?P<uname>\w+)/$',
views.user_kw,
name='user-kw'),
]
Here's the view I'm testing:
# app/views.py
def user_kw(request, *args, **kwargs):
uid = kwargs['uid']
uname = kwargs['uname']
return render(request, 'run/user.html', context)
Finally, here's the test:
# app/tests.py
def test_user_kw(self):
factory = RequestFactory()
# ???
request = factory.post('user/')
response = views.user_kw(request)
self.assertEqual(response.status_code, 200)
As you might expect, when I run the test, I get this error:
======================================================================
ERROR: test_user_kw (run.tests.TestViews)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/jones/code/django/testing/run/tests.py", line 53, in test_user_kw
response = views.user_kw(request, {"uid": 1, "uname": "foobar"})
File "/Users/jones/code/django/testing/run/views.py", line 28, in user_kw
uid = kwargs['uid']
KeyError: 'uid'
----------------------------------------------------------------------
The Django documentation on the RequestFactory object doesn't discuss this situation. I looked at the RequestFactory code itself but I couldn't figure out how to set up the object to account for the two keyword arguments contained in the URL. I also couldn't find anything online addressing this situation.
I should add that I did manage to write a test for the case in which I used positional arguments and it works:
def test_user_pos(self):
factory = RequestFactory()
request = factory.post('user/')
response = views.user_pos(request, 1, 'foo')
self.assertEqual(response.status_code, 200)
I just can't figure out how to rewrite the test for keyword arguments. Perhaps I've been looking at the problem for too long and the answer is staring me in the face, but I just don't see it.
You can pass keyword arguments to the user_pos method the normal way:
response = views.user_kw(request, uid=1, uname='foo')
Your error message shows that you tried:
response = views.user_kw(request, {"uid": 1, "uname": "foobar"})
This isn't passing keyword arguments, it's passing a dictionary as a positional argument. Note that you can use ** to unpack the dictionary:
response = views.user_kw(request, **{"uid": 1, "uname": "foobar"})

Flask global exception handling

How could one handle exceptions globally with Flask? I have found ways to use the following to handle custom db interactions:
try:
sess.add(cat2)
sess.commit()
except sqlalchemy.exc.IntegrityError, exc:
reason = exc.message
if reason.endswith('is not unique'):
print "%s already exists" % exc.params[0]
sess.rollback()
The problem with try-except is I would have to run that on every aspect of my code. I can find better ways to do that for custom code. My question is directed more towards global catching and handling for:
apimanager.create_api(
Model,
collection_name="models",
**base_writable_api_settings
)
I have found that this apimanager accepts validation_exceptions: [ValidationError] but I have found no examples of this being used.
I still would like a higher tier of handling that effects all db interactions with a simple concept of "If this error: show this, If another error: show something else" that just runs on all interactions/exceptions automatically without me including it on every apimanager (putting it in my base_writable_api_settings is fine I guess). (IntegrityError, NameError, DataError, DatabaseError, etc)
I tend to set up an error handler on the app that formats the exception into a json response. Then you can create custom exceptions like UnauthorizedException...
class Unauthorized(Exception):
status_code = 401
#app.errorhandler(Exception)
def _(error):
trace = traceback.format_exc()
status_code = getattr(error, 'status_code', 400)
response_dict = dict(getattr(error, 'payload', None) or ())
response_dict['message'] = getattr(error, 'message', None)
response_dict['traceback'] = trace
response = jsonify(response_dict)
response.status_code = status_code
traceback.print_exc(file=sys.stdout)
return response
You can also handle specific exceptions using this pattern...
#app.errorhandler(ValidationError)
def handle_validation_error(error):
# Do something...
Error handlers get attached to the app, not the apimanager. You probably have something like
app = Flask()
apimanager = ApiManager(app)
...
Put this somewhere using that app object.
My preferred approach uses decorated view-functions.
You could define a decorator like the following:
def handle_exceptions(func):
#wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except ValidationError:
# do something
except HTTPException:
# do something else ...
except MyCustomException:
# do a third thing
Then you can simply decorate your view-functions, e.g.
#app.route('/')
#handle_exceptions
def index():
# ...
I unfortunately do not know about the hooks Flask-Restless offers for passing view-functions.

HttpResponse object becomes string when passed to assertContains

I have a strange problem in a Django template test. When the test executes my view, the view returns an HttpResponse object. However, when I then pass that response object to the Django TestCase assertContains method, the response object becomes a string. Since this string doesn't have a 'status_code' attribute like a response object does, the test fails. Here's my code:
template_tests.py
from django.test import TestCase
from django.test.client import RequestFactory
class TestUploadMainPhotoTemplate(TestCase):
def setUp(self):
self.factory = RequestFactory()
def test_user_selects_non_jpeg_photo_file(self):
"""
User is trying to upload a photo file via a form
with an ImageField. However, the file doesn't have
a '.jpg' extension so the form's is_valid function, which
I've overridden, flags this as an error and returns False.
"""
with open('photo.png') as test_photo:
request = self.factory.post(reverse('upload-photo'),
{'upload_photo': '[Upload Photo]',
'photo': test_photo})
kwargs = {'template': 'upload_photo.html'}
response = upload_photo(request, **kwargs)
# pdb.set_trace()
self.assertContains(response, 'Error: photo file must be a JPEG file')
When I run this code in the debugger and do 'type(response)' before I call assertContains, I can see that 'response' is a HttpResponse object. However, when assertContains is called, I get this error:
AttributeError: 'str' object has no attribute 'status_code'
I set an additional breakpoint in the assertContains method at the location .../django/test/testcases.py:638:
self.assertEqual(response.status_code, status_code...
At this point, when I do 'type(response)' again, I see that it has become a string object and doesn't have a status_code attribute. Can anyone explain what's going on? I've used this same test pattern successfully in a dozen other template tests and it worked in all of them. Could it have something to do with the fact that this test involves uploading a file?
Thanks.
I had a similar problem and solved it by looking at assertContains, it doesn't really help you but who knows ?
void assertContains( SimpleTestCase self, WSGIRequest response, text, count = ..., int status_code = ..., string msg_prefix = ..., bool html = ... )
Asserts that a response indicates that some content was retrieved
successfully, (i.e., the HTTP status code was as expected), and that
text occurs count times in the content of the response.
If count is None, the count doesn't matter - the assertion is true
if the text occurs at least once in the response.
Could it have something to do with the fact that this test involves uploading a file?
Sure, as I successfully wrote my test for a simple HttpResponse :
response = self.client.get('/administration/', follow=True)
self.assertContains(response, '<link href="/static/css/bootstrap.min.css" rel="stylesheet">',msg_prefix="The page should use Bootstrap")
So I am not really helping, but maybe this could help somebody a little.
I had a similar problem handling Json Response .
self.assertEquals(json.loads(response.content),{'abc': True})
Following fixed the problem for me.

Why don't my Django unittests know that MessageMiddleware is installed?

I'm working on a Django project and am writing unittests for it. However, in a test, when I try and log a user in, I get this error:
MessageFailure: You cannot add messages without installing django.contrib.messages.middleware.MessageMiddleware
Logging in on the actual site works fine -- and a login message is displayed using the MessageMiddleware.
In my tests, if I do this:
from django.conf import settings
print settings.MIDDLEWARE_CLASSES
Then it outputs this:
('django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware')
Which appears to show the MessageMiddleware is installed when tests are run.
Is there an obvious step I'm missing?
UPDATE
After suggestions below, it does look like it's a settings thing.
I currently have settings/__init__.py like this:
try:
from settings.development import *
except ImportError:
pass
and settings/defaults.py containing most of the standard settings (including MIDDLEWARE_CLASSES). And then settings.development.py overrides some of those defaults like this:
from defaults import *
DEBUG = True
# etc
It looks like my dev site itself works fine, using the development settings. But although the tests seem to load the settings OK (both defaults and development) settings.DEBUG is set to False. I don't know why, or whether that's the cause of the problem.
Django 1.4 has a expected behavior when you create the request with RequestFactory that can trigger this error.
To resolve this issue, create your request with RequestFactory and do this:
from django.contrib.messages.storage.fallback import FallbackStorage
setattr(request, 'session', 'session')
messages = FallbackStorage(request)
setattr(request, '_messages', messages)
Works for me!
A way to solve this quite elegant is to mock the messages module using mock
Say you have a class based view named FooView in app named myapp
from django.contrib import messages
from django.views.generic import TemplateView
class FooView(TemplateView):
def post(self, request, *args, **kwargs):
...
messages.add_message(request, messages.SUCCESS, '\o/ Profit \o/')
...
You now can test it with
def test_successful_post(self):
mock_messages = patch('myapp.views.FooView.messages').start()
mock_messages.SUCCESS = success = 'super duper'
request = self.rf.post('/', {})
view = FooView.as_view()
response = view(request)
msg = _(u'\o/ Profit \o/')
mock_messages.add_message.assert_called_with(request, success, msg)
In my case (django 1.8) this problem occurs in when unit-test calls signal handler for user_logged_in signal, looks like messages app has not been called, i.e. request._messages is not yet set. This fails:
from django.contrib.auth.signals import user_logged_in
...
#receiver(user_logged_in)
def user_logged_in_handler(sender, user, request, **kwargs):
...
messages.warning(request, "user has logged in")
the same call to messages.warning in normal view function (that is called after) works without any issues.
A workaround I based on one of the suggestions from https://code.djangoproject.com/ticket/17971, use fail_silently argument only in signal handler function, i.e. this solved my problem:
messages.warning(request, "user has logged in",
fail_silently=True )
Do you only have one settings.py?
Tests create custom (tests) database. Maybe you have no messages there or something... Maybe you need setUp() fixtures or something?
Need more info to answer properly.
Why not simply do something like ? You sure run tests in debug mode right?
# settings.py
DEBUG = True
from django.conf import settings
# where message is sent:
if not settings.DEBUG:
# send your message ...
This builds on Tarsis Azevedo's answer by creating a MessagingRequest helper class below.
Given say a KittenAdmin I'd want to get 100% test coverage for:
from django.contrib import admin, messages
class KittenAdmin(admin.ModelAdmin):
def warm_fuzzy_method(self, request):
messages.warning(request, 'Can I haz cheezburger?')
I created a MessagingRequest helper class to use in say a test_helpers.py file:
from django.contrib.messages.storage.fallback import FallbackStorage
from django.http import HttpRequest
class MessagingRequest(HttpRequest):
session = 'session'
def __init__(self):
super(MessagingRequest, self).__init__()
self._messages = FallbackStorage(self)
def get_messages(self):
return getattr(self._messages, '_queued_messages')
def get_message_strings(self):
return [str(m) for m in self.get_messages()]
Then in a standard Django tests.py:
from django.contrib.admin.sites import AdminSite
from django.test import TestCase
from cats.kitten.admin import KittenAdmin
from cats.kitten.models import Kitten
from cats.kitten.test_helpers import MessagingRequest
class KittenAdminTest(TestCase):
def test_kitten_admin_message(self):
admin = KittenAdmin(model=Kitten, admin_site=AdminSite())
expect = ['Can I haz cheezburger?']
request = MessagingRequest()
admin.warm_fuzzy_method(request)
self.assertEqual(request.get_message_strings(), expect)
Results:
coverage run --include='cats/kitten/*' manage.py test; coverage report -m
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Destroying test database for alias 'default'...
Name Stmts Miss Cover Missing
----------------------------------------------------------------------
cats/kitten/__init__.py 0 0 100%
cats/kitten/admin.py 4 0 100%
cats/kitten/migrations/0001_initial.py 5 0 100%
cats/kitten/migrations/__init__.py 0 0 100%
cats/kitten/models.py 3 0 100%
cats/kitten/test_helpers.py 11 0 100%
cats/kitten/tests.py 12 0 100%
----------------------------------------------------------------------
TOTAL 35 0 100%
This happened to me in the login_callback signal receiver function when called from a unit test, and the way around the problem was:
from django.contrib.messages.storage import default_storage
#receiver(user_logged_in)
def login_callback(sender, user, request, **kwargs):
if not hasattr(request, '_messages'): # fails for tests
request._messages = default_storage(request)
Django 2.0.x
I found when I had a problem patching messages the solution was to patch the module from within the class under test (obsolete Django version BTW, YMMV). Pseudocode follows.
my_module.py:
from django.contrib import messages
class MyClass:
def help(self):
messages.add_message(self.request, messages.ERROR, "Foobar!")
test_my_module.py:
from unittest import patch, MagicMock
from my_module import MyClass
class TestMyClass(TestCase):
def test_help(self):
with patch("my_module.messages") as mock_messages:
mock_messages.add_message = MagicMock()
MyClass().help() # shouldn't complain about middleware
If you're seeing a problem in your Middleware, then you're not doing "Unit Test". Unit tests test a unit of functionality. If you interact with other parts of your system, you're making something called "integration" testing.
You should try to write better tests, and this kind of problems shouldn't arise. Try RequestFactory. ;)
def test_some_view(self):
factory = RequestFactory()
user = get_mock_user()
request = factory.get("/my/view")
request.user = user
response = my_view(request)
self.asssertEqual(status_code, 200)

How to have django give a HTTP response before continuing on to complete a task associated to the request?

In my django piston API, I want to yield/return a http response to the the client before calling another function that will take quite some time. How do I make the yield give a HTTP response containing the desired JSON and not a string relating to the creation of a generator object?
My piston handler method looks like so:
def create(self, request):
data = request.data
*other operations......................*
incident.save()
response = rc.CREATED
response.content = {"id":str(incident.id)}
yield response
manage_incident(incident)
Instead of the response I want, like:
{"id":"13"}
The client gets a string like this:
"<generator object create at 0x102c50050>"
EDIT:
I realise that using yield was the wrong way to go about this, in essence what I am trying to achieve is that the client receives a response right away before the server moves onto the time costly function of manage_incident()
This doesn't have anything to do with generators or yielding, but I've used the following code and decorator to have things run in the background while returning the client an HTTP response immediately.
Usage:
#postpone
def long_process():
do things...
def some_view(request):
long_process()
return HttpResponse(...)
And here's the code to make it work:
import atexit
import Queue
import threading
from django.core.mail import mail_admins
def _worker():
while True:
func, args, kwargs = _queue.get()
try:
func(*args, **kwargs)
except:
import traceback
details = traceback.format_exc()
mail_admins('Background process exception', details)
finally:
_queue.task_done() # so we can join at exit
def postpone(func):
def decorator(*args, **kwargs):
_queue.put((func, args, kwargs))
return decorator
_queue = Queue.Queue()
_thread = threading.Thread(target=_worker)
_thread.daemon = True
_thread.start()
def _cleanup():
_queue.join() # so we don't exit too soon
atexit.register(_cleanup)
Perhaps you could do something like this (be careful though):
import threading
def create(self, request):
data = request.data
# do stuff...
t = threading.Thread(target=manage_incident,
args=(incident,))
t.setDaemon(True)
t.start()
return response
Have anyone tried this? Is it safe? My guess is it's not, mostly because of concurrency issues but also due to the fact that if you get a lot of requests, you might also get a lot of processes (since they might be running for a while), but it might be worth a shot.
Otherwise, you could just add the incident that needs to be managed to your database and handle it later via a cron job or something like that.
I don't think Django is built either for concurrency or very time consuming operations.
Edit
Someone have tried it, seems to work.
Edit 2
These kind of things are often better handled by background jobs. The Django Background Tasks library is nice, but there are others of course.
You've turned your view into a generator thinking that Django will pick up on that fact and handle it appropriately. Well, it won't.
def create(self, request):
return HttpResponse(real_create(request))
EDIT:
Since you seem to be having trouble... visualizing it...
def stuff():
print 1
yield 'foo'
print 2
for i in stuff():
print i
output:
1
foo
2