Improve separation between front & back with etiquette_flask package

Move flask-specific operations out of etiquette's files and into new etiquette_flask. In etiquette_site.py, etiquette calls are fully qualified.
This commit is contained in:
voussoir 2017-05-01 21:23:16 -07:00
parent 83b9adbd61
commit a9c7ad6993
8 changed files with 151 additions and 137 deletions

View file

@ -1 +1,8 @@
pass from . import constants
from . import decorators
from . import exceptions
from . import helpers
from . import jsonify
from . import objects
from . import photodb
from . import searchhelpers

View file

@ -1,11 +1,7 @@
import flask
from flask import request
import functools import functools
import time import time
import warnings import warnings
from . import jsonify
def required_feature(features): def required_feature(features):
''' '''
@ -31,35 +27,6 @@ def required_feature(features):
return wrapped return wrapped
return wrapper return wrapper
def required_fields(fields, forbid_whitespace=False):
'''
Declare that the endpoint requires certain POST body fields. Without them,
we respond with 400 and a message.
forbid_whitespace:
If True, then providing the field is not good enough. It must also
contain at least some non-whitespace characters.
'''
def wrapper(function):
@functools.wraps(function)
def wrapped(*args, **kwargs):
for requirement in fields:
missing = (
requirement not in request.form or
(forbid_whitespace and request.form[requirement].strip() == '')
)
if missing:
response = {
'error_type': 'MISSING_FIELDS',
'error_message': 'Required fields: %s' % ', '.join(fields),
}
response = jsonify.make_json_response(response, status=400)
return response
return function(*args, **kwargs)
return wrapped
return wrapper
def not_implemented(function): def not_implemented(function):
''' '''
Decorator to remember what needs doing. Decorator to remember what needs doing.

View file

@ -1,11 +1,5 @@
import flask
import json import json
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 album(a, minimal=False): def album(a, minimal=False):
j = { j = {

View file

@ -0,0 +1,3 @@
from . import decorators
from . import jsonify
from . import sessions

View file

@ -0,0 +1,35 @@
import flask
from flask import request
import functools
from etiquette import jsonify
def required_fields(fields, forbid_whitespace=False):
'''
Declare that the endpoint requires certain POST body fields. Without them,
we respond with 400 and a message.
forbid_whitespace:
If True, then providing the field is not good enough. It must also
contain at least some non-whitespace characters.
'''
def wrapper(function):
@functools.wraps(function)
def wrapped(*args, **kwargs):
for requirement in fields:
missing = (
requirement not in request.form or
(forbid_whitespace and request.form[requirement].strip() == '')
)
if missing:
response = {
'error_type': 'MISSING_FIELDS',
'error_message': 'Required fields: %s' % ', '.join(fields),
}
response = jsonify.make_json_response(response, status=400)
return response
return function(*args, **kwargs)
return wrapped
return wrapper

View file

@ -0,0 +1,9 @@
import flask
import json
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

View file

@ -3,7 +3,7 @@ from flask import request
import functools import functools
import uuid import uuid
from . import helpers from etiquette import helpers
def _generate_token(): def _generate_token():
token = str(uuid.uuid4()) token = str(uuid.uuid4())
@ -14,6 +14,7 @@ def _normalize_token(token):
if isinstance(token, flask.Request): if isinstance(token, flask.Request):
token = token.cookies.get('etiquette_session', None) token = token.cookies.get('etiquette_session', None)
class SessionManager: class SessionManager:
def __init__(self): def __init__(self):
self.sessions = {} self.sessions = {}

View file

@ -9,24 +9,22 @@ import urllib.parse
import warnings import warnings
import zipstream import zipstream
from etiquette import constants import etiquette
from etiquette import decorators import etiquette_flask
from etiquette import exceptions
from etiquette import helpers from voussoirkit import pathclass
from etiquette import jsonify
from etiquette import objects
from etiquette import photodb
from etiquette import searchhelpers
from etiquette import sessions
TEMPLATE_DIR = 'C:\\git\\Etiquette\\templates' root_dir = pathclass.Path(__file__).parent
STATIC_DIR = 'C:\\git\\Etiquette\\static'
TEMPLATE_DIR = root_dir.with_child('templates')
STATIC_DIR = root_dir.with_child('static')
FAVICON_PATH = STATIC_DIR.with_child('favicon.png')
site = flask.Flask( site = flask.Flask(
__name__, __name__,
template_folder=TEMPLATE_DIR, template_folder=TEMPLATE_DIR.absolute_path,
static_folder=STATIC_DIR, static_folder=STATIC_DIR.absolute_path,
) )
site.config.update( site.config.update(
SEND_FILE_MAX_AGE_DEFAULT=180, SEND_FILE_MAX_AGE_DEFAULT=180,
@ -37,9 +35,9 @@ site.jinja_env.trim_blocks = True
site.jinja_env.lstrip_blocks = True site.jinja_env.lstrip_blocks = True
site.debug = True site.debug = True
P = photodb.PhotoDB() P = etiquette.photodb.PhotoDB()
session_manager = sessions.SessionManager() session_manager = etiquette_flask.sessions.SessionManager()
#################################################################################################### ####################################################################################################
#################################################################################################### ####################################################################################################
@ -67,8 +65,8 @@ def delete_synonym(synonym):
try: try:
master_tag = P.get_tag(synonym) master_tag = P.get_tag(synonym)
except exceptions.NoSuchTag as e: except etiquette.exceptions.NoSuchTag as e:
raise exceptions.NoSuchSynonym(*e.given_args, **e.given_kwargs) raise etiquette.exceptions.NoSuchSynonym(*e.given_args, **e.given_kwargs)
master_tag.remove_synonym(synonym) master_tag.remove_synonym(synonym)
return {'action':'delete_synonym', 'synonym': synonym} return {'action':'delete_synonym', 'synonym': synonym}
@ -78,8 +76,8 @@ def P_wrapper(function):
try: try:
return function(thingid) return function(thingid)
except exceptions.EtiquetteException as e: except etiquette.exceptions.EtiquetteException as e:
if isinstance(e, exceptions.NoSuch): if isinstance(e, etiquette.exceptions.NoSuch):
status = 404 status = 404
else: else:
status = 400 status = 400
@ -87,8 +85,8 @@ def P_wrapper(function):
if response_type == 'html': if response_type == 'html':
flask.abort(status, e.error_message) flask.abort(status, e.error_message)
else: else:
response = jsonify.exception(e) response = etiquette.jsonify.exception(e)
response = jsonify.make_json_response(response, status=status) response = etiquette_flask.jsonify.make_json_response(response, status=status)
flask.abort(response) flask.abort(response)
except Exception as e: except Exception as e:
@ -96,7 +94,7 @@ def P_wrapper(function):
if response_type == 'html': if response_type == 'html':
flask.abort(500) flask.abort(500)
else: else:
flask.abort(jsonify.make_response({}, status=500)) flask.abort(etiquette.jsonify.make_json_response({}, status=500))
return P_wrapped return P_wrapped
@ -175,7 +173,7 @@ def send_file(filepath, override_mimetype=None):
if request.method == 'HEAD': if request.method == 'HEAD':
outgoing_data = bytes() outgoing_data = bytes()
else: else:
outgoing_data = helpers.read_filebytes( outgoing_data = etiquette.helpers.read_filebytes(
filepath, filepath,
range_min=range_min, range_min=range_min,
range_max=range_max, range_max=range_max,
@ -215,12 +213,12 @@ def get_register():
@site.route('/login', methods=['POST']) @site.route('/login', methods=['POST'])
@session_manager.give_token @session_manager.give_token
@decorators.required_fields(['username', 'password']) @etiquette_flask.decorators.required_fields(['username', 'password'])
def post_login(): def post_login():
if session_manager.get(request): if session_manager.get(request):
e = exceptions.AlreadySignedIn() e = etiquette.exceptions.AlreadySignedIn()
response = jsonify.exception(e) response = etiquette.jsonify.exception(e)
return jsonify.make_json_response(response, status=403) return etiquette_flask.jsonify.make_json_response(response, status=403)
username = request.form['username'] username = request.form['username']
password = request.form['password'] password = request.form['password']
@ -231,22 +229,22 @@ def post_login():
# page 404s anyway. # page 404s anyway.
user = P.get_user(username=username) user = P.get_user(username=username)
user = P.login(user.id, password) user = P.login(user.id, password)
except (exceptions.NoSuchUser, exceptions.WrongLogin): except (etiquette.exceptions.NoSuchUser, etiquette.exceptions.WrongLogin):
e = exceptions.WrongLogin() e = etiquette.exceptions.WrongLogin()
response = jsonify.exception(e) response = etiquette.jsonify.exception(e)
return jsonify.make_json_response(response, status=422) return etiquette_flask.jsonify.make_json_response(response, status=422)
session = sessions.Session(request, user) session = etiquette_flask.sessions.Session(request, user)
session_manager.add(session) session_manager.add(session)
return jsonify.make_json_response({}) return etiquette_flask.jsonify.make_json_response({})
@site.route('/register', methods=['POST']) @site.route('/register', methods=['POST'])
@session_manager.give_token @session_manager.give_token
@decorators.required_fields(['username', 'password_1', 'password_2']) @etiquette_flask.decorators.required_fields(['username', 'password_1', 'password_2'])
def post_register(): def post_register():
if session_manager.get(request): if session_manager.get(request):
e = exceptions.AlreadySignedIn() e = etiquette.exceptions.AlreadySignedIn()
response = jsonify.exception(e) response = etiquette.jsonify.exception(e)
return jsonify.make_json_response(response, status=403) return etiquette_flask.jsonify.make_json_response(response, status=403)
username = request.form['username'] username = request.form['username']
password_1 = request.form['password_1'] password_1 = request.form['password_1']
@ -257,17 +255,17 @@ def post_register():
'error_type': 'PASSWORDS_DONT_MATCH', 'error_type': 'PASSWORDS_DONT_MATCH',
'error_message': 'Passwords do not match.', 'error_message': 'Passwords do not match.',
} }
return jsonify.make_json_response(response, status=422) return etiquette_flask.jsonify.make_json_response(response, status=422)
try: try:
user = P.register_user(username, password_1) user = P.register_user(username, password_1)
except exceptions.EtiquetteException as e: except etiquette.exceptions.EtiquetteException as e:
response = jsonify.exception(e) response = etiquette.jsonify.exception(e)
return jsonify.make_json_response(response, status=400) return etiquette_flask.jsonify.make_json_response(response, status=400)
session = sessions.Session(request, user) session = etiquette_flask.sessions.Session(request, user)
session_manager.add(session) session_manager.add(session)
return jsonify.make_json_response({}) return etiquette_flask.jsonify.make_json_response({})
@site.route('/logout', methods=['GET', 'POST']) @site.route('/logout', methods=['GET', 'POST'])
@session_manager.give_token @session_manager.give_token
@ -280,8 +278,7 @@ def logout():
@site.route('/favicon.ico') @site.route('/favicon.ico')
@site.route('/favicon.png') @site.route('/favicon.png')
def favicon(): def favicon():
filename = os.path.join(STATIC_DIR, 'favicon.png') return flask.send_file(FAVICON_PATH.absolute_path)
return flask.send_file(filename)
def get_album_core(albumid): def get_album_core(albumid):
@ -305,11 +302,11 @@ def get_album_html(albumid):
@session_manager.give_token @session_manager.give_token
def get_album_json(albumid): def get_album_json(albumid):
album = get_album_core(albumid) album = get_album_core(albumid)
album = jsonify.album(album) album = etiquette.jsonify.album(album)
album['sub_albums'] = [P_album(x) for x in album['sub_albums']] album['sub_albums'] = [P_album(x) for x in album['sub_albums']]
album['sub_albums'].sort(key=lambda x: (x.title or x.id).lower()) album['sub_albums'].sort(key=lambda x: (x.title or x.id).lower())
album['sub_albums'] = [jsonify.album(x, minimal=True) for x in album['sub_albums']] album['sub_albums'] = [etiquette.jsonify.album(x, minimal=True) for x in album['sub_albums']]
return jsonify.make_json_response(album) return etiquette_flask.jsonify.make_json_response(album)
@site.route('/album/<albumid>.zip') @site.route('/album/<albumid>.zip')
@ -317,16 +314,16 @@ def get_album_zip(albumid):
album = P_album(albumid) album = P_album(albumid)
recursive = request.args.get('recursive', True) recursive = request.args.get('recursive', True)
recursive = helpers.truthystring(recursive) recursive = etiquette.helpers.truthystring(recursive)
arcnames = helpers.album_zip_filenames(album, recursive=recursive) arcnames = etiquette.helpers.album_zip_filenames(album, recursive=recursive)
streamed_zip = zipstream.ZipFile() streamed_zip = zipstream.ZipFile()
for (real_filepath, arcname) in arcnames.items(): for (real_filepath, arcname) in arcnames.items():
streamed_zip.write(real_filepath, arcname=arcname) streamed_zip.write(real_filepath, arcname=arcname)
# Add the album metadata as an {id}.txt file within each directory. # Add the album metadata as an {id}.txt file within each directory.
directories = helpers.album_zip_directories(album, recursive=recursive) directories = etiquette.helpers.album_zip_directories(album, recursive=recursive)
for (inner_album, directory) in directories.items(): for (inner_album, directory) in directories.items():
text = [] text = []
if inner_album.title: if inner_album.title:
@ -346,7 +343,7 @@ def get_album_zip(albumid):
else: else:
download_as = 'album %s.zip' % album.id download_as = 'album %s.zip' % album.id
download_as = helpers.normalize_filepath(download_as) download_as = etiquette.helpers.normalize_filepath(download_as)
download_as = urllib.parse.quote(download_as) download_as = urllib.parse.quote(download_as)
outgoing_headers = { outgoing_headers = {
'Content-Type': 'application/octet-stream', 'Content-Type': 'application/octet-stream',
@ -372,8 +369,8 @@ def get_albums_html():
@session_manager.give_token @session_manager.give_token
def get_albums_json(): def get_albums_json():
albums = get_albums_core() albums = get_albums_core()
albums = [jsonify.album(album, minimal=True) for album in albums] albums = [etiquette.jsonify.album(album, minimal=True) for album in albums]
return jsonify.make_json_response(albums) return etiquette_flask.jsonify.make_json_response(albums)
@site.route('/bookmarks') @site.route('/bookmarks')
@ -390,10 +387,10 @@ def get_file(photoid):
photo = P.get_photo(photoid) photo = P.get_photo(photoid)
do_download = request.args.get('download', False) do_download = request.args.get('download', False)
do_download = helpers.truthystring(do_download) do_download = etiquette.helpers.truthystring(do_download)
use_original_filename = request.args.get('original_filename', False) use_original_filename = request.args.get('original_filename', False)
use_original_filename = helpers.truthystring(use_original_filename) use_original_filename = etiquette.helpers.truthystring(use_original_filename)
if do_download: if do_download:
if use_original_filename: if use_original_filename:
@ -401,7 +398,7 @@ def get_file(photoid):
else: else:
download_as = photo.id + photo.dot_extension download_as = photo.id + photo.dot_extension
download_as = helpers.normalize_filepath(download_as) download_as = etiquette.helpers.normalize_filepath(download_as)
download_as = urllib.parse.quote(download_as) download_as = urllib.parse.quote(download_as)
response = flask.make_response(send_file(photo.real_filepath)) response = flask.make_response(send_file(photo.real_filepath))
response.headers['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'%s' % download_as response.headers['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'%s' % download_as
@ -421,12 +418,12 @@ def get_photo_html(photoid):
@session_manager.give_token @session_manager.give_token
def get_photo_json(photoid): def get_photo_json(photoid):
photo = P_photo(photoid, response_type='json') photo = P_photo(photoid, response_type='json')
photo = jsonify.photo(photo) photo = etiquette.jsonify.photo(photo)
photo = jsonify.make_json_response(photo) photo = etiquette_flask.jsonify.make_json_response(photo)
return photo return photo
def get_search_core(): def get_search_core():
warning_bag = objects.WarningBag() warning_bag = etiquette.objects.WarningBag()
has_tags = request.args.get('has_tags') has_tags = request.args.get('has_tags')
tag_musts = request.args.get('tag_musts') tag_musts = request.args.get('tag_musts')
@ -442,7 +439,7 @@ def get_search_core():
limit = request.args.get('limit') limit = request.args.get('limit')
# This is being pre-processed because the site enforces a maximum value # This is being pre-processed because the site enforces a maximum value
# which the PhotoDB api does not. # which the PhotoDB api does not.
limit = searchhelpers.normalize_limit(limit, warning_bag=warning_bag) limit = etiquette.searchhelpers.normalize_limit(limit, warning_bag=warning_bag)
if limit is None: if limit is None:
limit = 50 limit = 50
@ -515,7 +512,7 @@ def get_search_core():
warnings = set() warnings = set()
photos = [] photos = []
for item in search_results: for item in search_results:
if isinstance(item, objects.WarningBag): if isinstance(item, etiquette.objects.WarningBag):
warnings.update(item.warnings) warnings.update(item.warnings)
else: else:
photos.append(item) photos.append(item)
@ -534,7 +531,7 @@ def get_search_core():
if len(photos) == limit: if len(photos) == limit:
next_params = original_params.copy() next_params = original_params.copy()
next_params['offset'] = offset + limit next_params['offset'] = offset + limit
next_params = helpers.dict_to_params(next_params) next_params = etiquette.helpers.dict_to_params(next_params)
next_page_url = '/search' + next_params next_page_url = '/search' + next_params
else: else:
next_page_url = None next_page_url = None
@ -542,7 +539,7 @@ def get_search_core():
if offset > 0: if offset > 0:
prev_params = original_params.copy() prev_params = original_params.copy()
prev_params['offset'] = max(0, offset - limit) prev_params['offset'] = max(0, offset - limit)
prev_params = helpers.dict_to_params(prev_params) prev_params = etiquette.helpers.dict_to_params(prev_params)
prev_page_url = '/search' + prev_params prev_page_url = '/search' + prev_params
else: else:
prev_page_url = None prev_page_url = None
@ -565,7 +562,7 @@ def get_search_core():
def get_search_html(): def get_search_html():
search_results = get_search_core() search_results = get_search_core()
search_kwargs = search_results['search_kwargs'] search_kwargs = search_results['search_kwargs']
qualname_map = P.export_tags(exporter=photodb.tag_export_qualname_map) qualname_map = P.export_tags(exporter=etiquette.photodb.tag_export_qualname_map)
session = session_manager.get(request) session = session_manager.get(request)
response = flask.render_template( response = flask.render_template(
'search.html', 'search.html',
@ -585,9 +582,9 @@ def get_search_html():
def get_search_json(): def get_search_json():
search_results = get_search_core() search_results = get_search_core()
search_results['photos'] = [ search_results['photos'] = [
jsonify.photo(photo, include_albums=False) for photo in search_results['photos'] etiquette.jsonify.photo(photo, include_albums=False) for photo in search_results['photos']
] ]
return jsonify.make_json_response(search_results) return etiquette_flask.jsonify.make_json_response(search_results)
def get_tags_core(specific_tag=None): def get_tags_core(specific_tag=None):
@ -608,7 +605,7 @@ def get_tags_html(specific_tag=None):
tags = get_tags_core(specific_tag) tags = get_tags_core(specific_tag)
session = session_manager.get(request) session = session_manager.get(request)
include_synonyms = request.args.get('synonyms') include_synonyms = request.args.get('synonyms')
include_synonyms = include_synonyms is None or helpers.truthystring(include_synonyms) include_synonyms = include_synonyms is None or etiquette.helpers.truthystring(include_synonyms)
response = flask.render_template( response = flask.render_template(
'tags.html', 'tags.html',
include_synonyms=include_synonyms, include_synonyms=include_synonyms,
@ -625,9 +622,9 @@ def get_tags_json(specific_tag=None):
specific_tag = P_tag(specific_tag, response_type='json') specific_tag = P_tag(specific_tag, response_type='json')
tags = get_tags_core(specific_tag) tags = get_tags_core(specific_tag)
include_synonyms = request.args.get('synonyms') include_synonyms = request.args.get('synonyms')
include_synonyms = include_synonyms is None or helpers.truthystring(include_synonyms) include_synonyms = include_synonyms is None or etiquette.helpers.truthystring(include_synonyms)
tags = [jsonify.tag(tag, include_synonyms=include_synonyms) for tag in tags] tags = [etiquette.jsonify.tag(tag, include_synonyms=include_synonyms) for tag in tags]
return jsonify.make_json_response(tags) return etiquette_flask.jsonify.make_json_response(tags)
@site.route('/thumbnail/<photoid>') @site.route('/thumbnail/<photoid>')
@ -656,8 +653,8 @@ def get_user_html(username):
@session_manager.give_token @session_manager.give_token
def get_user_json(username): def get_user_json(username):
user = get_user_core(username) user = get_user_core(username)
user = jsonify.user(user) user = etiquette.jsonify.user(user)
user = jsonify.make_json_response(user) user = etiquette_flask.jsonify.make_json_response(user)
return user return user
@ -673,15 +670,15 @@ def post_album_add_tag(albumid):
tag = request.form['tagname'].strip() tag = request.form['tagname'].strip()
try: try:
tag = P_tag(tag) tag = P_tag(tag)
except exceptions.NoSuchTag as e: except etiquette.exceptions.NoSuchTag as e:
response = jsonify.exception(e) response = etiquette.jsonify.exception(e)
return jsonify.make_json_response(response, status=404) return etiquette_flask.jsonify.make_json_response(response, status=404)
recursive = request.form.get('recursive', False) recursive = request.form.get('recursive', False)
recursive = helpers.truthystring(recursive) recursive = etiquette.helpers.truthystring(recursive)
album.add_tag_to_all(tag, nested_children=recursive) album.add_tag_to_all(tag, nested_children=recursive)
response['action'] = 'add_tag' response['action'] = 'add_tag'
response['tagname'] = tag.name response['tagname'] = tag.name
return jsonify.make_json_response(response) return etiquette_flask.jsonify.make_json_response(response)
@site.route('/album/<albumid>/edit', methods=['POST']) @site.route('/album/<albumid>/edit', methods=['POST'])
@ -696,7 +693,7 @@ def post_album_edit(albumid):
description = request.form.get('description', None) description = request.form.get('description', None)
album.edit(title=title, description=description) album.edit(title=title, description=description)
response = {'title': album.title, 'description': album.description} response = {'title': album.title, 'description': album.description}
return jsonify.make_json_response(response) return etiquette_flask.jsonify.make_json_response(response)
def post_photo_add_remove_tag_core(photoid, tagname, add_or_remove): def post_photo_add_remove_tag_core(photoid, tagname, add_or_remove):
@ -708,16 +705,16 @@ def post_photo_add_remove_tag_core(photoid, tagname, add_or_remove):
photo.add_tag(tag) photo.add_tag(tag)
elif add_or_remove == 'remove': elif add_or_remove == 'remove':
photo.remove_tag(tag) photo.remove_tag(tag)
except exceptions.EtiquetteException as e: except etiquette.exceptions.EtiquetteException as e:
response = jsonify.exception(e) response = etiquette.jsonify.exception(e)
response = jsonify.make_json_response(response, status=400) response = etiquette_flask.jsonify.make_json_response(response, status=400)
flask.abort(response) flask.abort(response)
response = {'tagname': tag.name} response = {'tagname': tag.name}
return jsonify.make_json_response(response) return etiquette_flask.jsonify.make_json_response(response)
@site.route('/photo/<photoid>/add_tag', methods=['POST']) @site.route('/photo/<photoid>/add_tag', methods=['POST'])
@decorators.required_fields(['tagname'], forbid_whitespace=True) @etiquette_flask.decorators.required_fields(['tagname'], forbid_whitespace=True)
def post_photo_add_tag(photoid): def post_photo_add_tag(photoid):
''' '''
Add a tag to this photo. Add a tag to this photo.
@ -725,7 +722,7 @@ def post_photo_add_tag(photoid):
return post_photo_add_remove_tag_core(photoid, request.form['tagname'], 'add') return post_photo_add_remove_tag_core(photoid, request.form['tagname'], 'add')
@site.route('/photo/<photoid>/remove_tag', methods=['POST']) @site.route('/photo/<photoid>/remove_tag', methods=['POST'])
@decorators.required_fields(['tagname'], forbid_whitespace=True) @etiquette_flask.decorators.required_fields(['tagname'], forbid_whitespace=True)
def post_photo_remove_tag(photoid): def post_photo_remove_tag(photoid):
''' '''
Remove a tag from this photo. Remove a tag from this photo.
@ -737,29 +734,30 @@ def post_photo_refresh_metadata(photoid):
''' '''
Refresh the file metadata. Refresh the file metadata.
''' '''
P.caches['photo'].remove(photoid)
photo = P_photo(photoid, response_type='json') photo = P_photo(photoid, response_type='json')
try: try:
photo.reload_metadata() photo.reload_metadata()
except exceptions.EtiquetteException as e: except etiquette.exceptions.EtiquetteException as e:
response = jsonify.exception(e) response = etiquette.jsonify.exception(e)
response = jsonify.make_json_response(response, status=400) response = etiquette_flask.jsonify.make_json_response(response, status=400)
flask.abort(response) flask.abort(response)
return jsonify.make_json_response({}) return etiquette_flask.jsonify.make_json_response({})
def post_tag_create_delete_core(tagname, function): def post_tag_create_delete_core(tagname, function):
try: try:
response = function(tagname) response = function(tagname)
status = 200 status = 200
except exceptions.EtiquetteException as e: except etiquette.exceptions.EtiquetteException as e:
response = jsonify.exception(e) response = etiquette.jsonify.exception(e)
status = 400 status = 400
#print(response) #print(response)
return jsonify.make_json_response(response, status=status) return etiquette_flask.jsonify.make_json_response(response, status=status)
@site.route('/tags/create_tag', methods=['POST']) @site.route('/tags/create_tag', methods=['POST'])
@decorators.required_fields(['tagname'], forbid_whitespace=True) @etiquette_flask.decorators.required_fields(['tagname'], forbid_whitespace=True)
def post_tag_create(): def post_tag_create():
''' '''
Create a tag. Create a tag.
@ -767,7 +765,7 @@ def post_tag_create():
return post_tag_create_delete_core(request.form['tagname'], create_tag) return post_tag_create_delete_core(request.form['tagname'], create_tag)
@site.route('/tags/delete_tag', methods=['POST']) @site.route('/tags/delete_tag', methods=['POST'])
@decorators.required_fields(['tagname'], forbid_whitespace=True) @etiquette_flask.decorators.required_fields(['tagname'], forbid_whitespace=True)
def post_tag_delete(): def post_tag_delete():
''' '''
Delete a tag. Delete a tag.
@ -775,7 +773,7 @@ def post_tag_delete():
return post_tag_create_delete_core(request.form['tagname'], delete_tag) return post_tag_create_delete_core(request.form['tagname'], delete_tag)
@site.route('/tags/delete_synonym', methods=['POST']) @site.route('/tags/delete_synonym', methods=['POST'])
@decorators.required_fields(['tagname'], forbid_whitespace=True) @etiquette_flask.decorators.required_fields(['tagname'], forbid_whitespace=True)
def post_tag_delete_synonym(): def post_tag_delete_synonym():
''' '''
Delete a synonym. Delete a synonym.