Why does Django trigger GET after POST? - python

I have the following view which takes either a URL or an uploaded text file, creates a Word Cloud and finally displays the generated image to the user.
def create(request):
"""
Displays the generated WordCloud from the
given URI or uploaded file
"""
response = HttpResponse(content_type="image/png")
# in order to avoid KeyError
myfile = request.FILES.get('myfile', None)
if request.POST['uri'] == '' and myfile is None:
return render(request, 'nube/index.html', {
'error_message': NOTHING_TO_PROCESS
})
try:
if myfile:
cloud = WordCloud(myfile, type="upload")
else:
cloud = WordCloud(request.POST['uri'], type="internet")
except (MissingSchema):
return render(request, 'nube/index.html', {
'error_message': URI_COULD_NOT_BE_PROCESSED
})
else:
# img is a PIL.Image instance
img = cloud.get_word_cloud_as_image()
img.save(response, 'PNG')
return response
The image is displayed with no problems; the POST request is processed properly, as can be seen from the log:
[16/Jan/2018 22:53:25] "POST /nube/create HTTP/1.1" 200 216961
However, even though the server didn't crash, I noticed an Exception was raised every time immediately after:
Internal Server Error: /nube/create
Traceback (most recent call last):
File "C:\repos\phuyu\venv\lib\site-packages\django\utils\datastructures.py", line 77, in __getitem__
list_ = super().__getitem__(key)
KeyError: 'uri'
After debugging the code I noticed that my create view was being called once again, but this time as a GET request, and of course, the parameters uri and myfile didn't exist this time, thus raising the exceptions.
To make sure, I changed create into a class based view and only defined its post method. As suspected, now I got the following line in the logs after the successful POST:
Method Not Allowed (GET): /nube/create
[16/Jan/2018 22:44:41] "GET /nube/create HTTP/1.1" 405 0
What would be the correct way to handle this? I'm fairly new to Django.

As #usman-maqbool suggested, the problem was actually in my WordCloud clode, especifically, in get_word_cloud_as_image(), a trailing semicolon:
def get_word_cloud_as_image(self):
return self.img;
After removing it, there's no more sneaky GET requests. I'm not sure why it had that effect. I'd appreciate if someone could clarify.

Related

How to set cookie values and then immediately redirect to the next page and display what's in them

So I need to store two variables from one view and redirect to another view and display them. The obvious solution would be to use sessions but I don't have access to sessions because I don't have a database for this project so I'm trying to do sessions client sided because this isn't really a security issue anyways. This is my attempt so far:
View where I set the cookies:
response = HttpResponse('/result')
response.set_cookie('passes', True)
response.set_cookie('errors', [])
v = jsonschema.Draft4Validator(schema)
#Uses lazy validation to add all errors to validationErrors array
for error in v.iter_errors(jsonFile):
validationErrors.append(error)
response.set_cookie('passes', False)
for error in validationErrors:
error.schma_path = error.schema_path.__str__()[5:]
print error.schma_path
print error.schema_path
response.set_cookie('errors',validationErrors)
...
return redirect('/result')
View where I try to get the cookies:
passes = request.COOKIES.get('passes',"Doesn't Exist")
errors = request.COOKIES.get('errors', "Doesn't Exist")
return render(request, 'result.html', context = {'errors': errors, 'passes': passes})
passes and errors isn't set because they both return Doesn't Exist. How would I do this without returning to the original page? I don't want to return response where response = render_to_response(current view's template) because it defeats the purpose of what I'm trying to do.
You are not getting any cookies values, because actually after assigning the response a cookie, the function returns another Http response.
response = HttpResponse('/result') # first Http Response
response.set_cookie('errors', [])
...
return redirect('/result') # An other one
In the last line, you return another response.
so you should return the same response:
response = redirect('/result') # redirect is here
response.set_cookie('errors', [])
...
return response

Flask and error handling BadRequest exception

I have simple view like that:
#blueprint.route('/')
def index():
'''CMS splash page!'''
print request.form['no-key-like-that']
return render_template('home/splash.html', title='Welcome')
The goal of that view is to cause BadRequest error on Flask. That happens and I got very generic error page in the process that says:
Bad Request
The browser (or proxy) sent a request that this server could not understand.
However I want to intercept all errors and serve them wrapped in our templates for better look and feel, I do this like that:
#app.errorhandler(TimeoutException)
def handle_timeout(error):
if utils.misc.request_is_xhr(request):
return jsonify({'api_timeout': True}), 503
return redirect(url_for('errors.api_timeout', path=request.path))
However the same cannot be done for BadRequest exception, I tried:
#app.errorhandler(werkzeug.exceptions.BadRequestError)
def handle_bad_request(error):
return render_template(
'errors/bad_request.html',
exception_message=unicode(error),
return_path=request.path), 400
And:
#app.errorhandler(werkzeug.exceptions.BadRequestKeyError)
def handle_bad_request(error):
return render_template(
'errors/bad_request.html',
exception_message=unicode(error),
return_path=request.path), 400
And:
#app.errorhandler(werkzeug.exceptions.HttpError)
def handle_bad_request(error):
return render_template(
'errors/bad_request.html',
exception_message=unicode(error),
return_path=request.path), 400
In each case, instead of my bad_request.html there is raw response I mentioned above.
Bad Request
The browser (or proxy) sent a request that this server could not understand.
What actually works for me:
# NOTE: for some reason we cannot intercpet BadRequestKeyError, didn't
# figure out why. Thus I have added check if given 400 is
# BadRequestKeyError if so display standard api error page. This happens
# often when dev tries to access request.form that does not exist.
#app.errorhandler(400)
def handle_bad_request(error):
if isinstance(error, werkzeug.exceptions.BadRequestKeyError):
if utils.misc.request_is_xhr(request):
return jsonify({
'api_internal_error': True,
'error': unicode(error)
}), 500
return render_template(
'errors/api_internal.html',
exception_message=unicode(error),
return_path=request.path), 500
However as you can see it's far from perfection as error 400 is not necessarily always BadRequestKeyError.
Because exception handling works for any other exception but not BadRequest family it keeps me wondering, is it a bug? Or perhaps I am doing something wrong.

How to catch all exceptions with CherryPy?

I use CherryPy to run a very simple web server. It is intended to process the GET parameters and, if they are correct, do something with them.
import cherrypy
class MainServer(object):
def index(self, **params):
# do things with correct parameters
if 'a' in params:
print params['a']
index.exposed = True
cherrypy.quickstart(MainServer())
For example,
http://127.0.0.1:8080/abcde:
404 Not Found
The path '/abcde' was not found.
Traceback (most recent call last):
File "C:\Python27\lib\site-packages\cherrypy\_cprequest.py", line 656, in respond
response.body = self.handler()
File "C:\Python27\lib\site-packages\cherrypy\lib\encoding.py", line 188, in __call__
self.body = self.oldhandler(*args, **kwargs)
File "C:\Python27\lib\site-packages\cherrypy\_cperror.py", line 386, in __call__
raise self
NotFound: (404, "The path '/abcde' was not found.")
Powered by CherryPy 3.2.4
I am trying to catch this exception and show a blank page because the clients do not care about it. Specifically, the result would be an empty body, no matter the url or query string that resulted in an exception.
I had a look at documentation on error handling cherrypy._cperror, but I did not find a way to actually use it.
Note: I gave up using CherryPy and found a simple solution using BaseHTTPServer (see my answer below)
Docs somehow seem to miss this section. This is what I found while looking for detailed explanation for custom error handling from the source code.
Custom Error Handling
Anticipated HTTP responses
The 'error_page' config namespace can be used to provide custom HTML output for
expected responses (like 404 Not Found). Supply a filename from which the
output will be read. The contents will be interpolated with the values
%(status)s, %(message)s, %(traceback)s, and %(version)s using plain old Python
string formatting.
_cp_config = {
'error_page.404': os.path.join(localDir, "static/index.html")
}
Beginning in version 3.1, you may also provide a function or other callable as
an error_page entry. It will be passed the same status, message, traceback and
version arguments that are interpolated into templates
def error_page_402(status, message, traceback, version):
return "Error %s - Well, I'm very sorry but you haven't paid!" % status
cherrypy.config.update({'error_page.402': error_page_402})
Also in 3.1, in addition to the numbered error codes, you may also supply
error_page.default to handle all codes which do not have their own error_page
entry.
Unanticipated errors
CherryPy also has a generic error handling mechanism: whenever an unanticipated
error occurs in your code, it will call
Request.error_response to
set the response status, headers, and body. By default, this is the same
output as
HTTPError(500). If you want to provide
some other behavior, you generally replace "request.error_response".
Here is some sample code that shows how to display a custom error message and
send an e-mail containing the error
from cherrypy import _cperror
def handle_error():
cherrypy.response.status = 500
cherrypy.response.body = [
"<html><body>Sorry, an error occurred</body></html>"
]
sendMail('error#domain.com',
'Error in your web app',
_cperror.format_exc())
#cherrypy.config(**{'request.error_response': handle_error})
class Root:
pass
Note that you have to explicitly set
response.body
and not simply return an error message as a result.
Choose what's most suitable for you: Default Methods, Custom Error Handling.
I don't think you should use BaseHTTPServer. If your app is that simple, just get a lightweight framework (e. g. Flask), even though it might be a bit overkill, OR stay low level but still within the WSGI standard and use a WSGI-compliant server.
CherryPy IS catching your exception. That's how it returns a valid page to the browser with the caught exception.
I suggest you read through all the documentation. I realize it isn't the best documentation or organized well, but if you at least skim through it the framework will make more sense. It is a small framework, but does almost everything you'd expect from a application server.
import cherrypy
def show_blank_page_on_error():
"""Instead of showing something useful to developers but
disturbing to clients we will show a blank page.
"""
cherrypy.response.status = 500
cherrypy.response.body = ''
class Root():
"""Root of the application"""
_cp_config = {'request.error_response': show_blank_page_on_error}
#cherrypy.expose
def index(self):
"""Root url handler"""
raise Exception
See this for the example in the documentation on the page mentioned above for further reference.
You can simply use a try/except clause:
try:
cherrypy.quickstart(MainServer())
except: #catches all errors, including basic python errors
print("Error!")
This will catch every single error. But if you want to catch only cherrypy._cperror:
from cherrypy import _cperror
try:
cherrypy.quickstart(MainServer())
except _cperror.CherryPyException: #catches only CherryPy errors.
print("CherryPy error!")
Hope this helps!
import cherrypy
from cherrypy import HTTPError
def handle_an_exception():
cherrypy.response.status = 500
cherrypy.response.headers['content-type'] = 'text/plain;charset=UTF-8'
cherrypy.response.body = b'Internal Server Error'
def handle_a_404(status=None, message=None, version=None, traceback=None):
cherrypy.response.headers['content-type'] = 'text/plain;charset=UTF-8'
return f'Error page for 404'.encode('UTF-8')
def handle_default(status=None, message=None, version=None, traceback=None):
cherrypy.response.headers['content-type'] = 'text/plain;charset=UTF-8'
return f'Default error page: {status}'.encode('UTF-8')
class Root:
"""Root of the application"""
_cp_config = {
# handler for an unhandled exception
'request.error_response': handle_an_exception,
# specific handler for HTTP 404 error
'error_page.404': handle_a_404,
# default handler for any other HTTP error
'error_page.default': handle_default
}
#cherrypy.expose
def index(self):
"""Root url handler"""
raise Exception("an exception")
#cherrypy.expose
def simulate400(self):
raise HTTPError(status=400, message="Bad Things Happened")
cherrypy.quickstart(Root())
Test with:
http://127.0.0.1:8080/
http://127.0.0.1:8080/simulate400
http://127.0.0.1:8080/missing
Though this was the one of the top results when I searched for cherrypy exception handling, accepted answer did not fully answered the question. Following is a working code against cherrypy 14.0.0
# Implement handler method
def exception_handler(status, message, traceback, version)
# Your logic goes here
class MyClass()
# Update configurations
_cp_config = {"error_page.default": exception_handler}
Note the method signature. Without this signature your method will not get invoked.Following are the contents of method parameters,
status : HTTP status and a description
message : Message attached to the exception
traceback : Formatted stack trace
version : Cherrypy version
Maybe you could use a 'before_error_response' handler from cherrypy.tools
#cherrypy.tools.register('before_error_response', priority=90)
def handleexception():
cherrypy.response.status = 500
cherrypy.response.body = ''
And don't forget to enable it:
tools.handleexception.on = True
I gave up using CherryPy and ended up using the follwing code, which solves the issue in a few lines with the standard BaseHTTPServer:
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from urlparse import urlparse, parse_qs
class GetHandler(BaseHTTPRequestHandler):
def do_GET(self):
url = urlparse(self.path)
d = parse_qs(url[4])
if 'c' in d:
print d['c'][0]
self.send_response(200)
self.end_headers()
return
server = HTTPServer(('localhost', 8080), GetHandler)
server.serve_forever()

How do I return the Instagram Realtime subscription challenge?

I'm trying to subscribe to a tag. It appears that the callback URL is being called correctly with a hub.challenge and hub.mode, and I figured out how to access the challenge using self.request.get('hub.challenge'). I thought I was just supposed to echo the challenge, but that doesn't appear to work since I receive the following errors in the GAE logs:
InstagramAPIError: (400) APISubscriptionError-Challenge verification failed. Sent "647bf6dbed31465093ee970577ce1b72", received "
647bf6dbed31465093ee970577ce1b72
".
Here is the full handler:
class InstagramHandler(BaseHandler):
def get(self):
def process_tag_update(update):
update = update
mode = self.request.get('hub.mode')
challenge = self.request.get('hub.challenge')
verify_token = self.request.get('hub.verify_token')
if challenge:
template_values = {'challenge':challenge}
path = os.path.join(os.path.dirname(__file__), '../templates/instagram.html')
html = template.render(path, template_values)
self.response.out.write(html)
else:
reactor = subscriptions.SubscriptionsReactor()
reactor.register_callback(subscriptions.SubscriptionType.TAG, process_tag_update)
x_hub_signature = self.request.headers.get('X-Hub-Signature')
raw_response = self.request.data
try:
reactor.process('INSTAGRAM_SECRET', raw_response, x_hub_signature)
except subscriptions.SubscriptionVerifyError:
logging.error('Instagram signature mismatch')
So returning it as a string worked. I should have payed closer attention to the error message, but it took a helpful person on the Python IRC to point out the extra line breaks in the message. Once I put the template files on one line, it seemed to work. I can now confirm that my app is authorized via Instagram's list subscription URL.

Pyramid on App Engine gets "InvalidResponseError: header values must be str, got 'unicode'

I am using Pyramid 1.3 with the AppEngine 1.6.4 SDK on OS X 10.7.3. I am using Python 2.7 and have threadsafe true in app.yaml.
#view_config(route_name='manager_swms', permission='manager', renderer='manager/swms.jinja2')
def manager_swms(request):
"""Generates blobstore url and passes users swms in swms table"""
# generate url for any form upload that may occur
upload_url = blobstore.create_upload_url('/upload_swm')
user = get_current_user(request)
swms = DBSession.query(SWMS).filter_by(owner_id=int(user.id)).all()
return {
"analytics_id": analytics_id,
"user": get_current_user(request),
"upload_url": upload_url,
"swms": [(x.filename, x.blob_key) for x in swms]
}
class BlobstoreUploadHandler(object):
"""Base class for creation blob upload handlers."""
def __init__(self, *args, **kwargs):
self.__uploads = None
def get_uploads(self, field_name=None):
"""Get uploads sent to this handler.
Args:
field_name: Only select uploads that were sent as a specific field.
Returns:
A list of BlobInfo records corresponding to each upload.
Empty list if there are no blob-info records for field_name.
"""
if self.__uploads is None:
self.__uploads = {}
for key, value in self.request.params.items():
if isinstance(value, cgi.FieldStorage):
if 'blob-key' in value.type_options:
self.__uploads.setdefault(key, []).append(
blobstore.parse_blob_info(value))
if field_name:
try:
return list(self.__uploads[field_name])
except KeyError:
return []
else:
results = []
for uploads in self.__uploads.itervalues():
results += uploads
return results
#view_config(route_name='upload_swm', permission='manager')
class UploadHandler(BlobstoreUploadHandler):
''' Handles redirects from Blobstore uploads. '''
def __init__(self, request):
self.request = request
super(UploadHandler, self).__init__()
def __call__(self):
user = get_current_user(self.request)
for blob_info in self.get_uploads('file'):
new_swm = SWMS(
owner_id = int(user.id),
blob_key = str(blob_info.key()),
filename = blob_info.filename,
size = blob_info.size,
)
DBSession.add(new_swm)
DBSession.flush()
# redirect to swms page
return HTTPFound(location='/manager/swms')
In the above code, manager_swms() generates a page which includes a form for uploading a file into the Blobstore. The form works OK and I can see the blob appear in the Blobstore when the form is used. The redirect from the blobstore POST is then to /upload_swm where I successfully take BlobInfo details and place them into a SQL table. All of this is good and the final thing I want to do is redirect to the first page so another file can be uploaded if need be and I can show the list of files uploaded.
As per Pyramid documentation, I am using HTTPFound(location='/manager/swms') [the original page URL] to try and redirect however I get:
ERROR 2012-03-29 22:56:38,170 wsgi.py:208]
Traceback (most recent call last):
File "/Users/tim/work/OHSPro/var/parts/google_appengine/google/appengine/runtime/wsgi.py", line 196, in Handle
result = handler(self._environ, self._StartResponse)
File "lib/dist/pyramid/router.py", line 195, in __call__
foo = response(request.environ, start_response)
File "lib/dist/pyramid/httpexceptions.py", line 291, in __call__
foo = Response.__call__(self, environ, start_response)
File "lib/dist/webob/response.py", line 922, in __call__
start_response(self.status, headerlist)
File "/Users/tim/work/OHSPro/var/parts/google_appengine/google/appengine/runtime/wsgi.py", line 150, in _StartResponse
_GetTypeName(header[1]))
InvalidResponseError: header values must be str, got 'unicode'
INFO 2012-03-29 22:56:38,174 dev_appserver_blobstore.py:408] Upload handler returned 500
INFO 2012-03-29 22:56:38,193 dev_appserver.py:2884] "POST /_ah/upload/ahJkZXZ-cHJvdG8tc2NvaHNwcm9yGwsSFV9fQmxvYlVwbG9hZFNlc3Npb25fXxgTDA HTTP/1.1" 500 -
AppEngine is clearly objecting to unicode in the HTTP header but I'm not doing anything unusual AFAIK. If I drop into pdb and take a look at the HTTPFound object, the headers are:
ResponseHeaders([('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0'), ('Location', '/manager/swms')])
Why would I get a unicode problem from these?
so it looks like you are overriding appengine's supported webob which is 1.1.1 on the 2.7 runtime. And pyramid 1.3 depends on webob>=1.2. This is most likely the problem because it was the Blobstore handler stuff that was keeping the sdk held back at webob==0.9 until SDK 1.6.4 was released.
FWIW, this problem is expected to be resolved by SDK 1.6.5 (late April). The only reason I know this is because I was trying to get all this crap resolved when the 2.7 runtime was deemed ready for general use, yet the SDK didn't support it. see this issue for more details.
If possible, I would suggest running it with pyramid 1.2, I know that works fine on appengine. And Just hold off on moving to 1.3 for a few weeks. :)
Yet another answer... adding a str() fixes the problem, but not the root cause. I spent hours trying to figure out why one particular redirect raised this error while others didn't, before noticing that the URL for the bad redirect was missing the initial "/".
I have no idea why this is the case - possibly an incomplete path is processed differently from a complete one. But if you hit this error try changing:
self.redirect('home.view')
to:
self.redirect('/home.view')

Categories

Resources