else
flask template
This commit is contained in:
parent
b0187f3269
commit
cea8ef6007
11 changed files with 481 additions and 0 deletions
51
Templates/flask/decorators.py
Normal file
51
Templates/flask/decorators.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import flask
|
||||||
|
from flask import request
|
||||||
|
import functools
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
def _generate_session_token():
|
||||||
|
token = str(uuid.uuid4())
|
||||||
|
#print('MAKE SESSION', token)
|
||||||
|
return token
|
||||||
|
|
||||||
|
def give_session_token(function):
|
||||||
|
@functools.wraps(function)
|
||||||
|
def wrapped(*args, **kwargs):
|
||||||
|
# Inject new token so the function doesn't know the difference
|
||||||
|
token = request.cookies.get('flasksite_session', None)
|
||||||
|
if not token:
|
||||||
|
token = _generate_session_token()
|
||||||
|
request.cookies = dict(request.cookies)
|
||||||
|
request.cookies['flasksite_session'] = token
|
||||||
|
|
||||||
|
ret = function(*args, **kwargs)
|
||||||
|
|
||||||
|
# Send the token back to the client
|
||||||
|
if not isinstance(ret, flask.Response):
|
||||||
|
ret = flask.Response(ret)
|
||||||
|
ret.set_cookie('flasksite_session', value=token, max_age=60)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
def not_implemented(function):
|
||||||
|
'''
|
||||||
|
Decorator to remember what needs doing.
|
||||||
|
'''
|
||||||
|
warnings.warn('%s is not implemented' % function.__name__)
|
||||||
|
return function
|
||||||
|
|
||||||
|
def time_me(function):
|
||||||
|
'''
|
||||||
|
After the function is run, print the elapsed time.
|
||||||
|
'''
|
||||||
|
@functools.wraps(function)
|
||||||
|
def timed_function(*args, **kwargs):
|
||||||
|
start = time.time()
|
||||||
|
result = function(*args, **kwargs)
|
||||||
|
end = time.time()
|
||||||
|
print('%s: %0.8f' % (function.__name__, end-start))
|
||||||
|
return result
|
||||||
|
return timed_function
|
120
Templates/flask/flasksite.py
Normal file
120
Templates/flask/flasksite.py
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import flask
|
||||||
|
from flask import request
|
||||||
|
import json
|
||||||
|
import mimetypes
|
||||||
|
import os
|
||||||
|
|
||||||
|
import decorators
|
||||||
|
|
||||||
|
|
||||||
|
site = flask.Flask(__name__)
|
||||||
|
site.config.update(
|
||||||
|
SEND_FILE_MAX_AGE_DEFAULT=180,
|
||||||
|
TEMPLATES_AUTO_RELOAD=True,
|
||||||
|
)
|
||||||
|
site.jinja_env.add_extension('jinja2.ext.do')
|
||||||
|
site.debug = True
|
||||||
|
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
####################################################################################################
|
||||||
|
####################################################################################################
|
||||||
|
####################################################################################################
|
||||||
|
|
||||||
|
def make_json_response(j, *args, **kwargs):
|
||||||
|
dumped = json.dumps(j)
|
||||||
|
response = flask.Response(dumped, *args, **kwargs)
|
||||||
|
response.headers['Content-Type'] = 'application/json;charset=utf-8'
|
||||||
|
return response
|
||||||
|
|
||||||
|
def send_file(filepath):
|
||||||
|
'''
|
||||||
|
Range-enabled file sending.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
file_size = os.path.getsize(filepath)
|
||||||
|
except FileNotFoundError:
|
||||||
|
flask.abort(404)
|
||||||
|
|
||||||
|
outgoing_headers = {}
|
||||||
|
mimetype = mimetypes.guess_type(filepath)[0]
|
||||||
|
if mimetype is not None:
|
||||||
|
if 'text/' in mimetype:
|
||||||
|
mimetype += '; charset=utf-8'
|
||||||
|
outgoing_headers['Content-Type'] = mimetype
|
||||||
|
|
||||||
|
if 'range' in request.headers:
|
||||||
|
desired_range = request.headers['range'].lower()
|
||||||
|
desired_range = desired_range.split('bytes=')[-1]
|
||||||
|
|
||||||
|
int_helper = lambda x: int(x) if x.isdigit() else None
|
||||||
|
if '-' in desired_range:
|
||||||
|
(desired_min, desired_max) = desired_range.split('-')
|
||||||
|
range_min = int_helper(desired_min)
|
||||||
|
range_max = int_helper(desired_max)
|
||||||
|
else:
|
||||||
|
range_min = int_helper(desired_range)
|
||||||
|
|
||||||
|
if range_min is None:
|
||||||
|
range_min = 0
|
||||||
|
if range_max is None:
|
||||||
|
range_max = file_size
|
||||||
|
|
||||||
|
# because ranges are 0-indexed
|
||||||
|
range_max = min(range_max, file_size - 1)
|
||||||
|
range_min = max(range_min, 0)
|
||||||
|
|
||||||
|
range_header = 'bytes {min}-{max}/{outof}'.format(
|
||||||
|
min=range_min,
|
||||||
|
max=range_max,
|
||||||
|
outof=file_size,
|
||||||
|
)
|
||||||
|
outgoing_headers['Content-Range'] = range_header
|
||||||
|
status = 206
|
||||||
|
else:
|
||||||
|
range_max = file_size - 1
|
||||||
|
range_min = 0
|
||||||
|
status = 200
|
||||||
|
|
||||||
|
outgoing_headers['Accept-Ranges'] = 'bytes'
|
||||||
|
outgoing_headers['Content-Length'] = (range_max - range_min) + 1
|
||||||
|
|
||||||
|
if request.method == 'HEAD':
|
||||||
|
outgoing_data = bytes()
|
||||||
|
else:
|
||||||
|
outgoing_data = helpers.read_filebytes(filepath, range_min=range_min, range_max=range_max)
|
||||||
|
|
||||||
|
response = flask.Response(
|
||||||
|
outgoing_data,
|
||||||
|
status=status,
|
||||||
|
headers=outgoing_headers,
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
####################################################################################################
|
||||||
|
####################################################################################################
|
||||||
|
####################################################################################################
|
||||||
|
|
||||||
|
@site.route('/')
|
||||||
|
@decorators.give_session_token
|
||||||
|
def root():
|
||||||
|
return flask.render_template('root.html')
|
||||||
|
|
||||||
|
@site.route('/favicon.ico')
|
||||||
|
@site.route('/favicon.png')
|
||||||
|
def favicon():
|
||||||
|
filename = os.path.join('static', 'favicon.png')
|
||||||
|
return flask.send_file(filename)
|
||||||
|
|
||||||
|
@site.route('/static/<filename>')
|
||||||
|
def get_static(filename):
|
||||||
|
filename = filename.replace('\\', os.sep)
|
||||||
|
filename = filename.replace('/', os.sep)
|
||||||
|
filename = os.path.join('static', filename)
|
||||||
|
return flask.send_file(filename)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
#site.run(threaded=True)
|
||||||
|
pass
|
29
Templates/flask/flasksite_launch.py
Normal file
29
Templates/flask/flasksite_launch.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import gevent.monkey
|
||||||
|
gevent.monkey.patch_all()
|
||||||
|
|
||||||
|
import flasksite
|
||||||
|
import gevent.pywsgi
|
||||||
|
import gevent.wsgi
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if len(sys.argv) == 2:
|
||||||
|
port = int(sys.argv[1])
|
||||||
|
else:
|
||||||
|
port = 5000
|
||||||
|
|
||||||
|
if port == 443:
|
||||||
|
http = gevent.pywsgi.WSGIServer(
|
||||||
|
listener=('', port),
|
||||||
|
application=flasksite.site,
|
||||||
|
keyfile='https\\flasksite.key',
|
||||||
|
certfile='https\\flasksite.crt',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
http = gevent.pywsgi.WSGIServer(
|
||||||
|
listener=('', port),
|
||||||
|
application=flasksite.site,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
print('Starting server')
|
||||||
|
http.serve_forever()
|
135
Templates/flask/helpers.py
Normal file
135
Templates/flask/helpers.py
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
import math
|
||||||
|
|
||||||
|
FILE_READ_CHUNK = 512 * 1024
|
||||||
|
|
||||||
|
def chunk_sequence(sequence, chunk_length, allow_incomplete=True):
|
||||||
|
'''
|
||||||
|
Given a sequence, divide it into sequences of length `chunk_length`.
|
||||||
|
|
||||||
|
allow_incomplete:
|
||||||
|
If True, allow the final chunk to be shorter if the
|
||||||
|
given sequence is not an exact multiple of `chunk_length`.
|
||||||
|
If False, the incomplete chunk will be discarded.
|
||||||
|
'''
|
||||||
|
(complete, leftover) = divmod(len(sequence), chunk_length)
|
||||||
|
if not allow_incomplete:
|
||||||
|
leftover = 0
|
||||||
|
|
||||||
|
chunk_count = complete + min(leftover, 1)
|
||||||
|
|
||||||
|
chunks = []
|
||||||
|
for x in range(chunk_count):
|
||||||
|
left = chunk_length * x
|
||||||
|
right = left + chunk_length
|
||||||
|
chunks.append(sequence[left:right])
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
def comma_split(s):
|
||||||
|
'''
|
||||||
|
Split the string apart by commas, discarding all extra whitespace and
|
||||||
|
blank phrases.
|
||||||
|
'''
|
||||||
|
if s is None:
|
||||||
|
return s
|
||||||
|
s = s.replace(' ', ',')
|
||||||
|
s = [x.strip() for x in s.split(',')]
|
||||||
|
s = [x for x in s if x]
|
||||||
|
return s
|
||||||
|
|
||||||
|
def edit_params(original, modifications):
|
||||||
|
'''
|
||||||
|
Given a dictionary representing URL parameters,
|
||||||
|
apply the modifications and return a URL parameter string.
|
||||||
|
|
||||||
|
{'a':1, 'b':2}, {'b':3} => ?a=1&b=3
|
||||||
|
'''
|
||||||
|
new_params = original.copy()
|
||||||
|
new_params.update(modifications)
|
||||||
|
if not new_params:
|
||||||
|
return ''
|
||||||
|
new_params = ['%s=%s' % (k, v) for (k, v) in new_params.items() if v]
|
||||||
|
new_params = '&'.join(new_params)
|
||||||
|
if new_params:
|
||||||
|
new_params = '?' + new_params
|
||||||
|
return new_params
|
||||||
|
|
||||||
|
def fit_into_bounds(image_width, image_height, frame_width, frame_height):
|
||||||
|
'''
|
||||||
|
Given the w+h of the image and the w+h of the frame,
|
||||||
|
return new w+h that fits the image into the frame
|
||||||
|
while maintaining the aspect ratio.
|
||||||
|
'''
|
||||||
|
ratio = min(frame_width/image_width, frame_height/image_height)
|
||||||
|
|
||||||
|
new_width = int(image_width * ratio)
|
||||||
|
new_height = int(image_height * ratio)
|
||||||
|
|
||||||
|
return (new_width, new_height)
|
||||||
|
|
||||||
|
def hms_to_seconds(hms):
|
||||||
|
'''
|
||||||
|
Convert hh:mm:ss string to an integer seconds.
|
||||||
|
'''
|
||||||
|
hms = hms.split(':')
|
||||||
|
seconds = 0
|
||||||
|
if len(hms) == 3:
|
||||||
|
seconds += int(hms[0])*3600
|
||||||
|
hms.pop(0)
|
||||||
|
if len(hms) == 2:
|
||||||
|
seconds += int(hms[0])*60
|
||||||
|
hms.pop(0)
|
||||||
|
if len(hms) == 1:
|
||||||
|
seconds += int(hms[0])
|
||||||
|
return seconds
|
||||||
|
|
||||||
|
def is_xor(*args):
|
||||||
|
'''
|
||||||
|
Return True if and only if one arg is truthy.
|
||||||
|
'''
|
||||||
|
return [bool(a) for a in args].count(True) == 1
|
||||||
|
|
||||||
|
def read_filebytes(filepath, range_min, range_max):
|
||||||
|
'''
|
||||||
|
Yield chunks of bytes from the file between the endpoints.
|
||||||
|
'''
|
||||||
|
range_span = range_max - range_min
|
||||||
|
|
||||||
|
#print('read span', range_min, range_max, range_span)
|
||||||
|
f = open(filepath, 'rb')
|
||||||
|
f.seek(range_min)
|
||||||
|
sent_amount = 0
|
||||||
|
with f:
|
||||||
|
while sent_amount < range_span:
|
||||||
|
#print(sent_amount)
|
||||||
|
chunk = f.read(FILE_READ_CHUNK)
|
||||||
|
if len(chunk) == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
yield chunk
|
||||||
|
sent_amount += len(chunk)
|
||||||
|
|
||||||
|
def seconds_to_hms(seconds):
|
||||||
|
'''
|
||||||
|
Convert integer number of seconds to an hh:mm:ss string.
|
||||||
|
Only the necessary fields are used.
|
||||||
|
'''
|
||||||
|
seconds = math.ceil(seconds)
|
||||||
|
(minutes, seconds) = divmod(seconds, 60)
|
||||||
|
(hours, minutes) = divmod(minutes, 60)
|
||||||
|
parts = []
|
||||||
|
if hours: parts.append(hours)
|
||||||
|
if minutes: parts.append(minutes)
|
||||||
|
parts.append(seconds)
|
||||||
|
hms = ':'.join('%02d' % part for part in parts)
|
||||||
|
return hms
|
||||||
|
|
||||||
|
def truthystring(s):
|
||||||
|
if isinstance(s, (bool, int)) or s is None:
|
||||||
|
return s
|
||||||
|
s = s.lower()
|
||||||
|
if s in {'1', 'true', 't', 'yes', 'y', 'on'}:
|
||||||
|
return True
|
||||||
|
if s in {'null', 'none'}:
|
||||||
|
return None
|
||||||
|
return False
|
0
Templates/flask/https/csr, key, crt goes here.txt
Normal file
0
Templates/flask/https/csr, key, crt goes here.txt
Normal file
2
Templates/flask/requirements.txt
Normal file
2
Templates/flask/requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
flask
|
||||||
|
gevent
|
32
Templates/flask/static/common.css
Normal file
32
Templates/flask/static/common.css
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
body
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color:#fff;
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
#header
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.header_element
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.header_element:hover
|
||||||
|
{
|
||||||
|
background-color: #f00;
|
||||||
|
}
|
||||||
|
#content_body
|
||||||
|
{
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
78
Templates/flask/static/common.js
Normal file
78
Templates/flask/static/common.js
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
function post_example(key, value)
|
||||||
|
{
|
||||||
|
var url = "/postexample";
|
||||||
|
data = new FormData();
|
||||||
|
data.append(key, value);
|
||||||
|
return post(url, data, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function post(url, data, callback)
|
||||||
|
{
|
||||||
|
var request = new XMLHttpRequest();
|
||||||
|
request.answer = null;
|
||||||
|
request.onreadystatechange = function()
|
||||||
|
{
|
||||||
|
if (request.readyState == 4)
|
||||||
|
{
|
||||||
|
var text = request.responseText;
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
console.log(text);
|
||||||
|
callback(JSON.parse(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var asynchronous = true;
|
||||||
|
request.open("POST", url, asynchronous);
|
||||||
|
request.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind_box_to_button(box, button)
|
||||||
|
{
|
||||||
|
box.onkeydown=function()
|
||||||
|
{
|
||||||
|
if (event.keyCode == 13)
|
||||||
|
{
|
||||||
|
button.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function entry_with_history_hook(box, button)
|
||||||
|
{
|
||||||
|
//console.log(event.keyCode);
|
||||||
|
if (box.entry_history === undefined)
|
||||||
|
{box.entry_history = [];}
|
||||||
|
if (box.entry_history_pos === undefined)
|
||||||
|
{box.entry_history_pos = -1;}
|
||||||
|
if (event.keyCode == 13)
|
||||||
|
{
|
||||||
|
/* Enter */
|
||||||
|
box.entry_history.push(box.value);
|
||||||
|
button.click();
|
||||||
|
box.value = "";
|
||||||
|
}
|
||||||
|
else if (event.keyCode == 38)
|
||||||
|
{
|
||||||
|
|
||||||
|
/* Up arrow */
|
||||||
|
if (box.entry_history.length == 0)
|
||||||
|
{return}
|
||||||
|
if (box.entry_history_pos == -1)
|
||||||
|
{
|
||||||
|
box.entry_history_pos = box.entry_history.length - 1;
|
||||||
|
}
|
||||||
|
else if (box.entry_history_pos > 0)
|
||||||
|
{
|
||||||
|
box.entry_history_pos -= 1;
|
||||||
|
}
|
||||||
|
box.value = box.entry_history[box.entry_history_pos];
|
||||||
|
}
|
||||||
|
else if (event.keyCode == 27)
|
||||||
|
{
|
||||||
|
box.value = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
box.entry_history_pos = -1;
|
||||||
|
}
|
||||||
|
}
|
BIN
Templates/flask/static/favicon.png
Normal file
BIN
Templates/flask/static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 448 B |
5
Templates/flask/templates/header.html
Normal file
5
Templates/flask/templates/header.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{% macro make_header() %}
|
||||||
|
<div id="header">
|
||||||
|
<a class="header_element" href="/">Home</a>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
29
Templates/flask/templates/root.html
Normal file
29
Templates/flask/templates/root.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<!DOCTYPE html5>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body, a
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
{% import "header.html" as header %}
|
||||||
|
<title>Flasksite</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" href="/static/common.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{{header.make_header()}}
|
||||||
|
<p>Welcome to my flask site</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
</script>
|
Loading…
Reference in a new issue