Skip to content

Commit 07f25b6

Browse files
authored
Display readable errors instead of html (#107)
* Display readable errors instead of html * Refactor Dropzone js into new file and add comments * Fix lint
1 parent 6cbc974 commit 07f25b6

File tree

6 files changed

+92
-56
lines changed

6 files changed

+92
-56
lines changed

OpenOversight/app/__init__.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
from logging.handlers import RotatingFileHandler
55

6-
from flask import Flask, render_template, request
6+
from flask import Flask, jsonify, render_template, request
77
from flask_bootstrap import Bootstrap
88
from flask_limiter import Limiter
99
from flask_limiter.util import get_remote_address
@@ -73,21 +73,33 @@ def create_app(config_name="default"):
7373
# Also log when endpoints are getting hit hard
7474
limiter.logger.addHandler(file_handler)
7575

76-
@app.errorhandler(404)
77-
def page_not_found(e):
78-
return render_template("404.html"), 404
79-
80-
@app.errorhandler(403)
81-
def forbidden(e):
82-
return render_template("403.html"), 403
83-
84-
@app.errorhandler(500)
85-
def internal_error(e):
86-
return render_template("500.html"), 500
76+
# Define error handlers
77+
def create_errorhandler(code, error, template):
78+
"""
79+
Create an error handler that returns a JSON or a template response
80+
based on the request "Accept" header.
81+
:param code: status code to handle
82+
:param error: response error message, if JSON
83+
:param template: template response
84+
"""
8785

88-
@app.errorhandler(429)
89-
def rate_exceeded(e):
90-
return render_template("429.html"), 429
86+
def _handler_method(e):
87+
if request.accept_mimetypes.best == "application/json":
88+
return jsonify(error=error), code
89+
return render_template(template), code
90+
91+
return _handler_method
92+
93+
error_handlers = [
94+
(403, "Forbidden", "403.html"),
95+
(404, "Not found", "404.html"),
96+
(413, "File too large", "413.html"),
97+
(429, "Too many requests", "429.html"),
98+
(500, "Internal Server Error", "500.html"),
99+
]
100+
for code, error, template in error_handlers:
101+
# Pass generated errorhandler function to @app.errorhandler decorator
102+
app.errorhandler(code)(create_errorhandler(code, error, template))
91103

92104
# create jinja2 filter for titles with multiple capitals
93105
@app.template_filter("capfirst")

OpenOversight/app/main/views.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,9 +1307,14 @@ def upload(department_id, officer_id=None):
13071307
file_to_upload = request.files["file"]
13081308
if not allowed_file(file_to_upload.filename):
13091309
return jsonify(error="File type not allowed!"), 415
1310-
image = upload_image_to_s3_and_store_in_db(
1311-
file_to_upload, current_user.get_id(), department_id=department_id
1312-
)
1310+
1311+
try:
1312+
image = upload_image_to_s3_and_store_in_db(
1313+
file_to_upload, current_user.get_id(), department_id=department_id
1314+
)
1315+
except ValueError:
1316+
# Raised if MIME type not allowed
1317+
return jsonify(error="Invalid data type!"), 415
13131318

13141319
if image:
13151320
db.session.add(image)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Initialize dropzone component
3+
* @param id element id
4+
* @param url url to upload to
5+
* @param csrf_token CSRF token
6+
* @return the Dropzone object
7+
*/
8+
function init_dropzone(id, url, csrf_token) {
9+
Dropzone.autoDiscover = false;
10+
11+
let myDropzone = new Dropzone(id, {
12+
url: url,
13+
method: "POST",
14+
uploadMultiple: false,
15+
parallelUploads: 50,
16+
acceptedFiles: "image/png, image/jpeg, image/gif, image/jpg, image/webp",
17+
maxFiles: 50,
18+
headers: {
19+
'Accept': 'application/json',
20+
'X-CSRF-TOKEN': csrf_token
21+
},
22+
init: function() {
23+
this.on("error", function(file, response) {
24+
if (typeof(response) == "object") {
25+
response = response.error;
26+
}
27+
if (response.startsWith("<!DOCTYPE")) {
28+
// Catch any remaining HTML error pages
29+
response = "Upload failed.";
30+
}
31+
file.previewTemplate.appendChild(document.createTextNode(response));
32+
});
33+
}
34+
});
35+
return myDropzone;
36+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{% extends "base.html" %}
2+
{% block title %}File Too Large{% endblock %}
3+
4+
{% block content %}
5+
6+
<div class="container theme-showcase" role="main">
7+
8+
<h1>File Too Large</h1>
9+
<p>The file you are trying to upload is too large. <a href="{{ url_for('main.index') }}">Return to homepage</a>.</p>
10+
11+
</div>
12+
{% endblock %}

OpenOversight/app/templates/submit_image.html

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ <h3>Drop images here to submit photos of officers:</h3>
4646

4747
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
4848
<script src="{{ url_for('static', filename='js/dropzone.js') }}"></script>
49+
<script src="{{ url_for('static', filename='js/init-dropzone.js') }}"></script>
4950

5051
<script>
5152
// Select user's preferred department by default
@@ -63,27 +64,8 @@ <h3>Drop images here to submit photos of officers:</h3>
6364
});
6465
});
6566

66-
Dropzone.autoDiscover = false;
67-
const getURL = (file) => {
68-
return "/upload/department/" + dept_id;
69-
}
70-
71-
let myDropzone = new Dropzone("#my-cop-dropzone", {
72-
method: "post",
73-
url: getURL,
74-
uploadMultiple: false,
75-
parallelUploads: 50,
76-
acceptedFiles: "image/png, image/jpeg, image/gif, image/jpg, image/webp",
77-
maxFiles: 50,
78-
headers: {
79-
'X-CSRF-TOKEN': csrf_token
80-
},
81-
init: function() {
82-
this.on("error", function(file, responseText) {
83-
file.previewTemplate.appendChild(document.createTextNode(responseText));
84-
});
85-
}
86-
});
67+
const getURL = (file) => "/upload/department/" + dept_id;
68+
init_dropzone("#my-cop-dropzone", getURL, csrf_token);
8769
</script>
8870
</div>
8971

OpenOversight/app/templates/submit_officer_image.html

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,13 @@ <h3>Drop images here to submit photos of {{ officer.full_name() }} in {{ officer
2828
{% block js_footer %}
2929
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
3030
<script src="{{ url_for('static', filename='js/dropzone.js') }}"></script>
31+
<script src="{{ url_for('static', filename='js/init-dropzone.js') }}"></script>
3132
<script>
3233
var csrf_token = "{{ csrf_token() }}";
33-
Dropzone.autoDiscover = false;
34-
let myDropzone = new Dropzone("#my-cop-dropzone", {
35-
url: "/upload/department/{{ officer.department_id }}/officer/{{ officer.id }}",
36-
method: "POST",
37-
uploadMultiple: false,
38-
parallelUploads: 50,
39-
acceptedFiles: "image/png, image/jpeg, image/gif, image/jpg",
40-
maxFiles: 50,
41-
headers: {
42-
'X-CSRF-TOKEN': csrf_token
43-
},
44-
init: function() {
45-
this.on("error", function(file, responseText) {
46-
file.previewTemplate.appendChild(document.createTextNode(responseText));
47-
});
48-
}
49-
});
34+
init_dropzone(
35+
"#my-cop-dropzone",
36+
"/upload/department/{{ officer.department_id }}/officer/{{ officer.id }}",
37+
csrf_token
38+
);
5039
</script>
5140
{% endblock %}

0 commit comments

Comments
 (0)