Okay, so as I mentioned, a pretty cool backend for this would be redis. As you're hosting on PythonAnywhere, there's actually a library called redislite
which will provide this functionality using a local file, instead of an actual redis server.
It appears PythonAnywhere, don't support redis but do support persistant storage. What's even better is if you move to a host with an actual redis server later, it should be a two line modification to switch over.
The concept behind this is:
- In the
/join
route, create a UUID to use as a unique_id
and store that with the dict in redis, then pass the unique_id
back to the template, presenting it to the user as a link.
- In the
/pdf
route, get the dict from redis based on the unique_id
in the URL string.
- For bonus points we can set an expiry on the redis key, so after
n
seconds, the link dissapears and a user would have to hit the calculate button again.
First you'll need to add redislite
to the requirements, or pip install redislite
. Then at the top of your Python file include the neccessary stuff to configure the redis connection:
from redislite import StrictRedis
import os
REDIS_DB_PATH = os.path.join('/tmp/my_redis.db')
r = StrictRedis(REDIS_DB_PATH, charset='utf-8', decode_responses=True)
You should also define the expiry time, a prefix which keeps the redis keys unique to this set of routes, and import other tools:
PDF_EXPIRY = 60*60*24 # 1 day in seconds
PREFIX = 'pdf:' # A prefix for our redis keys
from uuid import uuid4
from flask import url_for
Now this next part works because your data is a simple dictionary, with no nesting. I've used a mock version here, then add it to redis with hmset
and also set the expiry.
Then I add the key pdf_link
to your result
dict and set the value to the URL for the PDF, built by flask's url_for
function:
@app.route('/join')
def my_form_post():
# ---calculations done and variables set---
data_dict = {
"loco_length":3.14, "loco_weight":'str', "loco_bweight":'str',
} # a simplified version of your dictionary
unique_id = uuid4().__str__()
r.hmset(PREFIX + unique_id, data_dict)
r.expire(PREFIX + unique_id, PDF_EXPIRY)
result = {str(key): value for key, value in data_dict.items()}
# This creates a URL back to our PDF route, as a key in result
result['pdf_link'] = url_for('pdf',unique_id = unique_id)
return jsonify(result=result)
This results in a redis hash with the key: pdf:86341e77-f655-46d8-93c4-10e945fc3586
and field names/values, as your data_dict
's keys/values. See the HMSET
redis docs for another visulisation of this.
Now in the frontend result.pdf_link
is a valid link to the PDF which looks something like: /pdf/86341e77-f655-46d8-93c4-10e945fc3586
. You can render that in an <a href=>
tag on your template, next to the table.
Requests to that URL are handled by the next route:
@app.route('/pdf/<string:unique_id>')
def pdf(unique_id):
data_dict = r.hgetall(PREFIX + unique_id)
if not data_dict:
return 'Not found or expired', 404
else:
data = write_fillable_pdf(PDF_TEMP_PATH, data_dict)
return send_file(data, mimetype='application/pdf')
This method I think is quite clean, because it avoids building database models for the data which would have been the common approach. With this, you can add more values to data_dict
, as long as you don't nest them, and nothing else needs changed.
If you wanted to add support for a real redis server in the future, you'd just need to change the two lines to:
from redis import StrictRedis
r = StrictRedis(host='newredisserver', charset='utf-8', decode_responses=True)
as the methods we've used are identical between the redis
and redislite
libraries.
Be aware that anyone who has the link to the PDF can download it, provided it hasn't expired, so you probably shouldn't be passing personal info through this without authentication.