Check if inside errorhandler in before_request - flask

In Flask, is there a way to check which error from errorhandler triggered before_request to be executed?
I want to avoid querying the database for users in 500 error pages in case it's a database-related error. Currently, I have a setup like this:
from flask import request, g, app
from my_app import db, generate_my_sitemap
skip_load_user = set()
#app.before_request
def load_user():
g.user = None
if request.endpoint in skip_load_user:
return
if "user_id" in session:
g.user = db.session.get(session["user_id"])
def add_to_skip_load_user(view):
skip_load_user.add(view.__name__)
return view
#app.route('/sitemap.xml')
#add_to_skip_load_user
def my_view_without_auth():
return generate_my_sitemap()
#app.errorhandler(404)
#app.errorhandler(500)
#add_to_skip_load_user
def http_errors(error):
return render_template('http_error.html')
I noticed that in a 404 error handler, endpoint is None, so it's possible to do this:
if not request.endpoint:
return
However, I only want to make an exception for 500 error pages, not 404 error pages. Is there a way to check if before_request was triggered by a 500 errorhandler?
Edit: I did a workaround by not loading user if endpoint is None on before_request, and then calling the function explicitly on error_handler
#app.before_request
def load_user():
...
if request.endpoint is None:
return
...
#app.errorhandler(404)
#app.errorhandler(500)
def http_errors(error):
if getattr(error, 'code', None) == 404:
load_user()
...

Related

How to integrate webargs + marshmallow?

I am only beginner in flask. Trying to integrate marshmallow and webargs. It perfectly works in flask-restful Resource class. But when I use a simple flask route it does not work
routes.py
class UserAPI(Resource):
#use_args(UserSchema())
def post(self, *args):
print(args)
return 'success', 201
def get(self):
return '<h1>Hello</h1>'
#bp.route('/test/', methods=['POST'])
#use_kwargs(UserSchema())
def test2(*args, **kwargs):
print(args)
print(kwargs)
return 'success', 201
api.add_resource(UserAPI, '/', endpoint='user')
I've added error handler which is necessary when using use_args
from webargs.flaskparser import parser, abort
from webargs import core
#parser.error_handler
def webargs_validation_handler(error, req, schema, *, error_status_code, error_headers):
status_code = error_status_code or core.DEFAULT_VALIDATION_STATUS
abort(
400,
exc=error,
messages=error.messages,
)
That's what I'm getting when I make request to Resource endpoint what is normal
And that's what I'm getting when I make request to a simple flask route what is not normal
I want to be able to use both ways
Found answer in webargs docs :)
https://webargs.readthedocs.io/en/latest/framework_support.html#error-handling
from flask import jsonify
# Return validation errors as JSON
#app.errorhandler(422)
#app.errorhandler(400)
def handle_error(err):
headers = err.data.get("headers", None)
messages = err.data.get("messages", ["Invalid request."])
if headers:
return jsonify({"errors": messages}), err.code, headers
else:
return jsonify({"errors": messages}), err.code

Change HTTP Response message

I am using http response to handle exception in my website. I want to show proper message / validation during create and update data. But It shows HTTP responses like Bad Request , Internel server error. Here is my code:
from django import http
from rest_framework import viewsets
class SaleViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
data = request.data
try:
// Some code
return http.JsonResponse(response)
except Exception as e:
logger.error(e)
return http.HttpResponseBadRequest(content=e)
In dialog box message it shows ,"Bad Request". Instead of "Bad Request" in dialog box message I want to show custom message.
I want to do ,
except Exception as e:
logger.error(e)
return http.HttpResponseBadRequest(My message)
You can make use of JsonResponse object
create a custom error response handler
from django.http import JsonResponse
def error_response(message, status, error=None):
response = dict()
response["error"] = error
response["detail"] = message
return JsonResponse(response, status=status)
in your views
except Exception as e:
logger.error(e)
# call custom error_response handler
message = 'My message'
return error_response(message=message, error=e, status=status.HTTP_400_BAD_REQUEST)

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)

Does the Django Rest APIClient class make an actual request?

I am wondering if the request is actually being made via http. In my app I have a test that looks like
class Authenticate(APITestCase):
def setUp(self):
self.client = APIClient()
self.password_for_admin = '123456'
self.admin = User.objects.create_superuser(username='myname', email='email#email.com', password='123456')
self.token = Token.objects.create(user=self.admin)
def test_authenticate(self):
""" comment """
self.client.credentials(HTTP_AUTHORIZATION='Basic ' + base64.b64encode('{}:{}'.format(self.admin.username, self.password_for_admin)))
response = self.client.post('/api/authenticate/')
print response
And in my view I've got:
#api_view(('POST',))
def authenticate(request, format=None):
""" comment """
import pprint
log.debug(pprint.pprint(request))
try:
"asdlfjl"
except Exception, e:
response = "An error occurred, {}".format(e)
return Response(response)
My settings looks like:
INSTALLED_APPS = (
...
'django.contrib.sessions',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
...
)
The request is being printed out as None in my log file. I need to get the session. I tried request.session (which was None) and that's what led me to this question.
I figured it out. The server does send a request using the testserver domain. This was sort of a misleading question and the code was wrong. The user is already authenticated using the rest basic backend by the time they reach this view method.
Through much research I found out that the user was being authenticated by rest but the login method doesn't get called by the rest backend. Since login doesn't get called from a rest backend the session is never attached to the request. I changed the authenticate method to login and I simply called login by doing this:
...
#api_view(('POST',))
def login(request, format=None):
try:
from django.contrib.auth import login
if request.user and request.user.is_active:
login(request, request.user)
...
response = ...
else:
response = {}
except Exception, e:
response = "An error occurred, {}".format(e)
return Response(response)

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!