In the get_object method of class views, can I direct the user to a template instead of returning the object if an if statement fails?
Currently raise Http404("Some message.") works good but it doesn't look nice, I want to use my own template.
I'm trying to do this but with templates:
def get_object(self):
product = Product.objects.get(slug=self.kwargs.get('slug'))
if product.deleted == False:
if product.out_of_stock == False:
return product
else:
raise Http404("This product is sold out.")
# return reverse("404-error", kwargs={"error": "sold-out"})
# return render(request, "custom_404.html", {"error": "sold_out"})
else:
raise Http404("This product is no longer available.")
# return reverse("404-error", kwargs={"error": "deleted"})
# return render(request, "custom_404.html", {"error": "deleted"})
My main goal is to just avoid getting the object. I know I can perform the if statement in the get_context_data method, however I wasn't sure for objects containing sensitive data if there would be any way for a user to access it once it's in the get_object, so I just wanted to avoid getting the object altogether if the condition fails and display a template to the user.
You can use your own view when a 404 error occurs, first create a custom view:
views
from django.shortcuts import render
def handler404(request, *args, **kwargs):
return render(request, template_name='custom_404.html', status=404)
Now you need to override the default 404 view, adds this in your main urls.py file:
urls.py
handler404 = 'appname.views.handler404' # Replaces appname with the name of the app that contains the custom view
Now you can simply raise a Http404 exception to show your custom template (you can keep your actual code).
Related
I have a custom Django view class that inherits from the generic DetailView.
The generic DetailView class sequentially calls its methods get_queryset, get_object, and others to generate an object to pass for a Django template.
Moreover, the generic DetailView class raises Http404 exception within these methods to deal with erroneous situations #.
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
What I am trying to do is simple: I want to return other HTTP status codes to the client if an error is found within these methods. However, because Http404 is the only Django HttpResponse that has a form of exception, it seems like this cannot be achieved easily.
Because HttpResponse is not an exception, I have to return it instead of raising it. Therefore, the customized method becomes the following form.
def get_object(self, queryset=None):
...
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
return HttpResponseBadRequest("Bad request.")
However, above code does not send HttpResponseBadRequest to the client. Instead, it returns HttpResponseBadRequest as an object for a Django template. The client gets 200 status code with an invalid object for the template.
The only possible solution to think is writing some object-checking code within the Django template. However, I wonder if there is any better way to deal with this problem.
Since Django 3.2 we now have an exception BadRequest [Django docs] which will be turned into a HttpResponseBadRequest internally when caught, so you can try catching Http404 and raising BadRequest instead:
from django.core.exceptions import BadRequest
class MyView(SomeGenericView):
...
def get_object(self, queryset=None):
try:
return super().get_object(queryset=queryset)
except Http404 as e:
# raise BadRequest("Bad request.") from e
raise BadRequest("Bad request.")
For some previous version we can try to catch the exception at the dispatch method and return the appropriate response there:
class MyView(SomeGenericView):
...
def dispatch(self, request, *args, **kwargs):
try:
return super().dispatch(request, *args, **kwargs)
except Http404:
return HttpResponseBadRequest("Bad request.")
I have a Django view that looks for a variable in the user's session and, if it can't find it, raises a 500 error and redirects the user to my custom 'project/templates/500.html' page.
# views.py
def process_country(request, template):
try:
country = request.session['country']
except KeyError as e:
msg = "Key %s not found for uid %s" % ('country', request.user.id)
log.error(msg, exc_info=True)
return render(request, '500.html', status=500)
if request.method == "POST":
# do stuff...
pass
else:
form = MyForm(initial={'country': country})
context = {'form': form}
return render(request, template, context)
This view works as intended if the 'country' session variable doesn't exist. However, what I'd like to do is move this ugly block of exception handling code to a helper function:
# views.py
from utils import get_from_session
def process_country(request, template):
country = get_from_session(request, 'country') # Helper
if request.method == "POST":
# do stuff...
pass
else:
form = MyForm(initial={'country': country})
context = {'form': form}
return render(request, template, context)
# utils.py
from django.shortcuts import render
def get_from_session(request, key):
try:
value = request.session[key]
return value
except KeyError as e:
msg = "Key %s not found for uid %s" % (key, request.user.id)
log.error(msg, exc_info=True)
# Neither of these work!
#return render(request, '500.html', status=500)
#render(request, '500.html')
The problem is that in this second version, the user doesn't get redirected to the custom 500.html error page. Instead, Django displays the template passed to the process_country view but it embeds the raw HTML contained in the 500.html page in that template. I guess I could do an HttpResponseRedirect(reverse('500-page')) but that would entail creating a view and it doesn't feel like the right solution. What's going on here? Can I redirect the user to the custom 500.html template from a function and if so, how?
Thanks.
Raise an error this display 500.html in production automaticaly
raise KeyError
But this wrong way, you should never raise exceptions for user. Instead this you may checks country in your view and if it's not exists, for example, display some message for user in template.
In my Django project, I want to restrict a page depending on if they have an access level of 100, 200, etc. I tried making a wrapper for this like login_required, however I'm not sure how to access the user model. Any advice?
Example:
def required_access(access):
if access <= user.access:
print 'The user can access this page, their access level is greater than or equal to whats required.'
else:
print 'This page cannot be viewed, their access level is too low.'
#login_required
#required_access(300)
def foo(request):
return render(request, 'bar.html', {})
Python decorators have access to the wrapped functions arguments.
With this knowledge we could alter our decorator like so, to have access to the request.user object. This is assuming the first argument to my_view() will always be the request object.
def required_access(access=None):
def wrap(func):
def inner(*args, **kwargs):
if access <= args[0].user.access:
print 'The user can access this page, their access level is greater than or equal to whats required.'
else:
print 'This page cannot be viewed, their access level is too low.'
return func(*args, **kwargs)
return inner
return wrap
#login_required
#required_access(access=300)
def foo(request):
return render(request, 'bar.html', {})
Try this..
https://docs.djangoproject.com/en/1.7/topics/auth/default/#limiting-access-to-logged-in-users-that-pass-a-test
from django.contrib.auth.decorators import user_passes_test
def email_check(user):
return user.email.endswith('#example.com')
#user_passes_test(email_check)
def my_view(request):
[...]
I want to override the admin default detail view for an object to use a different template when the object does not exist, i.e.
<mydomain>/admin/<myapp>/<mymodel>/<someidthatdoesexist>/
should render the default object detail view, and
<mydomain>/admin/<myapp>/<mymodel>/<someidthatdoesNOTexist>/
should render a custom template, instead of the default 404 error.
From what I've read I should use django.views.generic.detail.DetailView, but I'm not sure how I can achieve what I want.
I tried:
<b>in urls.py</b>
url(r'^admin/<myapp>/<mymodel>/(?P<pk>\d+)/$', views.MyModelDetailView.as_view(), name='mymodel_detail'),
url(r'^admin/', include(admin.site.urls)),
<b>in models.py</b>
class MyModelDetailView(DetailView):
model = MyModel
def get(self, request, *args, **kwargs):
try:
self.model.objects.get(pk=kwargs['pk'])
return super(MyModelDetailView, self).get(request, **kwargs)
except Http404:
# render custom template
but I get a TemplateDoesNotExist error:
<myapp>/mymodel_detail.html
What template should I set to render the default object detail view when the object exists?
EDIT
From the example given here, no template needs to be set...
The DetailView does not raise TemplateDoesNotExist anywhere in its source code. So the only probable place where the exception is being raised is in your redirect (return redirect(url)).
By the way a very useful place to browse class-based-views code is classy views at http://ccbv.co.uk/projects/Django/1.5/django.views.generic.detail/DetailView/
As for rendering a custom template if the object does not exist, you can easily modify your get function to make that work:
class MyModelDetailView(DetailView):
model = MyModel
template_name = 'object_is_found.html' # <= make sure to have this
def get(self, request, *args, **kwargs):
try:
self.object = self.get_object()
except Http404:
# return custom template
return render(request, 'no_object.html', status=404)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
For my site I created an abstract Model which implements model-level read permissions. That part of the system is completed and works correctly. One of the methods the permissioned model exposes is is_safe(user) which can manually test if a user is allowed to view that model or not.
What I would like to do is add a method to the effect of continue_if_safe which can be called on any model instance, and instead of returning a boolean value like is_safe it would first test if the model can be viewed or not, then in the case of False, it would redirect the user, either to the login page if they aren't already logged in or return a 403 error if they are logged in.
Ideal usage:
model = get_object_or_404(Model, slug=slug)
model.continue_if_safe(request.user)
# ... remainder of code to be run if it's safe down here ...
I peeked at how the get_object_or_404 works, and it throws an Http404 error which seems to make sense. However, the problem is that there don't seem to be equivalent redirect or 403 errors. What's the best way to go about this?
(non-working) continue_if_safe method:
def continue_if_safe(self, user):
if not self.is_safe(user):
if user.is_authenticated():
raise HttpResponseForbidden()
else:
raise HttpResponseRedirect('/account/')
return
Edit -- The Solution
The code for the final solution, in case other "stackers" need some help with this:
In the Abstract Model:
def continue_if_safe(self, user):
if not self.is_safe(user):
raise PermissionDenied()
return
Views are caught by the middleware:
class PermissionDeniedToLoginMiddleware(object):
def process_exception(self, request, exception):
if type(exception) == PermissionDenied:
if not request.user.is_authenticated():
return HttpResponseRedirect('/account/?next=' + request.path)
return None
Usage in the view (very short and sweet):
model = get_object_or_404(Model, slug=slug)
model.continue_if_safe(request.user)
For the forbidden (403) error, you could raise a PermissionDenied exception (from django.core.exceptions).
For the redirecting behaviour, there's no built-in way to deal with it the way you describe in your question. You could write a custom middleware that will catch your exception and redirect in process_exception.
I've made a little middleware that return whatever your Exception class's render method returns. Now you can throw custom exception's (with render methods) in any of your views.
class ProductNotFound(Exception):
def render(self, request):
return HttpResponse("You could ofcourse use render_to_response or any response object")
pip install -e git+http://github.com/jonasgeiregat/django-excepted.git#egg=django_excepted
And add django_excepted.middleware.ExceptionHandlingMiddleware to your MIDDLEWARE_CLASSES.
Django annoying has a solution for this:
from annoying.exceptions import Redirect
...
raise Redirect('/') # or a url name, etc
You want to use decorators for this. Look up login_required for an example. Basically the decorator will allow you check check the safety and then return HttpResponseRedirect() if its not safe.
Your code will end up looking something like this:
#safety_check:
def some_view(request):
#Do Stuff