Django resolve with GET parameters — raises Resolver404() - django

I need to test if redirecting to a url will result in a 404 error.
Following example in django docs: https://docs.djangoproject.com/en/1.8/ref/urlresolvers/#resolve
I managed to get the following to work. So if the redirect_url raises a 404, we redirect the user to homepage.
redirect_url = '/blog/hello-world'
view, args, kwargs = resolve()
kwargs['request'] = request
try:
view(*args, **kwargs)
except Http404:
self.message_user(...)
return HttpResponseRedirect('/')
However I need to test if the redirect_url with a GET parameter of ?edit also raise 404. As the 'edit' flag is used to return a different queryset. e.g.
if 'edit' in request.GET:
qs = qs.drafts()
else:
qs = qs.public()
And I changed my original code:
redirect_url = '%s?edit' % redirect_url
[...]
However this raise Resolver404.
Full stacktrace here: http://dpaste.com/1DQHH7Q
Now my question is how can I test for HTTP404 error with GET parameters?
The path blog/2015/07/14/sky-limit-only-those-who-arent-afraid-fly/?edit is valid, when I go the url in the browser.. it works as expected.
A dirty solution is to do this:
from django.test.client import Client
client = Client()
resp = client.get(redirect_url)
if not resp.status_code == 404:
return HttpResponseRedirect(redirect_url)
I really don't want to use this.

The resolve() method takes the url without the GET parameters. You could try replacing request.GET.
redirect_url = '/blog/hello-world'
view, args, kwargs = resolve()
request.GET = {'edit': ''}
kwargs['request'] = request
try:
view(*args, **kwargs)
except Http404:
self.message_user(...)
return HttpResponseRedirect('/')
Note that request is the request object for your current view, so be careful be careful if you need the actual request.GET data.

Related

Django prevent direct url access

Lets say I have
.decs
def unwelcome_user(unwelcome_roles=[]):
def decorator(view_func):
def wrapper_func(request, *args, **kwargs):
if request.user.is_authenticated:
for group in request.user.groups.all():
if group.name in unwelcome_roles:
return redirect('main:unwelcome-user-type',
user_type=group.name)
else:
return view_func(request, *args, **kwargs)
else:
return view_func(request, *args, **kwargs)
return wrapper_func
return decorator
.urls
urlpatterns = [
path('error-unwelcome/<str:user_type>', views.unwelcome_user, name='success')
]
.views
#unwelcome_user(unwelcome_roles=['xyz'])
def success(request)
return render(request, 'success_page.html', {})
def unwelcome_user(request, user_type):
return render(request, 'errors/unwelcome_user.html', {})
I can access the unwelcome_user view directly by typing something like mysite.com/error-unwelcome/xyz in the browser even without going through the subsequent process that should result in showing that page.
How do I prevent that?
I'm still new to Django so I couldn't try out the cookie/sessions answers given (I still have to learn sessions). In the meantime, here's what I'm using:
def get_referer(request):
referer = request.META.get('HTTP_REFERER')
if not referer:
return None
return referer
then in any view
def my_view(request):
if not get_referer(request):
raise Http404
return render(request, 'sample.html', {})
Here I'm assuming that if there's no referer, then it means the person typed in the URL into the browser.
If the page is part of a chain of pages for the user to navigate, you will need to pass some state when moving from one page to the next. You can either use a cookie and check the value of the cookie in the view, or pass a GET query parameter:
def success(request):
token = request.GET.get('token', None)
if token is None or not Token.objects.filter(value=token).exists():
return redirect(...) # previous step or beginning
return render(...) # page
Previous pages should create this token. Then pass it in the URL:
/success/?token=<token>
Give this a try
The process_page will only render success.html if your_subsequent_process() returns a True, otherwise, the process.html will be rendered.
def proces_page(request):
"""Your Process View"""
if your_subsequent_process():
return render(request, 'success.html', {})
return render(request, 'proces.html', {})
You can try using sessions [Django docs]. If you are redirecting a user to the page, before redirecting just add a variable to the session indicating that they can access the view.
So just before the redirect:
request.session['is_unwelcome'] = True
return redirect('main:unwelcome-user-type', user_type=group.name)
In the view you would simply check whether the variable is present in the session:
def unwelcome_user(request, user_type):
if 'is_unwelcome' not in request.session:
# Redirect or raise Http 404?
# return redirect('somewhere')
# raise Http404()
del request.session['is_unwelcome']
return render(request, 'errors/unwelcome_user.html', {})

Django - server code response in JsonResponse

Is it possible to add server response code to JsonResponse ? I need server to reply with 404 in some circumstances.
I have following view
def CreateOrAuth(request):
try:
username = request.POST.get("username")
queryset = User.objects.get(username=username)
except Exception as e:
return JsonResponse({'status': 'user with {} not exist'.format(username)})
And I want to add 404 server code here
Yes, you can. Just pass additional parameter status to JsonResponse:
return JsonResponse({'status': 'user with {} not exist'.format(username)}, status=404)

Django testing if a view return 404 before reversing it

In my urls.py I have the following:
url(r'^foo/', FooView.as_view(), name='foo_name'),
FooView may return a 404
so before I do use :
url = reverse('foo_name', args=(,))
I would like to be sure that the view doesn't return a 404.
How can I do that, is there any way to check with resolve ?
The docs for resolve demonstrate this as an example. You need to reverse the url first, then you can resolve the url to get the callable for the view, and run the view to see whether it raises 404. Be careful if the view has an affect (e.g. changes data in the db), since the code will actually run the view.
from django.core.urlresolvers import resolve
from django.http import HttpResponse, Http404
def myview(request):
url = reverse('foo_name', args=(,))
view, args, kwargs = resolve(url)
kwargs['request'] = request
try:
view(*args, **kwargs)
except Http404:
return HttpResponseRedirect('/')
return HttpResponse("URL did not return 404")
Note that this is only testing the url for your current request. A url may return a 404 depending many factors including the time of the request, the logged in user, or request method.
If it is to simply test if there is a match in your urls.py, you could:
try:
url = reverse('foo_name', args=(,))
except NoReverseMatch:
# do sth
else:
# do sth else
This does not test if the specific view that would be called would eventually return 404.
Reference: Django docs

How can I test a 403 error?

I have a Test Class, that test the access of all page with different user.
Those access are defined by decorator on each of my views.
views.py :
#login_required
def afficher(request):
...
...
#creation_permission_required
def ajouter(request):
...
...
Some of these decorator are defined by me.
decorators.py :
def creation_permission_required(function):
#wraps(function)
#login_required
def decorateur(request, *k, **a):
user = get_object_or_404(User, username__iexact=request.user.username)
if user.is_superuser or user.get_profile().creation:
return function(request, *k, **a)
else:
return HttpResponseRedirect(reverse("non_autorise"))# <--- PROBLEM
return decorateur
return function
When I test them, I use the status_code attribute to verify if the user can access or not the page
test.py :
c = Client()
c.login(username='aucun', password='aucun')
for url in self.url_aucun:
r = c.get(reverse(url['url'], args=url['args']))
self.assertEqual(r.status_code, 200)
for url in self.url_creation:
r = c.get(reverse(url['url'], args=url['args']))
self.assertEqual(r.status_code, 302) # <--- SECOND PROBLEM
When a user doesn't have the right to access a page, the page should return a 403 error (forbidden). How can I do to test 403 instead of 302 ?
EDIT : I tried to use HttpResponseForbidden(reverse("non_autorise")), but couldn't get any content. So then I tried to make my own HttpResponse which is an exact copy of HttpResponseRedirect but with another status_code (403) still didn't get any content...
decorators.py :
class HttpResponseTest(HttpResponse):
def __init__(self, redirect_to):
super(HttpResponseTest, self).__init__()
self['Location'] = iri_to_uri(redirect_to)
self.status_code = 403
def creation_permission_required(function):
#wraps(function)
#login_required
def decorateur(request, *k, **a):
user = get_object_or_404(User, username__iexact=request.user.username)
if user.is_superuser or user.get_profile().creation:
return function(request, *k, **a)
else:
return HttpResponseTest(reverse("non_autorise"))# <--- PROBLEM
return decorateur
return function
If you want a 403 response, you can raise a PermissionDenied exception in your decorator if you are using Django 1.4. Alternatively, you can return a HttpResponseForbidden in your decorator. You will also have to build a custom login_required decorator.
self.assertEqual(r.status_code, 403)
I had this same issue and solved it by instructing the test get() to follow the redirect using follow=True. Using BlueMagma's example it would look something like this:
for url in self.url_creation:
r = c.get(reverse(url['url'], args=url['args']), follow=True)
self.assertEqual(r.status_code, 403) # <--- SECOND PROBLEM NO MORE!!!
Hope this helps someone else!

django anonymous_required custom decorator clashing with password_reset views

I am using the built-in auth_views.password_reset(_confirm, _done, _complete)functionalities, and I would like to limit access to these views only to non-logged-in (anonymous) users, because it doesn't make sense for logged-in users to reset their password.
I found the opposite of #login_required at this link: http://passingcuriosity.com/2009/writing-view-decorators-for-django/
The decorator works for auth_views.password_reset. I use it in my urls as such
url(r'^password/reset/$',
anonymous_required(auth_views.password_reset),
name='auth_password_reset'),
For some reason it does not work with the other 3 views. For example the following url:
url(r'^password/reset/done/$',
anonymous_required(auth_views.password_reset_done),
name='auth_password_reset_done'),
gives me the following error:
Exception Value:
Reverse for 'django.contrib.auth.views.password_reset_done' with arguments '()' and keyword arguments '{}' not found.
Can anyone tell me why?
The decorator code given is:
def anonymous_required(function=None, home_url=None, redirect_field_name=None):
"""Check that the user is NOT logged in.
This decorator ensures that the view functions it is called on can be
accessed only by anonymous users. When an authenticated user accesses
such a protected view, they are redirected to the address specified in
the field named in `next_field` or, lacking such a value, the URL in
`home_url`, or the `USER_HOME_URL` setting.
"""
if home_url is None:
home_url = settings.USER_HOME_URL
def _dec(view_func):
def _view(request, *args, **kwargs):
if request.user.is_authenticated():
url = None
if redirect_field_name and redirect_field_name in request.REQUEST:
url = request.REQUEST[redirect_field_name]
if not url:
url = home_url
if not url:
url = "/"
return HttpResponseRedirect(url)
else:
return view_func(request, *args, **kwargs)
_view.__name__ = view_func.__name__
_view.__dict__ = view_func.__dict__
_view.__doc__ = view_func.__doc__
return _view
if function is None:
return _dec
else:
return _dec(function)
I see password_reset includes a reverse of the routine mentioned in the error message. Is it trying to do this reverse but you have overridden it, or not included it in the urls.conf?
django/contrib/auth/views.py:141-142 (v1.3)
if post_reset_redirect is None:
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_done')