Replace etiquette_flask.py with endpoints package.
Split the object types' endpoints into separate files and group them better. Should be much easier to navigate and expand.
This commit is contained in:
		
							parent
							
								
									178a7df0b3
								
							
						
					
					
						commit
						c049b97bc6
					
				
					 9 changed files with 999 additions and 927 deletions
				
			
		|  | @ -1,6 +1,6 @@ | ||||||
| from . import decorators | from . import decorators | ||||||
| from . import etiquette_flask | from . import endpoints | ||||||
| from . import jsonify | from . import jsonify | ||||||
| from . import sessions | from . import sessions | ||||||
| 
 | 
 | ||||||
| site = etiquette_flask.site | site = endpoints.site | ||||||
|  |  | ||||||
|  | @ -0,0 +1,35 @@ | ||||||
|  | import flask; from flask import request | ||||||
|  | import random | ||||||
|  | 
 | ||||||
|  | from . import album_endpoints | ||||||
|  | from . import bookmark_endpoints | ||||||
|  | from . import common | ||||||
|  | from . import photo_endpoints | ||||||
|  | from . import tag_endpoints | ||||||
|  | from . import user_endpoints | ||||||
|  | 
 | ||||||
|  | site = common.site | ||||||
|  | session_manager = common.session_manager | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @site.route('/') | ||||||
|  | @session_manager.give_token | ||||||
|  | def root(): | ||||||
|  |     motd = random.choice(common.P.config['motd_strings']) | ||||||
|  |     session = session_manager.get(request) | ||||||
|  |     return flask.render_template('root.html', motd=motd, session=session) | ||||||
|  | 
 | ||||||
|  | @site.route('/favicon.ico') | ||||||
|  | @site.route('/favicon.png') | ||||||
|  | def favicon(): | ||||||
|  |     return flask.send_file(common.FAVICON_PATH.absolute_path) | ||||||
|  | 
 | ||||||
|  | @site.route('/apitest') | ||||||
|  | @session_manager.give_token | ||||||
|  | def apitest(): | ||||||
|  |     response = flask.Response('testing') | ||||||
|  |     return response | ||||||
|  | 
 | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     #site.run(threaded=True) | ||||||
|  |     pass | ||||||
|  | @ -0,0 +1,200 @@ | ||||||
|  | import flask; from flask import request | ||||||
|  | import os | ||||||
|  | import urllib.parse | ||||||
|  | import zipstream | ||||||
|  | 
 | ||||||
|  | import etiquette | ||||||
|  | 
 | ||||||
|  | from .. import decorators | ||||||
|  | from .. import jsonify | ||||||
|  | from . import common | ||||||
|  | 
 | ||||||
|  | site = common.site | ||||||
|  | session_manager = common.session_manager | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Individual albums ################################################################################ | ||||||
|  | 
 | ||||||
|  | @site.route('/album/<album_id>') | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_album_html(album_id): | ||||||
|  |     album = common.P_album(album_id) | ||||||
|  |     session = session_manager.get(request) | ||||||
|  |     response = flask.render_template( | ||||||
|  |         'album.html', | ||||||
|  |         album=album, | ||||||
|  |         session=session, | ||||||
|  |         view=request.args.get('view', 'grid'), | ||||||
|  |     ) | ||||||
|  |     return response | ||||||
|  | 
 | ||||||
|  | @site.route('/album/<album_id>.json') | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_album_json(album_id): | ||||||
|  |     album = common.P_album(album_id) | ||||||
|  |     album = etiquette.jsonify.album(album) | ||||||
|  |     album['sub_albums'] = [common.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'] = [etiquette.jsonify.album(x, minimal=True) for x in album['sub_albums']] | ||||||
|  |     return jsonify.make_json_response(album) | ||||||
|  | 
 | ||||||
|  | @site.route('/album/<album_id>.zip') | ||||||
|  | def get_album_zip(album_id): | ||||||
|  |     album = common.P_album(album_id) | ||||||
|  | 
 | ||||||
|  |     recursive = request.args.get('recursive', True) | ||||||
|  |     recursive = etiquette.helpers.truthystring(recursive) | ||||||
|  | 
 | ||||||
|  |     arcnames = etiquette.helpers.album_zip_filenames(album, recursive=recursive) | ||||||
|  | 
 | ||||||
|  |     streamed_zip = zipstream.ZipFile() | ||||||
|  |     for (real_filepath, arcname) in arcnames.items(): | ||||||
|  |         streamed_zip.write(real_filepath, arcname=arcname) | ||||||
|  | 
 | ||||||
|  |     # Add the album metadata as an {id}.txt file within each directory. | ||||||
|  |     directories = etiquette.helpers.album_zip_directories(album, recursive=recursive) | ||||||
|  |     for (inner_album, directory) in directories.items(): | ||||||
|  |         text = [] | ||||||
|  |         if inner_album.title: | ||||||
|  |             text.append('Title: ' + inner_album.title) | ||||||
|  |         if inner_album.description: | ||||||
|  |             text.append('Description: ' + inner_album.description) | ||||||
|  |         if not text: | ||||||
|  |             continue | ||||||
|  |         text = '\r\n\r\n'.join(text) | ||||||
|  |         streamed_zip.writestr( | ||||||
|  |             arcname=os.path.join(directory, 'album %s.txt' % inner_album.id), | ||||||
|  |             data=text.encode('utf-8'), | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     if album.title: | ||||||
|  |         download_as = 'album %s - %s.zip' % (album.id, album.title) | ||||||
|  |     else: | ||||||
|  |         download_as = 'album %s.zip' % album.id | ||||||
|  | 
 | ||||||
|  |     download_as = etiquette.helpers.remove_path_badchars(download_as) | ||||||
|  |     download_as = urllib.parse.quote(download_as) | ||||||
|  |     outgoing_headers = { | ||||||
|  |         'Content-Type': 'application/octet-stream', | ||||||
|  |         'Content-Disposition': 'attachment; filename*=UTF-8\'\'%s' % download_as, | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     return flask.Response(streamed_zip, headers=outgoing_headers) | ||||||
|  | 
 | ||||||
|  | # Album photo operations ########################################################################### | ||||||
|  | 
 | ||||||
|  | @site.route('/album/<album_id>/add_photo', methods=['POST']) | ||||||
|  | @session_manager.give_token | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | @decorators.required_fields(['photo_id'], forbid_whitespace=True) | ||||||
|  | def post_album_add_photo(album_id): | ||||||
|  |     ''' | ||||||
|  |     Add a photo or photos to this album. | ||||||
|  |     ''' | ||||||
|  |     response = {} | ||||||
|  |     album = common.P_album(album_id) | ||||||
|  | 
 | ||||||
|  |     photo_ids = etiquette.helpers.comma_space_split(request.form['photo_id']) | ||||||
|  |     photos = [common.P_photo(photo_id) for photo_id in photo_ids] | ||||||
|  |     for photo in photos: | ||||||
|  |         album.add_photo(photo, commit=False) | ||||||
|  |     common.P.commit() | ||||||
|  |     return jsonify.make_json_response(response) | ||||||
|  | 
 | ||||||
|  | @site.route('/album/<album_id>/remove_photo', methods=['POST']) | ||||||
|  | @session_manager.give_token | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | @decorators.required_fields(['photo_id'], forbid_whitespace=True) | ||||||
|  | def post_album_remove_photo(album_id): | ||||||
|  |     ''' | ||||||
|  |     Remove a photo or photos from this album. | ||||||
|  |     ''' | ||||||
|  |     response = {} | ||||||
|  |     album = common.P_album(album_id) | ||||||
|  | 
 | ||||||
|  |     photo_ids = etiquette.helpers.comma_space_split(request.form['photo_id']) | ||||||
|  |     photos = [common.P_photo(photo_id) for photo_id in photo_ids] | ||||||
|  |     for photo in photos: | ||||||
|  |         album.remove_photo(photo, commit=False) | ||||||
|  |     common.P.commit() | ||||||
|  |     return jsonify.make_json_response(response) | ||||||
|  | 
 | ||||||
|  | # Album tag operations ############################################################################# | ||||||
|  | 
 | ||||||
|  | @site.route('/album/<album_id>/add_tag', methods=['POST']) | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | @session_manager.give_token | ||||||
|  | def post_album_add_tag(album_id): | ||||||
|  |     ''' | ||||||
|  |     Apply a tag to every photo in the album. | ||||||
|  |     ''' | ||||||
|  |     response = {} | ||||||
|  |     album = common.P_album(album_id) | ||||||
|  | 
 | ||||||
|  |     tag = request.form['tagname'].strip() | ||||||
|  |     try: | ||||||
|  |         tag = common.P_tag(tag) | ||||||
|  |     except etiquette.exceptions.NoSuchTag as exc: | ||||||
|  |         response = etiquette.jsonify.exception(exc) | ||||||
|  |         return jsonify.make_json_response(response, status=404) | ||||||
|  |     recursive = request.form.get('recursive', False) | ||||||
|  |     recursive = etiquette.helpers.truthystring(recursive) | ||||||
|  |     album.add_tag_to_all(tag, nested_children=recursive) | ||||||
|  |     response['action'] = 'add_tag' | ||||||
|  |     response['tagname'] = tag.name | ||||||
|  |     return jsonify.make_json_response(response) | ||||||
|  | 
 | ||||||
|  | # Album metadata operations ######################################################################## | ||||||
|  | 
 | ||||||
|  | @site.route('/album/<album_id>/edit', methods=['POST']) | ||||||
|  | @session_manager.give_token | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | def post_album_edit(album_id): | ||||||
|  |     ''' | ||||||
|  |     Edit the title / description. | ||||||
|  |     ''' | ||||||
|  |     album = common.P_album(album_id) | ||||||
|  | 
 | ||||||
|  |     title = request.form.get('title', None) | ||||||
|  |     description = request.form.get('description', None) | ||||||
|  |     album.edit(title=title, description=description) | ||||||
|  |     response = etiquette.jsonify.album(album, minimal=True) | ||||||
|  |     return jsonify.make_json_response(response) | ||||||
|  | 
 | ||||||
|  | # Album listings ################################################################################### | ||||||
|  | 
 | ||||||
|  | def get_albums_core(): | ||||||
|  |     albums = list(common.P.get_root_albums()) | ||||||
|  |     albums.sort(key=lambda x: x.display_name.lower()) | ||||||
|  |     return albums | ||||||
|  | 
 | ||||||
|  | @site.route('/albums') | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_albums_html(): | ||||||
|  |     albums = get_albums_core() | ||||||
|  |     session = session_manager.get(request) | ||||||
|  |     return flask.render_template('albums.html', albums=albums, session=session) | ||||||
|  | 
 | ||||||
|  | @site.route('/albums.json') | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_albums_json(): | ||||||
|  |     albums = get_albums_core() | ||||||
|  |     albums = [etiquette.jsonify.album(album, minimal=True) for album in albums] | ||||||
|  |     return jsonify.make_json_response(albums) | ||||||
|  | 
 | ||||||
|  | # Album create and delete ########################################################################## | ||||||
|  | 
 | ||||||
|  | @site.route('/albums/create_album', methods=['POST']) | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | def post_albums_create(): | ||||||
|  |     title = request.form.get('title', None) | ||||||
|  |     description = request.form.get('description', None) | ||||||
|  |     parent = request.form.get('parent', None) | ||||||
|  |     if parent is not None: | ||||||
|  |         parent = common.P_album(parent) | ||||||
|  | 
 | ||||||
|  |     album = common.P.new_album(title=title, description=description) | ||||||
|  |     if parent is not None: | ||||||
|  |         parent.add_child(album) | ||||||
|  |     response = etiquette.jsonify.album(album, minimal=False) | ||||||
|  |     return jsonify.make_json_response(response) | ||||||
|  | @ -0,0 +1,64 @@ | ||||||
|  | import flask; from flask import request | ||||||
|  | 
 | ||||||
|  | import etiquette | ||||||
|  | 
 | ||||||
|  | from .. import decorators | ||||||
|  | from .. import jsonify | ||||||
|  | from . import common | ||||||
|  | 
 | ||||||
|  | site = common.site | ||||||
|  | session_manager = common.session_manager | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Individual bookmarks ############################################################################# | ||||||
|  | 
 | ||||||
|  | @site.route('/bookmark/<bookmarkid>.json') | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_bookmark_json(bookmarkid): | ||||||
|  |     bookmark = common.P_bookmark(bookmarkid) | ||||||
|  |     response = etiquette.jsonify.bookmark(bookmark) | ||||||
|  |     return jsonify.make_json_response(response) | ||||||
|  | 
 | ||||||
|  | # Bookmark metadata operations ##################################################################### | ||||||
|  | 
 | ||||||
|  | @site.route('/bookmark/<bookmarkid>/edit', methods=['POST']) | ||||||
|  | @session_manager.give_token | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | def post_bookmark_edit(bookmarkid): | ||||||
|  |     bookmark = common.P_bookmark(bookmarkid) | ||||||
|  |     # Emptystring is okay for titles, but not for URL. | ||||||
|  |     title = request.form.get('title', None) | ||||||
|  |     url = request.form.get('url', None) or None | ||||||
|  |     bookmark.edit(title=title, url=url) | ||||||
|  | 
 | ||||||
|  |     response = etiquette.jsonify.bookmark(bookmark) | ||||||
|  |     response = jsonify.make_json_response(response) | ||||||
|  |     return response | ||||||
|  | 
 | ||||||
|  | # Bookmark listings ################################################################################ | ||||||
|  | 
 | ||||||
|  | @site.route('/bookmarks') | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_bookmarks_html(): | ||||||
|  |     session = session_manager.get(request) | ||||||
|  |     bookmarks = list(common.P.get_bookmarks()) | ||||||
|  |     return flask.render_template('bookmarks.html', bookmarks=bookmarks, session=session) | ||||||
|  | 
 | ||||||
|  | @site.route('/bookmarks.json') | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_bookmarks_json(): | ||||||
|  |     bookmarks = [etiquette.jsonify.bookmark(b) for b in common.P.get_bookmarks()] | ||||||
|  |     return jsonify.make_json_response(bookmarks) | ||||||
|  | 
 | ||||||
|  | # Bookmark create and delete ####################################################################### | ||||||
|  | 
 | ||||||
|  | @site.route('/bookmarks/create_bookmark', methods=['POST']) | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | @decorators.required_fields(['url'], forbid_whitespace=True) | ||||||
|  | def post_bookmarks_create(): | ||||||
|  |     url = request.form['url'] | ||||||
|  |     title = request.form.get('title', None) | ||||||
|  |     bookmark = common.P.new_bookmark(url=url, title=title) | ||||||
|  |     response = etiquette.jsonify.bookmark(bookmark) | ||||||
|  |     response = jsonify.make_json_response(response) | ||||||
|  |     return response | ||||||
							
								
								
									
										165
									
								
								frontends/etiquette_flask/etiquette_flask/endpoints/common.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								frontends/etiquette_flask/etiquette_flask/endpoints/common.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,165 @@ | ||||||
|  | import flask; from flask import request | ||||||
|  | import os | ||||||
|  | import mimetypes | ||||||
|  | import traceback | ||||||
|  | 
 | ||||||
|  | import etiquette | ||||||
|  | 
 | ||||||
|  | from voussoirkit import pathclass | ||||||
|  | 
 | ||||||
|  | from .. import jsonify | ||||||
|  | from .. import sessions | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | root_dir = pathclass.Path(__file__).parent.parent.parent | ||||||
|  | 
 | ||||||
|  | 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( | ||||||
|  |     __name__, | ||||||
|  |     template_folder=TEMPLATE_DIR.absolute_path, | ||||||
|  |     static_folder=STATIC_DIR.absolute_path, | ||||||
|  | ) | ||||||
|  | site.config.update( | ||||||
|  |     SEND_FILE_MAX_AGE_DEFAULT=180, | ||||||
|  |     TEMPLATES_AUTO_RELOAD=True, | ||||||
|  | ) | ||||||
|  | site.jinja_env.add_extension('jinja2.ext.do') | ||||||
|  | site.jinja_env.trim_blocks = True | ||||||
|  | site.jinja_env.lstrip_blocks = True | ||||||
|  | site.debug = True | ||||||
|  | 
 | ||||||
|  | P = etiquette.photodb.PhotoDB() | ||||||
|  | 
 | ||||||
|  | session_manager = sessions.SessionManager() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def P_wrapper(function): | ||||||
|  |     def P_wrapped(thingid, response_type='html'): | ||||||
|  |         try: | ||||||
|  |             return function(thingid) | ||||||
|  | 
 | ||||||
|  |         except etiquette.exceptions.EtiquetteException as exc: | ||||||
|  |             if isinstance(exc, etiquette.exceptions.NoSuch): | ||||||
|  |                 status = 404 | ||||||
|  |             else: | ||||||
|  |                 status = 400 | ||||||
|  | 
 | ||||||
|  |             if response_type == 'html': | ||||||
|  |                 flask.abort(status, exc.error_message) | ||||||
|  |             else: | ||||||
|  |                 response = etiquette.jsonify.exception(exc) | ||||||
|  |                 response = jsonify.make_json_response(response, status=status) | ||||||
|  |                 flask.abort(response) | ||||||
|  | 
 | ||||||
|  |         except Exception as exc: | ||||||
|  |             traceback.print_exc() | ||||||
|  |             if response_type == 'html': | ||||||
|  |                 flask.abort(500) | ||||||
|  |             else: | ||||||
|  |                 flask.abort(jsonify.make_json_response({}, status=500)) | ||||||
|  | 
 | ||||||
|  |     return P_wrapped | ||||||
|  | 
 | ||||||
|  | @P_wrapper | ||||||
|  | def P_album(album_id): | ||||||
|  |     return P.get_album(album_id) | ||||||
|  | 
 | ||||||
|  | @P_wrapper | ||||||
|  | def P_bookmark(bookmarkid): | ||||||
|  |     return P.get_bookmark(bookmarkid) | ||||||
|  | 
 | ||||||
|  | @P_wrapper | ||||||
|  | def P_photo(photo_id): | ||||||
|  |     return P.get_photo(photo_id) | ||||||
|  | 
 | ||||||
|  | @P_wrapper | ||||||
|  | def P_tag(tagname): | ||||||
|  |     return P.get_tag(tagname) | ||||||
|  | 
 | ||||||
|  | @P_wrapper | ||||||
|  | def P_user(username): | ||||||
|  |     return P.get_user(username=username) | ||||||
|  | 
 | ||||||
|  | @P_wrapper | ||||||
|  | def P_user_id(user_id): | ||||||
|  |     return P.get_user(id=user_id) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def back_url(): | ||||||
|  |     return request.args.get('goto') or request.referrer or '/' | ||||||
|  | 
 | ||||||
|  | def send_file(filepath, override_mimetype=None): | ||||||
|  |     ''' | ||||||
|  |     Range-enabled file sending. | ||||||
|  |     ''' | ||||||
|  |     try: | ||||||
|  |         file_size = os.path.getsize(filepath) | ||||||
|  |     except FileNotFoundError: | ||||||
|  |         flask.abort(404) | ||||||
|  | 
 | ||||||
|  |     outgoing_headers = {} | ||||||
|  |     if override_mimetype is not None: | ||||||
|  |         mimetype = override_mimetype | ||||||
|  |     else: | ||||||
|  |         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 = etiquette.helpers.read_filebytes( | ||||||
|  |             filepath, | ||||||
|  |             range_min=range_min, | ||||||
|  |             range_max=range_max, | ||||||
|  |             chunk_size=P.config['file_read_chunk'], | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     response = flask.Response( | ||||||
|  |         outgoing_data, | ||||||
|  |         status=status, | ||||||
|  |         headers=outgoing_headers, | ||||||
|  |     ) | ||||||
|  |     return response | ||||||
|  | @ -0,0 +1,278 @@ | ||||||
|  | import flask; from flask import request | ||||||
|  | import json | ||||||
|  | import traceback | ||||||
|  | import urllib.parse | ||||||
|  | 
 | ||||||
|  | import etiquette | ||||||
|  | 
 | ||||||
|  | from .. import decorators | ||||||
|  | from .. import jsonify | ||||||
|  | from . import common | ||||||
|  | 
 | ||||||
|  | site = common.site | ||||||
|  | session_manager = common.session_manager | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Individual photos ################################################################################ | ||||||
|  | 
 | ||||||
|  | @site.route('/photo/<photo_id>', methods=['GET']) | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_photo_html(photo_id): | ||||||
|  |     photo = common.P_photo(photo_id, response_type='html') | ||||||
|  |     session = session_manager.get(request) | ||||||
|  |     return flask.render_template('photo.html', photo=photo, session=session) | ||||||
|  | 
 | ||||||
|  | @site.route('/photo/<photo_id>.json', methods=['GET']) | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_photo_json(photo_id): | ||||||
|  |     photo = common.P_photo(photo_id, response_type='json') | ||||||
|  |     photo = etiquette.jsonify.photo(photo) | ||||||
|  |     photo = jsonify.make_json_response(photo) | ||||||
|  |     return photo | ||||||
|  | 
 | ||||||
|  | @site.route('/file/<photo_id>') | ||||||
|  | def get_file(photo_id): | ||||||
|  |     photo_id = photo_id.split('.')[0] | ||||||
|  |     photo = common.P.get_photo(photo_id) | ||||||
|  | 
 | ||||||
|  |     do_download = request.args.get('download', False) | ||||||
|  |     do_download = etiquette.helpers.truthystring(do_download) | ||||||
|  | 
 | ||||||
|  |     use_original_filename = request.args.get('original_filename', False) | ||||||
|  |     use_original_filename = etiquette.helpers.truthystring(use_original_filename) | ||||||
|  | 
 | ||||||
|  |     if do_download: | ||||||
|  |         if use_original_filename: | ||||||
|  |             download_as = photo.basename | ||||||
|  |         else: | ||||||
|  |             download_as = photo.id + photo.dot_extension | ||||||
|  | 
 | ||||||
|  |         download_as = etiquette.helpers.remove_path_badchars(download_as) | ||||||
|  |         download_as = urllib.parse.quote(download_as) | ||||||
|  |         response = flask.make_response(common.send_file(photo.real_filepath)) | ||||||
|  |         response.headers['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'%s' % download_as | ||||||
|  |         return response | ||||||
|  |     else: | ||||||
|  |         return common.send_file(photo.real_filepath, override_mimetype=photo.mimetype) | ||||||
|  | 
 | ||||||
|  | @site.route('/thumbnail/<photo_id>') | ||||||
|  | def get_thumbnail(photo_id): | ||||||
|  |     photo_id = photo_id.split('.')[0] | ||||||
|  |     photo = common.P_photo(photo_id) | ||||||
|  |     if photo.thumbnail: | ||||||
|  |         path = photo.thumbnail | ||||||
|  |     else: | ||||||
|  |         flask.abort(404, 'That file doesnt have a thumbnail') | ||||||
|  |     return common.send_file(path) | ||||||
|  | 
 | ||||||
|  | # Photo tag operations ############################################################################# | ||||||
|  | 
 | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | def post_photo_add_remove_tag_core(photo_id, tagname, add_or_remove): | ||||||
|  |     photo = common.P_photo(photo_id, response_type='json') | ||||||
|  |     tag = common.P_tag(tagname, response_type='json') | ||||||
|  | 
 | ||||||
|  |     if add_or_remove == 'add': | ||||||
|  |         photo.add_tag(tag) | ||||||
|  |     elif add_or_remove == 'remove': | ||||||
|  |         photo.remove_tag(tag) | ||||||
|  | 
 | ||||||
|  |     response = {'tagname': tag.name} | ||||||
|  |     return jsonify.make_json_response(response) | ||||||
|  | 
 | ||||||
|  | @site.route('/photo/<photo_id>/add_tag', methods=['POST']) | ||||||
|  | @decorators.required_fields(['tagname'], forbid_whitespace=True) | ||||||
|  | def post_photo_add_tag(photo_id): | ||||||
|  |     ''' | ||||||
|  |     Add a tag to this photo. | ||||||
|  |     ''' | ||||||
|  |     return post_photo_add_remove_tag_core(photo_id, request.form['tagname'], 'add') | ||||||
|  | 
 | ||||||
|  | @site.route('/photo/<photo_id>/remove_tag', methods=['POST']) | ||||||
|  | @decorators.required_fields(['tagname'], forbid_whitespace=True) | ||||||
|  | def post_photo_remove_tag(photo_id): | ||||||
|  |     ''' | ||||||
|  |     Remove a tag from this photo. | ||||||
|  |     ''' | ||||||
|  |     return post_photo_add_remove_tag_core(photo_id, request.form['tagname'], 'remove') | ||||||
|  | 
 | ||||||
|  | # Photo metadata operations ######################################################################## | ||||||
|  | 
 | ||||||
|  | @site.route('/photo/<photo_id>/refresh_metadata', methods=['POST']) | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | def post_photo_refresh_metadata(photo_id): | ||||||
|  |     ''' | ||||||
|  |     Refresh the file metadata. | ||||||
|  |     ''' | ||||||
|  |     common.P.caches['photo'].remove(photo_id) | ||||||
|  |     photo = common.P_photo(photo_id, response_type='json') | ||||||
|  |     photo.reload_metadata() | ||||||
|  |     if photo.thumbnail is None: | ||||||
|  |         try: | ||||||
|  |             photo.generate_thumbnail() | ||||||
|  |         except Exception: | ||||||
|  |             traceback.print_exc() | ||||||
|  | 
 | ||||||
|  |     return jsonify.make_json_response({}) | ||||||
|  | 
 | ||||||
|  | # Search ########################################################################################### | ||||||
|  | 
 | ||||||
|  | def get_search_core(): | ||||||
|  |     warning_bag = etiquette.objects.WarningBag() | ||||||
|  | 
 | ||||||
|  |     has_tags = request.args.get('has_tags') | ||||||
|  |     tag_musts = request.args.get('tag_musts') | ||||||
|  |     tag_mays = request.args.get('tag_mays') | ||||||
|  |     tag_forbids = request.args.get('tag_forbids') | ||||||
|  |     tag_expression = request.args.get('tag_expression') | ||||||
|  | 
 | ||||||
|  |     filename_terms = request.args.get('filename') | ||||||
|  |     extension = request.args.get('extension') | ||||||
|  |     extension_not = request.args.get('extension_not') | ||||||
|  |     mimetype = request.args.get('mimetype') | ||||||
|  | 
 | ||||||
|  |     limit = request.args.get('limit') | ||||||
|  |     # This is being pre-processed because the site enforces a maximum value | ||||||
|  |     # which the PhotoDB api does not. | ||||||
|  |     limit = etiquette.searchhelpers.normalize_limit(limit, warning_bag=warning_bag) | ||||||
|  | 
 | ||||||
|  |     if limit is None: | ||||||
|  |         limit = 50 | ||||||
|  |     else: | ||||||
|  |         limit = min(limit, 100) | ||||||
|  | 
 | ||||||
|  |     offset = request.args.get('offset') | ||||||
|  | 
 | ||||||
|  |     authors = request.args.get('author') | ||||||
|  | 
 | ||||||
|  |     orderby = request.args.get('orderby') | ||||||
|  |     area = request.args.get('area') | ||||||
|  |     width = request.args.get('width') | ||||||
|  |     height = request.args.get('height') | ||||||
|  |     ratio = request.args.get('ratio') | ||||||
|  |     bytes = request.args.get('bytes') | ||||||
|  |     duration = request.args.get('duration') | ||||||
|  |     created = request.args.get('created') | ||||||
|  | 
 | ||||||
|  |     # These are in a dictionary so I can pass them to the page template. | ||||||
|  |     search_kwargs = { | ||||||
|  |         'area': area, | ||||||
|  |         'width': width, | ||||||
|  |         'height': height, | ||||||
|  |         'ratio': ratio, | ||||||
|  |         'bytes': bytes, | ||||||
|  |         'duration': duration, | ||||||
|  | 
 | ||||||
|  |         'authors': authors, | ||||||
|  |         'created': created, | ||||||
|  |         'extension': extension, | ||||||
|  |         'extension_not': extension_not, | ||||||
|  |         'filename': filename_terms, | ||||||
|  |         'has_tags': has_tags, | ||||||
|  |         'mimetype': mimetype, | ||||||
|  |         'tag_musts': tag_musts, | ||||||
|  |         'tag_mays': tag_mays, | ||||||
|  |         'tag_forbids': tag_forbids, | ||||||
|  |         'tag_expression': tag_expression, | ||||||
|  | 
 | ||||||
|  |         'limit': limit, | ||||||
|  |         'offset': offset, | ||||||
|  |         'orderby': orderby, | ||||||
|  | 
 | ||||||
|  |         'warning_bag': warning_bag, | ||||||
|  |         'give_back_parameters': True | ||||||
|  |     } | ||||||
|  |     #print(search_kwargs) | ||||||
|  |     search_generator = common.P.search(**search_kwargs) | ||||||
|  |     # Because of the giveback, first element is cleaned up kwargs | ||||||
|  |     search_kwargs = next(search_generator) | ||||||
|  | 
 | ||||||
|  |     # The search has converted many arguments into sets or other types. | ||||||
|  |     # Convert them back into something that will display nicely on the search form. | ||||||
|  |     join_helper = lambda x: ', '.join(x) if x else None | ||||||
|  |     search_kwargs['extension'] = join_helper(search_kwargs['extension']) | ||||||
|  |     search_kwargs['extension_not'] = join_helper(search_kwargs['extension_not']) | ||||||
|  |     search_kwargs['mimetype'] = join_helper(search_kwargs['mimetype']) | ||||||
|  | 
 | ||||||
|  |     tagname_helper = lambda tags: [tag.qualified_name() for tag in tags] if tags else None | ||||||
|  |     search_kwargs['tag_musts'] = tagname_helper(search_kwargs['tag_musts']) | ||||||
|  |     search_kwargs['tag_mays'] = tagname_helper(search_kwargs['tag_mays']) | ||||||
|  |     search_kwargs['tag_forbids'] = tagname_helper(search_kwargs['tag_forbids']) | ||||||
|  | 
 | ||||||
|  |     search_results = list(search_generator) | ||||||
|  |     warnings = set() | ||||||
|  |     photos = [] | ||||||
|  |     for item in search_results: | ||||||
|  |         if isinstance(item, etiquette.objects.WarningBag): | ||||||
|  |             warnings.update(item.warnings) | ||||||
|  |         else: | ||||||
|  |             photos.append(item) | ||||||
|  | 
 | ||||||
|  |     # TAGS ON THIS PAGE | ||||||
|  |     total_tags = set() | ||||||
|  |     for photo in photos: | ||||||
|  |         for tag in photo.tags(): | ||||||
|  |             total_tags.add(tag) | ||||||
|  |     total_tags = sorted(total_tags, key=lambda t: t.qualified_name()) | ||||||
|  | 
 | ||||||
|  |     # PREV-NEXT PAGE URLS | ||||||
|  |     offset = search_kwargs['offset'] or 0 | ||||||
|  |     original_params = request.args.to_dict() | ||||||
|  |     original_params['limit'] = limit | ||||||
|  |     if len(photos) == limit: | ||||||
|  |         next_params = original_params.copy() | ||||||
|  |         next_params['offset'] = offset + limit | ||||||
|  |         next_params = etiquette.helpers.dict_to_params(next_params) | ||||||
|  |         next_page_url = '/search' + next_params | ||||||
|  |     else: | ||||||
|  |         next_page_url = None | ||||||
|  | 
 | ||||||
|  |     if offset > 0: | ||||||
|  |         prev_params = original_params.copy() | ||||||
|  |         prev_params['offset'] = max(0, offset - limit) | ||||||
|  |         prev_params = etiquette.helpers.dict_to_params(prev_params) | ||||||
|  |         prev_page_url = '/search' + prev_params | ||||||
|  |     else: | ||||||
|  |         prev_page_url = None | ||||||
|  | 
 | ||||||
|  |     view = request.args.get('view', 'grid') | ||||||
|  |     search_kwargs['view'] = view | ||||||
|  | 
 | ||||||
|  |     final_results = { | ||||||
|  |         'next_page_url': next_page_url, | ||||||
|  |         'prev_page_url': prev_page_url, | ||||||
|  |         'photos': photos, | ||||||
|  |         'total_tags': total_tags, | ||||||
|  |         'warnings': list(warnings), | ||||||
|  |         'search_kwargs': search_kwargs, | ||||||
|  |     } | ||||||
|  |     return final_results | ||||||
|  | 
 | ||||||
|  | @site.route('/search') | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_search_html(): | ||||||
|  |     search_results = get_search_core() | ||||||
|  |     search_kwargs = search_results['search_kwargs'] | ||||||
|  |     qualname_map = etiquette.tag_export.qualified_names(common.P.get_tags()) | ||||||
|  |     session = session_manager.get(request) | ||||||
|  |     response = flask.render_template( | ||||||
|  |         'search.html', | ||||||
|  |         next_page_url=search_results['next_page_url'], | ||||||
|  |         prev_page_url=search_results['prev_page_url'], | ||||||
|  |         photos=search_results['photos'], | ||||||
|  |         qualname_map=json.dumps(qualname_map), | ||||||
|  |         search_kwargs=search_kwargs, | ||||||
|  |         session=session, | ||||||
|  |         total_tags=search_results['total_tags'], | ||||||
|  |         warnings=search_results['warnings'], | ||||||
|  |     ) | ||||||
|  |     return response | ||||||
|  | 
 | ||||||
|  | @site.route('/search.json') | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_search_json(): | ||||||
|  |     search_results = get_search_core() | ||||||
|  |     search_results['photos'] = [ | ||||||
|  |         etiquette.jsonify.photo(photo, include_albums=False) for photo in search_results['photos'] | ||||||
|  |     ] | ||||||
|  |     return jsonify.make_json_response(search_results) | ||||||
|  | @ -0,0 +1,137 @@ | ||||||
|  | import flask; from flask import request | ||||||
|  | 
 | ||||||
|  | import etiquette | ||||||
|  | 
 | ||||||
|  | from .. import decorators | ||||||
|  | from .. import jsonify | ||||||
|  | from . import common | ||||||
|  | 
 | ||||||
|  | site = common.site | ||||||
|  | session_manager = common.session_manager | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Individual tags ################################################################################## | ||||||
|  | 
 | ||||||
|  | @site.route('/tags/<specific_tag>') | ||||||
|  | @site.route('/tags/<specific_tag>.json') | ||||||
|  | def get_tags_specific_redirect(specific_tag): | ||||||
|  |     return flask.redirect(request.url.replace('/tags/', '/tag/')) | ||||||
|  | 
 | ||||||
|  | # Tag metadata operations ########################################################################## | ||||||
|  | 
 | ||||||
|  | @site.route('/tag/<specific_tag>/edit', methods=['POST']) | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | def post_tag_edit(specific_tag): | ||||||
|  |     tag = common.P_tag(specific_tag) | ||||||
|  |     name = request.form.get('name', '').strip() | ||||||
|  |     if name: | ||||||
|  |         tag.rename(name, commit=False) | ||||||
|  | 
 | ||||||
|  |     description = request.form.get('description', None) | ||||||
|  |     tag.edit(description=description) | ||||||
|  | 
 | ||||||
|  |     response = etiquette.jsonify.tag(tag) | ||||||
|  |     response = jsonify.make_json_response(response) | ||||||
|  |     return response | ||||||
|  | 
 | ||||||
|  | # Tag listings ##################################################################################### | ||||||
|  | 
 | ||||||
|  | def get_tags_core(specific_tag=None): | ||||||
|  |     if specific_tag is None: | ||||||
|  |         tags = common.P.get_tags() | ||||||
|  |     else: | ||||||
|  |         tags = specific_tag.walk_children() | ||||||
|  |     tags = list(tags) | ||||||
|  |     tags.sort(key=lambda x: x.qualified_name()) | ||||||
|  |     return tags | ||||||
|  | 
 | ||||||
|  | @site.route('/tag/<specific_tag_name>') | ||||||
|  | @site.route('/tags') | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_tags_html(specific_tag_name=None): | ||||||
|  |     if specific_tag_name is None: | ||||||
|  |         specific_tag = None | ||||||
|  |     else: | ||||||
|  |         specific_tag = common.P_tag(specific_tag_name, response_type='html') | ||||||
|  |         if specific_tag.name != specific_tag_name: | ||||||
|  |             new_url = request.url.replace('/tag/' + specific_tag_name, '/tag/' + specific_tag.name) | ||||||
|  |             response = flask.redirect(new_url) | ||||||
|  |             return response | ||||||
|  |     tags = get_tags_core(specific_tag) | ||||||
|  |     session = session_manager.get(request) | ||||||
|  |     include_synonyms = request.args.get('synonyms') | ||||||
|  |     include_synonyms = include_synonyms is None or etiquette.helpers.truthystring(include_synonyms) | ||||||
|  |     response = flask.render_template( | ||||||
|  |         'tags.html', | ||||||
|  |         include_synonyms=include_synonyms, | ||||||
|  |         session=session, | ||||||
|  |         specific_tag=specific_tag, | ||||||
|  |         tags=tags, | ||||||
|  |     ) | ||||||
|  |     return response | ||||||
|  | 
 | ||||||
|  | @site.route('/tag/<specific_tag_name>.json') | ||||||
|  | @site.route('/tags.json') | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_tags_json(specific_tag_name=None): | ||||||
|  |     if specific_tag_name is None: | ||||||
|  |         specific_tag = None | ||||||
|  |     else: | ||||||
|  |         specific_tag = common.P_tag(specific_tag_name, response_type='json') | ||||||
|  |         if specific_tag.name != specific_tag_name: | ||||||
|  |             new_url = request.url.replace('/tag/' + specific_tag_name, '/tag/' + specific_tag.name) | ||||||
|  |             return flask.redirect(new_url) | ||||||
|  |     tags = get_tags_core(specific_tag=specific_tag) | ||||||
|  |     include_synonyms = request.args.get('synonyms') | ||||||
|  |     include_synonyms = include_synonyms is None or etiquette.helpers.truthystring(include_synonyms) | ||||||
|  |     tags = [etiquette.jsonify.tag(tag, include_synonyms=include_synonyms) for tag in tags] | ||||||
|  |     return jsonify.make_json_response(tags) | ||||||
|  | 
 | ||||||
|  | # Tag create and delete ############################################################################ | ||||||
|  | 
 | ||||||
|  | @site.route('/tags/create_tag', methods=['POST']) | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | @decorators.required_fields(['tagname'], forbid_whitespace=True) | ||||||
|  | def post_tag_create(): | ||||||
|  |     ''' | ||||||
|  |     Create a tag. | ||||||
|  |     ''' | ||||||
|  |     easybake_string = request.form['tagname'] | ||||||
|  |     notes = common.P.easybake(easybake_string) | ||||||
|  |     notes = [{'action': action, 'tagname': tagname} for (action, tagname) in notes] | ||||||
|  |     return jsonify.make_json_response(notes) | ||||||
|  | 
 | ||||||
|  | @site.route('/tags/delete_synonym', methods=['POST']) | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | @decorators.required_fields(['tagname'], forbid_whitespace=True) | ||||||
|  | def post_tag_delete_synonym(): | ||||||
|  |     ''' | ||||||
|  |     Delete a synonym. | ||||||
|  |     ''' | ||||||
|  |     synonym = request.form['tagname'] | ||||||
|  |     synonym = synonym.split('+')[-1].split('.')[-1] | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         master_tag = common.P_tag(synonym, response_type='json') | ||||||
|  |     except etiquette.exceptions.NoSuchTag as exc: | ||||||
|  |         raise etiquette.exceptions.NoSuchSynonym(*exc.given_args, **exc.given_kwargs) | ||||||
|  |     else: | ||||||
|  |         master_tag.remove_synonym(synonym) | ||||||
|  | 
 | ||||||
|  |     response = {'action':'delete_synonym', 'synonym': synonym} | ||||||
|  |     return jsonify.make_json_response(response) | ||||||
|  | 
 | ||||||
|  | @site.route('/tags/delete_tag', methods=['POST']) | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | @decorators.required_fields(['tagname'], forbid_whitespace=True) | ||||||
|  | def post_tag_delete(): | ||||||
|  |     ''' | ||||||
|  |     Delete a tag. | ||||||
|  |     ''' | ||||||
|  |     tagname = request.form['tagname'] | ||||||
|  |     tagname = tagname.split('.')[-1].split('+')[0] | ||||||
|  |     tag = common.P.get_tag(tagname) | ||||||
|  | 
 | ||||||
|  |     tag.delete() | ||||||
|  |     response = {'action': 'delete_tag', 'tagname': tag.name} | ||||||
|  |     return jsonify.make_json_response(response) | ||||||
|  | @ -0,0 +1,118 @@ | ||||||
|  | import flask; from flask import request | ||||||
|  | 
 | ||||||
|  | import etiquette | ||||||
|  | 
 | ||||||
|  | from .. import decorators | ||||||
|  | from .. import jsonify | ||||||
|  | from .. import sessions | ||||||
|  | from . import common | ||||||
|  | 
 | ||||||
|  | site = common.site | ||||||
|  | session_manager = common.session_manager | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Individual users ################################################################################# | ||||||
|  | 
 | ||||||
|  | @site.route('/user/<username>', methods=['GET']) | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_user_html(username): | ||||||
|  |     user = common.P_user(username, response_type='html') | ||||||
|  |     session = session_manager.get(request) | ||||||
|  |     return flask.render_template('user.html', user=user, session=session) | ||||||
|  | 
 | ||||||
|  | @site.route('/user/<username>.json', methods=['GET']) | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_user_json(username): | ||||||
|  |     user = common.P_user(username, response_type='json') | ||||||
|  |     user = etiquette.jsonify.user(user) | ||||||
|  |     return jsonify.make_json_response(user) | ||||||
|  | 
 | ||||||
|  | @site.route('/userid/<user_id>') | ||||||
|  | @site.route('/userid/<user_id>.json') | ||||||
|  | def get_user_id_redirect(user_id): | ||||||
|  |     if request.url.endswith('.json'): | ||||||
|  |         user = common.P_user_id(user_id, response_type='json') | ||||||
|  |     else: | ||||||
|  |         user = common.P_user_id(user_id, response_type='html') | ||||||
|  |     url_from = '/userid/' + user_id | ||||||
|  |     url_to = '/user/' + user.username | ||||||
|  |     url = request.url.replace(url_from, url_to) | ||||||
|  |     return flask.redirect(url) | ||||||
|  | 
 | ||||||
|  | # Login and logout ################################################################################# | ||||||
|  | 
 | ||||||
|  | @site.route('/login', methods=['GET']) | ||||||
|  | @session_manager.give_token | ||||||
|  | def get_login(): | ||||||
|  |     session = session_manager.get(request) | ||||||
|  |     return flask.render_template('login.html', session=session) | ||||||
|  | 
 | ||||||
|  | @site.route('/login', methods=['POST']) | ||||||
|  | @session_manager.give_token | ||||||
|  | @decorators.required_fields(['username', 'password']) | ||||||
|  | def post_login(): | ||||||
|  |     if session_manager.get(request): | ||||||
|  |         exc = etiquette.exceptions.AlreadySignedIn() | ||||||
|  |         response = etiquette.jsonify.exception(exc) | ||||||
|  |         return jsonify.make_json_response(response, status=403) | ||||||
|  | 
 | ||||||
|  |     username = request.form['username'] | ||||||
|  |     password = request.form['password'] | ||||||
|  |     try: | ||||||
|  |         # Consideration: Should the server hash the password to discourage | ||||||
|  |         # information (user exists) leak via response time? | ||||||
|  |         # Currently I think not, because they can check if the account | ||||||
|  |         # page 404s anyway. | ||||||
|  |         user = common.P.get_user(username=username) | ||||||
|  |         user = common.P.login(user.id, password) | ||||||
|  |     except (etiquette.exceptions.NoSuchUser, etiquette.exceptions.WrongLogin): | ||||||
|  |         exc = etiquette.exceptions.WrongLogin() | ||||||
|  |         response = etiquette.jsonify.exception(exc) | ||||||
|  |         return jsonify.make_json_response(response, status=422) | ||||||
|  |     except etiquette.exceptions.FeatureDisabled as exc: | ||||||
|  |         response = etiquette.jsonify.exception(exc) | ||||||
|  |         return jsonify.make_json_response(response, status=400) | ||||||
|  |     session = sessions.Session(request, user) | ||||||
|  |     session_manager.add(session) | ||||||
|  |     return jsonify.make_json_response({}) | ||||||
|  | 
 | ||||||
|  | @site.route('/logout', methods=['GET', 'POST']) | ||||||
|  | @session_manager.give_token | ||||||
|  | def logout(): | ||||||
|  |     session_manager.remove(request) | ||||||
|  |     response = flask.Response('redirect', status=302, headers={'Location': common.back_url()}) | ||||||
|  |     return response | ||||||
|  | 
 | ||||||
|  | # User registration ################################################################################ | ||||||
|  | 
 | ||||||
|  | @site.route('/register', methods=['GET']) | ||||||
|  | def get_register(): | ||||||
|  |     return flask.redirect('/login') | ||||||
|  | 
 | ||||||
|  | @site.route('/register', methods=['POST']) | ||||||
|  | @session_manager.give_token | ||||||
|  | @decorators.catch_etiquette_exception | ||||||
|  | @decorators.required_fields(['username', 'password_1', 'password_2']) | ||||||
|  | def post_register(): | ||||||
|  |     if session_manager.get(request): | ||||||
|  |         exc = etiquette.exceptions.AlreadySignedIn() | ||||||
|  |         response = etiquette.jsonify.exception(exc) | ||||||
|  |         return jsonify.make_json_response(response, status=403) | ||||||
|  | 
 | ||||||
|  |     username = request.form['username'] | ||||||
|  |     password_1 = request.form['password_1'] | ||||||
|  |     password_2 = request.form['password_2'] | ||||||
|  | 
 | ||||||
|  |     if password_1 != password_2: | ||||||
|  |         response = { | ||||||
|  |             'error_type': 'PASSWORDS_DONT_MATCH', | ||||||
|  |             'error_message': 'Passwords do not match.', | ||||||
|  |         } | ||||||
|  |         return jsonify.make_json_response(response, status=422) | ||||||
|  | 
 | ||||||
|  |     user = common.P.register_user(username, password_1) | ||||||
|  | 
 | ||||||
|  |     session = sessions.Session(request, user) | ||||||
|  |     session_manager.add(session) | ||||||
|  |     return jsonify.make_json_response({}) | ||||||
|  | 
 | ||||||
|  | @ -1,925 +0,0 @@ | ||||||
| import flask |  | ||||||
| from flask import request |  | ||||||
| import json |  | ||||||
| import mimetypes |  | ||||||
| import os |  | ||||||
| import random |  | ||||||
| import traceback |  | ||||||
| import urllib.parse |  | ||||||
| import warnings |  | ||||||
| import zipstream |  | ||||||
| 
 |  | ||||||
| import etiquette |  | ||||||
| 
 |  | ||||||
| from . import decorators |  | ||||||
| from . import jsonify |  | ||||||
| from . import sessions |  | ||||||
| 
 |  | ||||||
| from voussoirkit import pathclass |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| root_dir = pathclass.Path(__file__).parent.parent |  | ||||||
| 
 |  | ||||||
| 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( |  | ||||||
|     __name__, |  | ||||||
|     template_folder=TEMPLATE_DIR.absolute_path, |  | ||||||
|     static_folder=STATIC_DIR.absolute_path, |  | ||||||
| ) |  | ||||||
| site.config.update( |  | ||||||
|     SEND_FILE_MAX_AGE_DEFAULT=180, |  | ||||||
|     TEMPLATES_AUTO_RELOAD=True, |  | ||||||
| ) |  | ||||||
| site.jinja_env.add_extension('jinja2.ext.do') |  | ||||||
| site.jinja_env.trim_blocks = True |  | ||||||
| site.jinja_env.lstrip_blocks = True |  | ||||||
| site.debug = True |  | ||||||
| 
 |  | ||||||
| P = etiquette.photodb.PhotoDB() |  | ||||||
| 
 |  | ||||||
| session_manager = sessions.SessionManager() |  | ||||||
| 
 |  | ||||||
| #################################################################################################### |  | ||||||
| #################################################################################################### |  | ||||||
| #################################################################################################### |  | ||||||
| #################################################################################################### |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def back_url(): |  | ||||||
|     return request.args.get('goto') or request.referrer or '/' |  | ||||||
| 
 |  | ||||||
| def create_tag(easybake_string): |  | ||||||
|     notes = P.easybake(easybake_string) |  | ||||||
|     notes = [{'action': action, 'tagname': tagname} for (action, tagname) in notes] |  | ||||||
|     return notes |  | ||||||
| 
 |  | ||||||
| def delete_tag(tag): |  | ||||||
|     tag = tag.split('.')[-1].split('+')[0] |  | ||||||
|     tag = P.get_tag(tag) |  | ||||||
| 
 |  | ||||||
|     tag.delete() |  | ||||||
|     return {'action': 'delete_tag', 'tagname': tag.name} |  | ||||||
| 
 |  | ||||||
| def delete_synonym(synonym): |  | ||||||
|     synonym = synonym.split('+')[-1].split('.')[-1] |  | ||||||
| 
 |  | ||||||
|     try: |  | ||||||
|         master_tag = P.get_tag(synonym) |  | ||||||
|     except etiquette.exceptions.NoSuchTag as e: |  | ||||||
|         raise etiquette.exceptions.NoSuchSynonym(*e.given_args, **e.given_kwargs) |  | ||||||
|     else: |  | ||||||
|         master_tag.remove_synonym(synonym) |  | ||||||
| 
 |  | ||||||
|     return {'action':'delete_synonym', 'synonym': synonym} |  | ||||||
| 
 |  | ||||||
| def P_wrapper(function): |  | ||||||
|     def P_wrapped(thingid, response_type='html'): |  | ||||||
|         try: |  | ||||||
|             return function(thingid) |  | ||||||
| 
 |  | ||||||
|         except etiquette.exceptions.EtiquetteException as e: |  | ||||||
|             if isinstance(e, etiquette.exceptions.NoSuch): |  | ||||||
|                 status = 404 |  | ||||||
|             else: |  | ||||||
|                 status = 400 |  | ||||||
| 
 |  | ||||||
|             if response_type == 'html': |  | ||||||
|                 flask.abort(status, e.error_message) |  | ||||||
|             else: |  | ||||||
|                 response = etiquette.jsonify.exception(e) |  | ||||||
|                 response = jsonify.make_json_response(response, status=status) |  | ||||||
|                 flask.abort(response) |  | ||||||
| 
 |  | ||||||
|         except Exception as e: |  | ||||||
|             traceback.print_exc() |  | ||||||
|             if response_type == 'html': |  | ||||||
|                 flask.abort(500) |  | ||||||
|             else: |  | ||||||
|                 flask.abort(etiquette.jsonify.make_json_response({}, status=500)) |  | ||||||
| 
 |  | ||||||
|     return P_wrapped |  | ||||||
| 
 |  | ||||||
| @P_wrapper |  | ||||||
| def P_album(album_id): |  | ||||||
|     return P.get_album(album_id) |  | ||||||
| 
 |  | ||||||
| @P_wrapper |  | ||||||
| def P_bookmark(bookmarkid): |  | ||||||
|     return P.get_bookmark(bookmarkid) |  | ||||||
| 
 |  | ||||||
| @P_wrapper |  | ||||||
| def P_photo(photo_id): |  | ||||||
|     return P.get_photo(photo_id) |  | ||||||
| 
 |  | ||||||
| @P_wrapper |  | ||||||
| def P_tag(tagname): |  | ||||||
|     return P.get_tag(tagname) |  | ||||||
| 
 |  | ||||||
| @P_wrapper |  | ||||||
| def P_user(username): |  | ||||||
|     return P.get_user(username=username) |  | ||||||
| 
 |  | ||||||
| @P_wrapper |  | ||||||
| def P_user_id(user_id): |  | ||||||
|     return P.get_user(id=user_id) |  | ||||||
| 
 |  | ||||||
| def send_file(filepath, override_mimetype=None): |  | ||||||
|     ''' |  | ||||||
|     Range-enabled file sending. |  | ||||||
|     ''' |  | ||||||
|     try: |  | ||||||
|         file_size = os.path.getsize(filepath) |  | ||||||
|     except FileNotFoundError: |  | ||||||
|         flask.abort(404) |  | ||||||
| 
 |  | ||||||
|     outgoing_headers = {} |  | ||||||
|     if override_mimetype is not None: |  | ||||||
|         mimetype = override_mimetype |  | ||||||
|     else: |  | ||||||
|         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 = etiquette.helpers.read_filebytes( |  | ||||||
|             filepath, |  | ||||||
|             range_min=range_min, |  | ||||||
|             range_max=range_max, |  | ||||||
|             chunk_size=P.config['file_read_chunk'], |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     response = flask.Response( |  | ||||||
|         outgoing_data, |  | ||||||
|         status=status, |  | ||||||
|         headers=outgoing_headers, |  | ||||||
|     ) |  | ||||||
|     return response |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #################################################################################################### |  | ||||||
| #################################################################################################### |  | ||||||
| #################################################################################################### |  | ||||||
| #################################################################################################### |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def get_album_core(album_id): |  | ||||||
|     album = P_album(album_id) |  | ||||||
|     return album |  | ||||||
| 
 |  | ||||||
| def get_albums_core(): |  | ||||||
|     albums = list(P.get_root_albums()) |  | ||||||
|     albums.sort(key=lambda x: x.display_name.lower()) |  | ||||||
|     return albums |  | ||||||
| 
 |  | ||||||
| def get_search_core(): |  | ||||||
|     warning_bag = etiquette.objects.WarningBag() |  | ||||||
| 
 |  | ||||||
|     has_tags = request.args.get('has_tags') |  | ||||||
|     tag_musts = request.args.get('tag_musts') |  | ||||||
|     tag_mays = request.args.get('tag_mays') |  | ||||||
|     tag_forbids = request.args.get('tag_forbids') |  | ||||||
|     tag_expression = request.args.get('tag_expression') |  | ||||||
| 
 |  | ||||||
|     filename_terms = request.args.get('filename') |  | ||||||
|     extension = request.args.get('extension') |  | ||||||
|     extension_not = request.args.get('extension_not') |  | ||||||
|     mimetype = request.args.get('mimetype') |  | ||||||
| 
 |  | ||||||
|     limit = request.args.get('limit') |  | ||||||
|     # This is being pre-processed because the site enforces a maximum value |  | ||||||
|     # which the PhotoDB api does not. |  | ||||||
|     limit = etiquette.searchhelpers.normalize_limit(limit, warning_bag=warning_bag) |  | ||||||
| 
 |  | ||||||
|     if limit is None: |  | ||||||
|         limit = 50 |  | ||||||
|     else: |  | ||||||
|         limit = min(limit, 100) |  | ||||||
| 
 |  | ||||||
|     offset = request.args.get('offset') |  | ||||||
| 
 |  | ||||||
|     authors = request.args.get('author') |  | ||||||
| 
 |  | ||||||
|     orderby = request.args.get('orderby') |  | ||||||
|     area = request.args.get('area') |  | ||||||
|     width = request.args.get('width') |  | ||||||
|     height = request.args.get('height') |  | ||||||
|     ratio = request.args.get('ratio') |  | ||||||
|     bytes = request.args.get('bytes') |  | ||||||
|     duration = request.args.get('duration') |  | ||||||
|     created = request.args.get('created') |  | ||||||
| 
 |  | ||||||
|     # These are in a dictionary so I can pass them to the page template. |  | ||||||
|     search_kwargs = { |  | ||||||
|         'area': area, |  | ||||||
|         'width': width, |  | ||||||
|         'height': height, |  | ||||||
|         'ratio': ratio, |  | ||||||
|         'bytes': bytes, |  | ||||||
|         'duration': duration, |  | ||||||
| 
 |  | ||||||
|         'authors': authors, |  | ||||||
|         'created': created, |  | ||||||
|         'extension': extension, |  | ||||||
|         'extension_not': extension_not, |  | ||||||
|         'filename': filename_terms, |  | ||||||
|         'has_tags': has_tags, |  | ||||||
|         'mimetype': mimetype, |  | ||||||
|         'tag_musts': tag_musts, |  | ||||||
|         'tag_mays': tag_mays, |  | ||||||
|         'tag_forbids': tag_forbids, |  | ||||||
|         'tag_expression': tag_expression, |  | ||||||
| 
 |  | ||||||
|         'limit': limit, |  | ||||||
|         'offset': offset, |  | ||||||
|         'orderby': orderby, |  | ||||||
| 
 |  | ||||||
|         'warning_bag': warning_bag, |  | ||||||
|         'give_back_parameters': True |  | ||||||
|     } |  | ||||||
|     #print(search_kwargs) |  | ||||||
|     search_generator = P.search(**search_kwargs) |  | ||||||
|     # Because of the giveback, first element is cleaned up kwargs |  | ||||||
|     search_kwargs = next(search_generator) |  | ||||||
| 
 |  | ||||||
|     # The search has converted many arguments into sets or other types. |  | ||||||
|     # Convert them back into something that will display nicely on the search form. |  | ||||||
|     join_helper = lambda x: ', '.join(x) if x else None |  | ||||||
|     search_kwargs['extension'] = join_helper(search_kwargs['extension']) |  | ||||||
|     search_kwargs['extension_not'] = join_helper(search_kwargs['extension_not']) |  | ||||||
|     search_kwargs['mimetype'] = join_helper(search_kwargs['mimetype']) |  | ||||||
| 
 |  | ||||||
|     tagname_helper = lambda tags: [tag.qualified_name() for tag in tags] if tags else None |  | ||||||
|     search_kwargs['tag_musts'] = tagname_helper(search_kwargs['tag_musts']) |  | ||||||
|     search_kwargs['tag_mays'] = tagname_helper(search_kwargs['tag_mays']) |  | ||||||
|     search_kwargs['tag_forbids'] = tagname_helper(search_kwargs['tag_forbids']) |  | ||||||
| 
 |  | ||||||
|     search_results = list(search_generator) |  | ||||||
|     warnings = set() |  | ||||||
|     photos = [] |  | ||||||
|     for item in search_results: |  | ||||||
|         if isinstance(item, etiquette.objects.WarningBag): |  | ||||||
|             warnings.update(item.warnings) |  | ||||||
|         else: |  | ||||||
|             photos.append(item) |  | ||||||
| 
 |  | ||||||
|     # TAGS ON THIS PAGE |  | ||||||
|     total_tags = set() |  | ||||||
|     for photo in photos: |  | ||||||
|         for tag in photo.tags(): |  | ||||||
|             total_tags.add(tag) |  | ||||||
|     total_tags = sorted(total_tags, key=lambda t: t.qualified_name()) |  | ||||||
| 
 |  | ||||||
|     # PREV-NEXT PAGE URLS |  | ||||||
|     offset = search_kwargs['offset'] or 0 |  | ||||||
|     original_params = request.args.to_dict() |  | ||||||
|     original_params['limit'] = limit |  | ||||||
|     if len(photos) == limit: |  | ||||||
|         next_params = original_params.copy() |  | ||||||
|         next_params['offset'] = offset + limit |  | ||||||
|         next_params = etiquette.helpers.dict_to_params(next_params) |  | ||||||
|         next_page_url = '/search' + next_params |  | ||||||
|     else: |  | ||||||
|         next_page_url = None |  | ||||||
| 
 |  | ||||||
|     if offset > 0: |  | ||||||
|         prev_params = original_params.copy() |  | ||||||
|         prev_params['offset'] = max(0, offset - limit) |  | ||||||
|         prev_params = etiquette.helpers.dict_to_params(prev_params) |  | ||||||
|         prev_page_url = '/search' + prev_params |  | ||||||
|     else: |  | ||||||
|         prev_page_url = None |  | ||||||
| 
 |  | ||||||
|     view = request.args.get('view', 'grid') |  | ||||||
|     search_kwargs['view'] = view |  | ||||||
| 
 |  | ||||||
|     final_results = { |  | ||||||
|         'next_page_url': next_page_url, |  | ||||||
|         'prev_page_url': prev_page_url, |  | ||||||
|         'photos': photos, |  | ||||||
|         'total_tags': total_tags, |  | ||||||
|         'warnings': list(warnings), |  | ||||||
|         'search_kwargs': search_kwargs, |  | ||||||
|     } |  | ||||||
|     return final_results |  | ||||||
| 
 |  | ||||||
| def get_tags_core(specific_tag=None): |  | ||||||
|     if specific_tag is None: |  | ||||||
|         tags = P.get_tags() |  | ||||||
|     else: |  | ||||||
|         tags = specific_tag.walk_children() |  | ||||||
|     tags = list(tags) |  | ||||||
|     tags.sort(key=lambda x: x.qualified_name()) |  | ||||||
|     return tags |  | ||||||
| 
 |  | ||||||
| @decorators.catch_etiquette_exception |  | ||||||
| def post_photo_add_remove_tag_core(photo_id, tagname, add_or_remove): |  | ||||||
|     photo = P_photo(photo_id, response_type='json') |  | ||||||
|     tag = P_tag(tagname, response_type='json') |  | ||||||
| 
 |  | ||||||
|     if add_or_remove == 'add': |  | ||||||
|         photo.add_tag(tag) |  | ||||||
|     elif add_or_remove == 'remove': |  | ||||||
|         photo.remove_tag(tag) |  | ||||||
| 
 |  | ||||||
|     response = {'tagname': tag.name} |  | ||||||
|     return jsonify.make_json_response(response) |  | ||||||
| 
 |  | ||||||
| @decorators.catch_etiquette_exception |  | ||||||
| def post_tag_create_delete_core(tagname, function): |  | ||||||
|     return jsonify.make_json_response(function(tagname)) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #################################################################################################### |  | ||||||
| #################################################################################################### |  | ||||||
| #################################################################################################### |  | ||||||
| #################################################################################################### |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @site.route('/') |  | ||||||
| @session_manager.give_token |  | ||||||
| def root(): |  | ||||||
|     motd = random.choice(P.config['motd_strings']) |  | ||||||
|     session = session_manager.get(request) |  | ||||||
|     return flask.render_template('root.html', motd=motd, session=session) |  | ||||||
| 
 |  | ||||||
| @site.route('/album/<album_id>') |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_album_html(album_id): |  | ||||||
|     album = get_album_core(album_id) |  | ||||||
|     session = session_manager.get(request) |  | ||||||
|     response = flask.render_template( |  | ||||||
|         'album.html', |  | ||||||
|         album=album, |  | ||||||
|         session=session, |  | ||||||
|         view=request.args.get('view', 'grid'), |  | ||||||
|     ) |  | ||||||
|     return response |  | ||||||
| 
 |  | ||||||
| @site.route('/album/<album_id>.json') |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_album_json(album_id): |  | ||||||
|     album = get_album_core(album_id) |  | ||||||
|     album = etiquette.jsonify.album(album) |  | ||||||
|     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'] = [etiquette.jsonify.album(x, minimal=True) for x in album['sub_albums']] |  | ||||||
|     return jsonify.make_json_response(album) |  | ||||||
| 
 |  | ||||||
| @site.route('/album/<album_id>.zip') |  | ||||||
| def get_album_zip(album_id): |  | ||||||
|     album = P_album(album_id) |  | ||||||
| 
 |  | ||||||
|     recursive = request.args.get('recursive', True) |  | ||||||
|     recursive = etiquette.helpers.truthystring(recursive) |  | ||||||
| 
 |  | ||||||
|     arcnames = etiquette.helpers.album_zip_filenames(album, recursive=recursive) |  | ||||||
| 
 |  | ||||||
|     streamed_zip = zipstream.ZipFile() |  | ||||||
|     for (real_filepath, arcname) in arcnames.items(): |  | ||||||
|         streamed_zip.write(real_filepath, arcname=arcname) |  | ||||||
| 
 |  | ||||||
|     # Add the album metadata as an {id}.txt file within each directory. |  | ||||||
|     directories = etiquette.helpers.album_zip_directories(album, recursive=recursive) |  | ||||||
|     for (inner_album, directory) in directories.items(): |  | ||||||
|         text = [] |  | ||||||
|         if inner_album.title: |  | ||||||
|             text.append('Title: ' + inner_album.title) |  | ||||||
|         if inner_album.description: |  | ||||||
|             text.append('Description: ' + inner_album.description) |  | ||||||
|         if not text: |  | ||||||
|             continue |  | ||||||
|         text = '\r\n\r\n'.join(text) |  | ||||||
|         streamed_zip.writestr( |  | ||||||
|             arcname=os.path.join(directory, 'album %s.txt' % inner_album.id), |  | ||||||
|             data=text.encode('utf-8'), |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     if album.title: |  | ||||||
|         download_as = 'album %s - %s.zip' % (album.id, album.title) |  | ||||||
|     else: |  | ||||||
|         download_as = 'album %s.zip' % album.id |  | ||||||
| 
 |  | ||||||
|     download_as = etiquette.helpers.remove_path_badchars(download_as) |  | ||||||
|     download_as = urllib.parse.quote(download_as) |  | ||||||
|     outgoing_headers = { |  | ||||||
|         'Content-Type': 'application/octet-stream', |  | ||||||
|         'Content-Disposition': 'attachment; filename*=UTF-8\'\'%s' % download_as, |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
|     return flask.Response(streamed_zip, headers=outgoing_headers) |  | ||||||
| 
 |  | ||||||
| @site.route('/album/<album_id>/add_tag', methods=['POST']) |  | ||||||
| @decorators.catch_etiquette_exception |  | ||||||
| @session_manager.give_token |  | ||||||
| def post_album_add_tag(album_id): |  | ||||||
|     ''' |  | ||||||
|     Apply a tag to every photo in the album. |  | ||||||
|     ''' |  | ||||||
|     response = {} |  | ||||||
|     album = P_album(album_id) |  | ||||||
| 
 |  | ||||||
|     tag = request.form['tagname'].strip() |  | ||||||
|     try: |  | ||||||
|         tag = P_tag(tag) |  | ||||||
|     except etiquette.exceptions.NoSuchTag as e: |  | ||||||
|         response = etiquette.jsonify.exception(e) |  | ||||||
|         return jsonify.make_json_response(response, status=404) |  | ||||||
|     recursive = request.form.get('recursive', False) |  | ||||||
|     recursive = etiquette.helpers.truthystring(recursive) |  | ||||||
|     album.add_tag_to_all(tag, nested_children=recursive) |  | ||||||
|     response['action'] = 'add_tag' |  | ||||||
|     response['tagname'] = tag.name |  | ||||||
|     return jsonify.make_json_response(response) |  | ||||||
| 
 |  | ||||||
| @site.route('/album/<album_id>/add_photo', methods=['POST']) |  | ||||||
| @session_manager.give_token |  | ||||||
| @decorators.catch_etiquette_exception |  | ||||||
| @decorators.required_fields(['photo_id'], forbid_whitespace=True) |  | ||||||
| def post_album_add_photo(album_id): |  | ||||||
|     ''' |  | ||||||
|     Add a photo or photos to this album. |  | ||||||
|     ''' |  | ||||||
|     response = {} |  | ||||||
|     album = P_album(album_id) |  | ||||||
| 
 |  | ||||||
|     photo_ids = etiquette.helpers.comma_space_split(request.form['photo_id']) |  | ||||||
|     photos = [P_photo(photo_id) for photo_id in photo_ids] |  | ||||||
|     for photo in photos: |  | ||||||
|         album.add_photo(photo, commit=False) |  | ||||||
|     P.commit() |  | ||||||
|     return jsonify.make_json_response(response) |  | ||||||
| 
 |  | ||||||
| @site.route('/album/<album_id>/remove_photo', methods=['POST']) |  | ||||||
| @session_manager.give_token |  | ||||||
| @decorators.catch_etiquette_exception |  | ||||||
| @decorators.required_fields(['photo_id'], forbid_whitespace=True) |  | ||||||
| def post_album_remove_photo(album_id): |  | ||||||
|     ''' |  | ||||||
|     Remove a photo or photos from this album. |  | ||||||
|     ''' |  | ||||||
|     response = {} |  | ||||||
|     album = P_album(album_id) |  | ||||||
| 
 |  | ||||||
|     photo_ids = etiquette.helpers.comma_space_split(request.form['photo_id']) |  | ||||||
|     photos = [P_photo(photo_id) for photo_id in photo_ids] |  | ||||||
|     for photo in photos: |  | ||||||
|         album.remove_photo(photo, commit=False) |  | ||||||
|     P.commit() |  | ||||||
|     return jsonify.make_json_response(response) |  | ||||||
| 
 |  | ||||||
| @site.route('/album/<album_id>/edit', methods=['POST']) |  | ||||||
| @session_manager.give_token |  | ||||||
| @decorators.catch_etiquette_exception |  | ||||||
| def post_album_edit(album_id): |  | ||||||
|     ''' |  | ||||||
|     Edit the title / description. |  | ||||||
|     ''' |  | ||||||
|     album = P_album(album_id) |  | ||||||
| 
 |  | ||||||
|     title = request.form.get('title', None) |  | ||||||
|     description = request.form.get('description', None) |  | ||||||
|     album.edit(title=title, description=description) |  | ||||||
|     response = etiquette.jsonify.album(album, minimal=True) |  | ||||||
|     return jsonify.make_json_response(response) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @site.route('/albums') |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_albums_html(): |  | ||||||
|     albums = get_albums_core() |  | ||||||
|     session = session_manager.get(request) |  | ||||||
|     return flask.render_template('albums.html', albums=albums, session=session) |  | ||||||
| 
 |  | ||||||
| @site.route('/albums.json') |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_albums_json(): |  | ||||||
|     albums = get_albums_core() |  | ||||||
|     albums = [etiquette.jsonify.album(album, minimal=True) for album in albums] |  | ||||||
|     return jsonify.make_json_response(albums) |  | ||||||
| 
 |  | ||||||
| @site.route('/albums/create_album', methods=['POST']) |  | ||||||
| @decorators.catch_etiquette_exception |  | ||||||
| def post_albums_create(): |  | ||||||
|     title = request.form.get('title', None) |  | ||||||
|     description = request.form.get('description', None) |  | ||||||
|     parent = request.form.get('parent', None) |  | ||||||
|     if parent is not None: |  | ||||||
|         parent = P_album(parent) |  | ||||||
| 
 |  | ||||||
|     album = P.new_album(title=title, description=description) |  | ||||||
|     if parent is not None: |  | ||||||
|         parent.add_child(album) |  | ||||||
|     response = etiquette.jsonify.album(album, minimal=False) |  | ||||||
|     return jsonify.make_json_response(response) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @site.route('/bookmark/<bookmarkid>.json') |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_bookmark_json(bookmarkid): |  | ||||||
|     bookmark = P_bookmark(bookmarkid) |  | ||||||
|     response = etiquette.jsonify.bookmark(bookmark) |  | ||||||
|     return jsonify.make_json_response(response) |  | ||||||
| 
 |  | ||||||
| @site.route('/bookmark/<bookmarkid>/edit', methods=['POST']) |  | ||||||
| @session_manager.give_token |  | ||||||
| @decorators.catch_etiquette_exception |  | ||||||
| def post_bookmark_edit(bookmarkid): |  | ||||||
|     bookmark = P_bookmark(bookmarkid) |  | ||||||
|     # Emptystring is okay for titles, but not for URL. |  | ||||||
|     title = request.form.get('title', None) |  | ||||||
|     url = request.form.get('url', None) or None |  | ||||||
|     bookmark.edit(title=title, url=url) |  | ||||||
| 
 |  | ||||||
|     response = etiquette.jsonify.bookmark(bookmark) |  | ||||||
|     response = jsonify.make_json_response(response) |  | ||||||
|     return response |  | ||||||
| 
 |  | ||||||
| @site.route('/bookmarks') |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_bookmarks_html(): |  | ||||||
|     session = session_manager.get(request) |  | ||||||
|     bookmarks = list(P.get_bookmarks()) |  | ||||||
|     return flask.render_template('bookmarks.html', bookmarks=bookmarks, session=session) |  | ||||||
| 
 |  | ||||||
| @site.route('/bookmarks.json') |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_bookmarks_json(): |  | ||||||
|     bookmarks = [etiquette.jsonify.bookmark(b) for b in P.get_bookmarks()] |  | ||||||
|     return jsonify.make_json_response(bookmarks) |  | ||||||
| 
 |  | ||||||
| @site.route('/bookmarks/create_bookmark', methods=['POST']) |  | ||||||
| @decorators.catch_etiquette_exception |  | ||||||
| @decorators.required_fields(['url'], forbid_whitespace=True) |  | ||||||
| def post_bookmarks_create(): |  | ||||||
|     url = request.form['url'] |  | ||||||
|     title = request.form.get('title', None) |  | ||||||
|     bookmark = P.new_bookmark(url=url, title=title) |  | ||||||
|     response = etiquette.jsonify.bookmark(bookmark) |  | ||||||
|     response = jsonify.make_json_response(response) |  | ||||||
|     return response |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @site.route('/favicon.ico') |  | ||||||
| @site.route('/favicon.png') |  | ||||||
| def favicon(): |  | ||||||
|     return flask.send_file(FAVICON_PATH.absolute_path) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @site.route('/file/<photo_id>') |  | ||||||
| def get_file(photo_id): |  | ||||||
|     photo_id = photo_id.split('.')[0] |  | ||||||
|     photo = P.get_photo(photo_id) |  | ||||||
| 
 |  | ||||||
|     do_download = request.args.get('download', False) |  | ||||||
|     do_download = etiquette.helpers.truthystring(do_download) |  | ||||||
| 
 |  | ||||||
|     use_original_filename = request.args.get('original_filename', False) |  | ||||||
|     use_original_filename = etiquette.helpers.truthystring(use_original_filename) |  | ||||||
| 
 |  | ||||||
|     if do_download: |  | ||||||
|         if use_original_filename: |  | ||||||
|             download_as = photo.basename |  | ||||||
|         else: |  | ||||||
|             download_as = photo.id + photo.dot_extension |  | ||||||
| 
 |  | ||||||
|         download_as = etiquette.helpers.remove_path_badchars(download_as) |  | ||||||
|         download_as =  urllib.parse.quote(download_as) |  | ||||||
|         response = flask.make_response(send_file(photo.real_filepath)) |  | ||||||
|         response.headers['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'%s' % download_as |  | ||||||
|         return response |  | ||||||
|     else: |  | ||||||
|         return send_file(photo.real_filepath, override_mimetype=photo.mimetype) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @site.route('/login', methods=['GET']) |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_login(): |  | ||||||
|     session = session_manager.get(request) |  | ||||||
|     return flask.render_template('login.html', session=session) |  | ||||||
| 
 |  | ||||||
| @site.route('/login', methods=['POST']) |  | ||||||
| @session_manager.give_token |  | ||||||
| @decorators.required_fields(['username', 'password']) |  | ||||||
| def post_login(): |  | ||||||
|     if session_manager.get(request): |  | ||||||
|         e = etiquette.exceptions.AlreadySignedIn() |  | ||||||
|         response = etiquette.jsonify.exception(e) |  | ||||||
|         return jsonify.make_json_response(response, status=403) |  | ||||||
| 
 |  | ||||||
|     username = request.form['username'] |  | ||||||
|     password = request.form['password'] |  | ||||||
|     try: |  | ||||||
|         # Consideration: Should the server hash the password to discourage |  | ||||||
|         # information (user exists) leak via response time? |  | ||||||
|         # Currently I think not, because they can check if the account |  | ||||||
|         # page 404s anyway. |  | ||||||
|         user = P.get_user(username=username) |  | ||||||
|         user = P.login(user.id, password) |  | ||||||
|     except (etiquette.exceptions.NoSuchUser, etiquette.exceptions.WrongLogin): |  | ||||||
|         e = etiquette.exceptions.WrongLogin() |  | ||||||
|         response = etiquette.jsonify.exception(e) |  | ||||||
|         return jsonify.make_json_response(response, status=422) |  | ||||||
|     except etiquette.exceptions.FeatureDisabled as e: |  | ||||||
|         response = etiquette.jsonify.exception(e) |  | ||||||
|         return jsonify.make_json_response(response, status=400) |  | ||||||
|     session = sessions.Session(request, user) |  | ||||||
|     session_manager.add(session) |  | ||||||
|     return jsonify.make_json_response({}) |  | ||||||
| 
 |  | ||||||
| @site.route('/logout', methods=['GET', 'POST']) |  | ||||||
| @session_manager.give_token |  | ||||||
| def logout(): |  | ||||||
|     session_manager.remove(request) |  | ||||||
|     response = flask.Response('redirect', status=302, headers={'Location': back_url()}) |  | ||||||
|     return response |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @site.route('/photo/<photo_id>', methods=['GET']) |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_photo_html(photo_id): |  | ||||||
|     photo = P_photo(photo_id, response_type='html') |  | ||||||
|     session = session_manager.get(request) |  | ||||||
|     return flask.render_template('photo.html', photo=photo, session=session) |  | ||||||
| 
 |  | ||||||
| @site.route('/photo/<photo_id>.json', methods=['GET']) |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_photo_json(photo_id): |  | ||||||
|     photo = P_photo(photo_id, response_type='json') |  | ||||||
|     photo = etiquette.jsonify.photo(photo) |  | ||||||
|     photo = jsonify.make_json_response(photo) |  | ||||||
|     return photo |  | ||||||
| 
 |  | ||||||
| @site.route('/photo/<photo_id>/add_tag', methods=['POST']) |  | ||||||
| @decorators.required_fields(['tagname'], forbid_whitespace=True) |  | ||||||
| def post_photo_add_tag(photo_id): |  | ||||||
|     ''' |  | ||||||
|     Add a tag to this photo. |  | ||||||
|     ''' |  | ||||||
|     return post_photo_add_remove_tag_core(photo_id, request.form['tagname'], 'add') |  | ||||||
| 
 |  | ||||||
| @site.route('/photo/<photo_id>/refresh_metadata', methods=['POST']) |  | ||||||
| @decorators.catch_etiquette_exception |  | ||||||
| def post_photo_refresh_metadata(photo_id): |  | ||||||
|     ''' |  | ||||||
|     Refresh the file metadata. |  | ||||||
|     ''' |  | ||||||
|     P.caches['photo'].remove(photo_id) |  | ||||||
|     photo = P_photo(photo_id, response_type='json') |  | ||||||
|     photo.reload_metadata() |  | ||||||
|     if photo.thumbnail is None: |  | ||||||
|         try: |  | ||||||
|             photo.generate_thumbnail() |  | ||||||
|         except Exception: |  | ||||||
|             traceback.print_exc() |  | ||||||
| 
 |  | ||||||
|     return jsonify.make_json_response({}) |  | ||||||
| 
 |  | ||||||
| @site.route('/photo/<photo_id>/remove_tag', methods=['POST']) |  | ||||||
| @decorators.required_fields(['tagname'], forbid_whitespace=True) |  | ||||||
| def post_photo_remove_tag(photo_id): |  | ||||||
|     ''' |  | ||||||
|     Remove a tag from this photo. |  | ||||||
|     ''' |  | ||||||
|     return post_photo_add_remove_tag_core(photo_id, request.form['tagname'], 'remove') |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @site.route('/register', methods=['GET']) |  | ||||||
| def get_register(): |  | ||||||
|     return flask.redirect('/login') |  | ||||||
| 
 |  | ||||||
| @site.route('/register', methods=['POST']) |  | ||||||
| @session_manager.give_token |  | ||||||
| @decorators.catch_etiquette_exception |  | ||||||
| @decorators.required_fields(['username', 'password_1', 'password_2']) |  | ||||||
| def post_register(): |  | ||||||
|     if session_manager.get(request): |  | ||||||
|         e = etiquette.exceptions.AlreadySignedIn() |  | ||||||
|         response = etiquette.jsonify.exception(e) |  | ||||||
|         return jsonify.make_json_response(response, status=403) |  | ||||||
| 
 |  | ||||||
|     username = request.form['username'] |  | ||||||
|     password_1 = request.form['password_1'] |  | ||||||
|     password_2 = request.form['password_2'] |  | ||||||
| 
 |  | ||||||
|     if password_1 != password_2: |  | ||||||
|         response = { |  | ||||||
|             'error_type': 'PASSWORDS_DONT_MATCH', |  | ||||||
|             'error_message': 'Passwords do not match.', |  | ||||||
|         } |  | ||||||
|         return jsonify.make_json_response(response, status=422) |  | ||||||
| 
 |  | ||||||
|     user = P.register_user(username, password_1) |  | ||||||
| 
 |  | ||||||
|     session = sessions.Session(request, user) |  | ||||||
|     session_manager.add(session) |  | ||||||
|     return jsonify.make_json_response({}) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @site.route('/search') |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_search_html(): |  | ||||||
|     search_results = get_search_core() |  | ||||||
|     search_kwargs = search_results['search_kwargs'] |  | ||||||
|     qualname_map = etiquette.tag_export.qualified_names(P.get_tags()) |  | ||||||
|     session = session_manager.get(request) |  | ||||||
|     response = flask.render_template( |  | ||||||
|         'search.html', |  | ||||||
|         next_page_url=search_results['next_page_url'], |  | ||||||
|         prev_page_url=search_results['prev_page_url'], |  | ||||||
|         photos=search_results['photos'], |  | ||||||
|         qualname_map=json.dumps(qualname_map), |  | ||||||
|         search_kwargs=search_kwargs, |  | ||||||
|         session=session, |  | ||||||
|         total_tags=search_results['total_tags'], |  | ||||||
|         warnings=search_results['warnings'], |  | ||||||
|     ) |  | ||||||
|     return response |  | ||||||
| 
 |  | ||||||
| @site.route('/search.json') |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_search_json(): |  | ||||||
|     search_results = get_search_core() |  | ||||||
|     search_results['photos'] = [ |  | ||||||
|         etiquette.jsonify.photo(photo, include_albums=False) for photo in search_results['photos'] |  | ||||||
|     ] |  | ||||||
|     return jsonify.make_json_response(search_results) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @site.route('/tag/<specific_tag_name>') |  | ||||||
| @site.route('/tags') |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_tags_html(specific_tag_name=None): |  | ||||||
|     if specific_tag_name is None: |  | ||||||
|         specific_tag = None |  | ||||||
|     else: |  | ||||||
|         specific_tag = P_tag(specific_tag_name, response_type='html') |  | ||||||
|         if specific_tag.name != specific_tag_name: |  | ||||||
|             new_url = request.url.replace('/tag/' + specific_tag_name, '/tag/' + specific_tag.name) |  | ||||||
|             response = flask.redirect(new_url) |  | ||||||
|             return response |  | ||||||
|     tags = get_tags_core(specific_tag) |  | ||||||
|     session = session_manager.get(request) |  | ||||||
|     include_synonyms = request.args.get('synonyms') |  | ||||||
|     include_synonyms = include_synonyms is None or etiquette.helpers.truthystring(include_synonyms) |  | ||||||
|     response = flask.render_template( |  | ||||||
|         'tags.html', |  | ||||||
|         include_synonyms=include_synonyms, |  | ||||||
|         session=session, |  | ||||||
|         specific_tag=specific_tag, |  | ||||||
|         tags=tags, |  | ||||||
|     ) |  | ||||||
|     return response |  | ||||||
| 
 |  | ||||||
| @site.route('/tag/<specific_tag_name>.json') |  | ||||||
| @site.route('/tags.json') |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_tags_json(specific_tag_name=None): |  | ||||||
|     if specific_tag_name is None: |  | ||||||
|         specific_tag = None |  | ||||||
|     else: |  | ||||||
|         specific_tag = P_tag(specific_tag_name, response_type='json') |  | ||||||
|         if specific_tag.name != specific_tag_name: |  | ||||||
|             new_url = request.url.replace('/tag/' + specific_tag_name, '/tag/' + specific_tag.name) |  | ||||||
|             return flask.redirect(new_url) |  | ||||||
|     tags = get_tags_core(specific_tag=specific_tag) |  | ||||||
|     include_synonyms = request.args.get('synonyms') |  | ||||||
|     include_synonyms = include_synonyms is None or etiquette.helpers.truthystring(include_synonyms) |  | ||||||
|     tags = [etiquette.jsonify.tag(tag, include_synonyms=include_synonyms) for tag in tags] |  | ||||||
|     return jsonify.make_json_response(tags) |  | ||||||
| 
 |  | ||||||
| @site.route('/tag/<specific_tag>/edit', methods=['POST']) |  | ||||||
| @decorators.catch_etiquette_exception |  | ||||||
| def post_tag_edit(specific_tag): |  | ||||||
|     tag = P_tag(specific_tag) |  | ||||||
|     name = request.form.get('name', '').strip() |  | ||||||
|     if name: |  | ||||||
|         tag.rename(name, commit=False) |  | ||||||
| 
 |  | ||||||
|     description = request.form.get('description', None) |  | ||||||
|     tag.edit(description=description) |  | ||||||
| 
 |  | ||||||
|     response = etiquette.jsonify.tag(tag) |  | ||||||
|     response = jsonify.make_json_response(response) |  | ||||||
|     return response |  | ||||||
| 
 |  | ||||||
| @site.route('/tags/<specific_tag>') |  | ||||||
| @site.route('/tags/<specific_tag>.json') |  | ||||||
| def get_tags_specific_redirect(specific_tag): |  | ||||||
|     return flask.redirect(request.url.replace('/tags/', '/tag/')) |  | ||||||
| 
 |  | ||||||
| @site.route('/tags/create_tag', methods=['POST']) |  | ||||||
| @decorators.required_fields(['tagname'], forbid_whitespace=True) |  | ||||||
| def post_tag_create(): |  | ||||||
|     ''' |  | ||||||
|     Create a tag. |  | ||||||
|     ''' |  | ||||||
|     return post_tag_create_delete_core(request.form['tagname'], create_tag) |  | ||||||
| 
 |  | ||||||
| @site.route('/tags/delete_synonym', methods=['POST']) |  | ||||||
| @decorators.required_fields(['tagname'], forbid_whitespace=True) |  | ||||||
| def post_tag_delete_synonym(): |  | ||||||
|     ''' |  | ||||||
|     Delete a synonym. |  | ||||||
|     ''' |  | ||||||
|     return post_tag_create_delete_core(request.form['tagname'], delete_synonym) |  | ||||||
| 
 |  | ||||||
| @site.route('/tags/delete_tag', methods=['POST']) |  | ||||||
| @decorators.required_fields(['tagname'], forbid_whitespace=True) |  | ||||||
| def post_tag_delete(): |  | ||||||
|     ''' |  | ||||||
|     Delete a tag. |  | ||||||
|     ''' |  | ||||||
|     return post_tag_create_delete_core(request.form['tagname'], delete_tag) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @site.route('/thumbnail/<photo_id>') |  | ||||||
| def get_thumbnail(photo_id): |  | ||||||
|     photo_id = photo_id.split('.')[0] |  | ||||||
|     photo = P_photo(photo_id) |  | ||||||
|     if photo.thumbnail: |  | ||||||
|         path = photo.thumbnail |  | ||||||
|     else: |  | ||||||
|         flask.abort(404, 'That file doesnt have a thumbnail') |  | ||||||
|     return send_file(path) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @site.route('/user/<username>', methods=['GET']) |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_user_html(username): |  | ||||||
|     user = P_user(username, response_type='html') |  | ||||||
|     session = session_manager.get(request) |  | ||||||
|     return flask.render_template('user.html', user=user, session=session) |  | ||||||
| 
 |  | ||||||
| @site.route('/user/<username>.json', methods=['GET']) |  | ||||||
| @session_manager.give_token |  | ||||||
| def get_user_json(username): |  | ||||||
|     user = P_user(username, response_type='json') |  | ||||||
|     user = etiquette.jsonify.user(user) |  | ||||||
|     return jsonify.make_json_response(user) |  | ||||||
| 
 |  | ||||||
| @site.route('/userid/<user_id>') |  | ||||||
| @site.route('/userid/<user_id>.json') |  | ||||||
| def get_user_id_redirect(user_id): |  | ||||||
|     if request.url.endswith('.json'): |  | ||||||
|         user = P_user_id(user_id, response_type='json') |  | ||||||
|     else: |  | ||||||
|         user = P_user_id(user_id, response_type='html') |  | ||||||
|     url_from = '/userid/' + user_id |  | ||||||
|     url_to = '/user/' + user.username |  | ||||||
|     url = request.url.replace(url_from, url_to) |  | ||||||
|     return flask.redirect(url) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @site.route('/apitest') |  | ||||||
| @session_manager.give_token |  | ||||||
| def apitest(): |  | ||||||
|     response = flask.Response('testing') |  | ||||||
|     return response |  | ||||||
| 
 |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     #site.run(threaded=True) |  | ||||||
|     pass |  | ||||||
		Loading…
	
		Reference in a new issue