Django Test Client client.post() sends GET request? - django

I am new to Python and Django. I did a experiment on enforcing request method (e.g. for certain url you can only use GET). Here is my code.
tests.py
from django.test import TestCase, Client
client = Client()
class MyTests(TestCase):
def test_request_method:
""" Sending wrong request methods should result in 405 error """
self.assertEqual(client.post('/mytest', follow = True).status_code, 405)
urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name = 'index'),
url(r'^mytest/', views.mytest, name = 'mytest'),
]
views.py
from django.http import HttpResponse
def mytest(request):
if request.method == 'GET':
return HttpResponse("Not implemented", status = 500)
else:
return HttpResponse("Only GET method allowed", status = 405)
But the test always returns status 500.
I saw here that this may be related to using follow=True in the client.post() call. However if I use follow=False I will get status 301 instead.
Any ideas? Thank you!

Does it perhaps redirect /mytest to /mytest/? The documentation suggests that by default, a trailing slash is added by doing a redirect if no URL pattern matches without the slash, and to quote:
Note that the redirect may cause any data submitted in a POST request to be lost.
A request caused by commonly used redirect status codes is always a GET request. You could either make the request to /mytest/ or remove the trailing slash from your URL pattern.

Related

Django. 301 Redirect from old URL to new

Hello! Please tell me how to organize a redirect correctly.
There is an old version of the site and a new one. In the old version (another CMS, not Django) the objects have their own URL, in the new revised scheme, and the objects have a different URL.
In each object on the new site, there is a completed field with the old URL. In model.py it looks like this:
old_url = models.CharField('Old URL', blank=True, max_length=100)
I specifically moved the old url to a separate field. Or was it not necessary to do this?
Question. How to set up a redirect correctly, so that after going to the object using the old URL, the site visitor will be redirected to the new URL of this object?
IMHO, I don't think writting old_url for each and every object is pretty inefficient. Instead you can implement a custom 404 view, and handle the redirection there.
I think you can create some regex or plain url maps to new url and redirect accordingly.
import re
from django.http import HttpResponseNotFound
OLD_URL_MAP = { 'old_url_regex': 'new_url_path'}
def handler404(self, request):
for old_re, new_url in OLD_URL_MAP.items():
if re.match(old_re, request.path):
return redirect(new_url, request.resolver_match.kwargs)
return HttpResponseNotFound('not found')
# inside urls.py
handler404 = 'myapp.views.handler404'
Here I have used a map hard coded in python, you can create a model for that as well.
Update
A costly solution is to use middleware. You can try like this:
import re
from django.urls import resolve
OLD_URL_MAP = { 'old_url_regex': 'new_url_path'}
class RerouteMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
try:
resolve(request.path_info) # trying to find if the current url exists in your django project. if not, it will throw exception.
except:
for old_re, new_url in OLD_URL_MAP.items(): # iterating through urls
if re.match(old_re, request.path):
return redirect(new_url, request.resolver_match.kwargs)
response = self.get_response(request)
return response
And add that middleware at the bottom of MIDDLEWARE settings.
FYI, its a regex based solution, assuming those urls are dynamic. Instead you can use plain text urls, but its up to you.
Use redirect() from django.shortcuts [the same library from where you import render]. Also, assuming, that the old_url contains only the relative url.
from django.shortcuts import render, redirect
def someView(request):
q = ~some queryset returning the current object~
current_url = request.get_full_path().strip("http://www.example.com/")
if q.old_url == current_url:
redirect(q.new_url)
else:
pass
Remember, redirect() returns an HttpResponse.

How to find the location URL in a Django response object?

Let's say I have a Django response object.
I want to find the URL (location).
However, the response header does not actually contain a Location or Content-Location field.
How do I determine, from this response object, the URL it is showing?
This is old, but I ran into a similar issue when doing unit tests. Here is how I solved the problem.
You can use the response.redirect_chain and/or the response.request['PATH_INFO'] to grab redirect urls.
Check out the documentation as well! Django Testing Tools: assertRedirects
from django.core.urlresolvers import reverse
from django.test import TestCase
class MyTest(TestCase)
def test_foo(self):
foo_path = reverse('foo')
bar_path = reverse('bar')
data = {'bar': 'baz'}
response = self.client.post(foo_path, data, follow=True)
# Get last redirect
self.assertGreater(len(response.redirect_chain), 0)
# last_url will be something like 'http://testserver/.../'
last_url, status_code = response.redirect_chain[-1]
self.assertIn(bar_path, last_url)
self.assertEqual(status_code, 302)
# Get the exact final path from the response,
# excluding server and get params.
last_path = response.request['PATH_INFO']
self.assertEqual(bar_path, last_path)
# Note that you can also assert for redirects directly.
self.assertRedirects(response, bar_path)
The response does not decide the url, the request does.
The response gives you the content of the response, not the url of it.

Call an API on my server from another view

So, kind of a in a weird situation here. I've got a Django project using TastyPie to power its API and some views/templates that will be used to power plugins for various sites. Rather than building this plugins as standard Django templates, I've been asked to use our API to process requests to the plugins, which means I'm calling a view on my server from another view, and for some reason that isn't working with any of my views. For reference:
#views.py
from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.template.context import Context, RequestContext
import json, urllib, urllib2
SITE_NAME = "http://localhost:8000/"
API_PATH = "api/v1/"
def aggregate(request):
template = 'lemonwise/plugins/aggregate.html'
sku = request.GET.get('sku')
url = ''.join([SITE_NAME, API_PATH, 'product/?sku=', sku])
product = json.loads(urllib.urlopen(url).read())['objects'][0]
return render_to_response(template, product)
def reviews(request):
template = 'lemonwise/plugins/reviews.html'
sku = request.GET.get('sku')
url = ''.join([SITE_NAME, API_PATH, 'product/?format=json&sku=', sku])
#Comment the next line out and the url is passed correctly
response = urllib2.build_opener().open(url).read()
return HttpResponse(url)
#page = opener.open(url).read()
#return HttpResponse(url)
#product = json.loads(urllib2.build_opener().open(url).read())['objects'][0]
#return HttpResponse(url)
#reviews = [json.loads(urllib.urlopen(SITE_NAME + uri)) for uri in product['reviews']]
#return render_to_response(template, {'reviews': reviews})
def survey(request):
template = 'lemonwise/plugins/survey.html'
sku = request.GET.get('sku')
url = ''.join([SITE_NAME, API_PATH, 'product/?sku=', sku])
product = json.loads(urllib2.build_opener().open(url).read())['objects'][0]
return render_to_response(template, product)
def mosthelpfulpositive(request):
template = 'lemonwise/plugins/mosthelpfulpositive.html'
sku = request.GET.get('sku')
url = ''.join([SITE_NAME, API_PATH, 'product/?sku=', sku])
product = json.loads(urllib2.build_opener().open(url).read())['objects'][0]
uri = product['most_helpful_positive']
most_helpful_positive = json.loads(urllib.urlopen(SITE_NAME + uri))
return render_to_response(template, most_helpful_positive)
def mosthelpfulnegative(request):
template = 'lemonwise/plugins/mosthelpfulnegative.html'
sku = request.GET.get('sku')
url = ''.join([SITE_NAME, API_PATH, 'product/?sku=', sku])
product = json.loads(urllib2.build_opener().open(url).read())['objects'][0]
uri = product['most_helpful_negative']
most_helpful_negative = json.loads(urllib.urlopen(SITE_NAME + uri))
return render_to_response(template, most_helpful_negative)
And the corresponding urls.py (in a different app):
#urls.py
from django.conf import settings
from django.conf.urls.defaults import patterns, include, url
from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from tastypie.api import Api
from lemonwise.reviews.api import *
admin.autodiscover()
v1_api = Api(api_name='v1')
v1_api.register(UserResource())
v1_api.register(MerchantResource())
v1_api.register(ProductFamilyResource())
v1_api.register(ProductResource())
v1_api.register(BooleanAttributeResource())
v1_api.register(SlideAttributeResource())
v1_api.register(ProductAttributeResource())
v1_api.register(SubmissionResource())
v1_api.register(ReviewResource())
v1_api.register(ReviewProductAttributeResource())
v1_api.register(CommentResource())
v1_api.register(BestUseResource())
v1_api.register(HelpfulVoteResource())
#Acess the api via http://127.0.0.1:8000/api/v1/user/?format=json
urlpatterns = patterns('',
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)),
# Lemonwise apps
url(r'^reviews/', include('lemonwise.reviews.urls')),
#API
url(r'^api/', include(v1_api.urls)),
)
if settings.DEBUG:
urlpatterns += staticfiles_urlpatterns()
Any idea on how to fix this? I can't find any writings on the subject.
EDIT: More specifically, what's happening is that when I load any of these views they hang trying to read the api page. ALso, my server shows no indications of any requests being processed. (Though I can load the api pages directly.)
Now your comment has clarified the situation, I can take a guess at what the problem is. It is that you are using the built-in development server, which is single-threaded. So while it's processing the original request, it's not able to process the internal request for another URL - so it hangs indefinitely.
The solution, as Mao points out, is to think of a better architecture. If you can't do that, you may have some luck with using something like gunicorn instead of the built-in server.
Particular to Tastypie you can use your Resources in views, if you need to, accessing the objects directly and calling the get methods and building the bundle.
You can find out how: http://django-tastypie.readthedocs.org/en/latest/cookbook.html
Go to the section titled - Using Your Resource In Regular Views
Agreed, your development server likely only has one instance. Launch another one on a different port. ie/
SITE_NAME = "http://localhost:8001/"
Okay, so I already said that having views making HTTPRequests against other views is kind of silly, but I do see what your problem currently is. You are calling json.loads(urllib.urlopen(SITE_NAME + uri)), but urllib.urlopen returns a file-like object, not a string, so it should actually be json.load(urllib.urlopen(SITE_NAME + uri)).
I just encountered the same problem i.e. Calling an API from the same server it is hosted. As Daniel suggested, I was able to solve this by using multi threads in gunicorn:
gunicorn --certfile=crt --keyfile=key --bind 0.0.0.0:8000 app.wsgi --threads 5

Django URL Detail

I have to assign to work on one Django project. I need to know about the URL say, http://....
Since with ‘urls.py’ we indeed have ‘raw’ information. How I come to know about the complete URL name; mean with
http+domain+parameters
Amit.
Look at this snippet :
http://djangosnippets.org/snippets/1197/
I modified it like this :
from django.contrib.sites.models import RequestSite
from django.contrib.sites.models import Site
def site_info(request):
site_info = {'protocol': request.is_secure() and 'https' or 'http'}
if Site._meta.installed:
site_info['domain'] = Site.objects.get_current().domain
site_info['name'] = Site.objects.get_current().name
else:
site_info['domain'] = RequestSite(request).domain
site_info['name'] = RequestSite(request).name
site_info['root'] = site_info['protocol'] + '://' + site_info['domain']
return {'site_info':site_info}
The if/else is because of different versions of Django Site API
This snippet is actually a context processor, so you have to paste it in a file called context_processors.py in your application, then add to your settings :
TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + (
'name-of-your-app.context_processors.site_info',
)
The + is here to take care that we d'ont override the possible default context processor set up by django, now or in the future, we just add this one to the tuple.
Finally, make sure that you use RequestContext in your views when returning the response, and not just Context. This explained here in the docs.
It's just a matter of using :
def some_view(request):
# ...
return render_to_response('my_template.html',
my_data_dictionary,
context_instance=RequestContext(request))
HTTPS status would be handled differently by different web servers.
For my Nginx reverse proxy to Apache+WSGI setup, I explicitly set a header that apache (django) can check to see if the connection is secure.
This info would not be available in the URL but in your view request object.
django uses request.is_secure() to determine if the connection is secure. How it does so depends on the backend.
http://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.is_secure
For example, for mod_python, it's the following code:
def is_secure(self):
try:
return self._req.is_https()
except AttributeError:
# mod_python < 3.2.10 doesn't have req.is_https().
return self._req.subprocess_env.get('HTTPS', '').lower() in ('on', '1')
If you are using a proxy, you will probably find it useful that HTTP Headers are available in HttpRequest.META
http://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.META
Update: if you want to log every secure request, use the above example with a middleware
class LogHttpsMiddleware(object):
def process_request(self, request):
if request.is_secure():
protocol = 'https'
else:
protocol = 'http'
print "%s://www.mydomain.com%s" % (protocol, request.path)
Add LogHttpsMiddleware to your settings.py MIDDLEWARE_CLASSES

How to display a custom error page for HTTP status 405 (method not allowed) in Django when using #require_POST

My question is simple, how do I display a custom error page for HTTP status 405 (method not allowed) in Django when using the #require_POST decorator?
I'm using the django.views.decorators.http.require_POST decorator, and when the page is visited by GET request, the console shows a 405 error, but the page is just blank (not even a Django error page). How do I get Django to display a custom and/or default error page for this kind of error?
EDIT:
It's worth mentioning that I've tried putting a 404.html, 500.html and 405.html page in my templates folder - but that does not help either. I have also varied between DEBUG = True and False, to no avail.
You have to write custom Django middleware. You can start with this one and extend it to check if 405.html file exists and so on:
from django.http import HttpResponseNotAllowed
from django.template import RequestContext
from django.template import loader
class HttpResponseNotAllowedMiddleware(object):
def process_response(self, request, response):
if isinstance(response, HttpResponseNotAllowed):
context = RequestContext(request)
response.content = loader.render_to_string("405.html", context_instance=context)
return response
Check docs if you don't know how to install middleware:
http://docs.djangoproject.com/en/dev/topics/http/middleware/
You can also check this article:
http://mitchfournier.com/2010/07/12/show-a-custom-403-forbidden-error-page-in-django/
If you look into the documentation and the source code of django.views.defaults you see that only 404 and 500 errors are supported in a way that you only have to add the 404.html resp. 500.html to your templates directory.
In the doc. you can also read the following
Returning HTTP error codes in Django
is easy. There are subclasses of
HttpResponse for a number of common
HTTP status codes other than 200
(which means "OK"). You can find the
full list of available subclasses in
the request/response documentation.
Thus if you want to return a 405 error, you have to use the HttpResponseNotAllowed class
An example
I'm not sure that's possible. Perhaps you should consider filing a bug report.
I landed here in 2022. The above accepted answer is not working for me. I use django rest framework. My sollution was to create a middleware with
#app/middleware.py
from django.http import HttpResponse
from django.template import loader
class HttpResponseNotAllowedMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
if response.status_code == 405:
context = {}
template = loader.get_template('app/405.html')
return HttpResponse(template.render(context, request))
return response
then install it by adding this to settings
MIDDLEWARE = [
.......
'app.middleware.HttpResponseNotAllowedMiddleware',
]
the 405.html template is just a plain not allowed text