From 3077b025086822ddd17170fea8634c4c84b0a896 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Fri, 24 Apr 2020 11:37:55 -0700 Subject: [PATCH] Kick off the server refactor with home and auth --- amanuensis/cli/server.py | 17 +- amanuensis/resources/global.json | 2 +- amanuensis/server/__init__.py | 65 +++-- amanuensis/server/auth.py | 62 +++-- amanuensis/server/forms.py | 378 ++++++++++++++-------------- amanuensis/server/helpers.py | 59 ++--- amanuensis/server/home.py | 94 ++++--- amanuensis/templates/home/home.html | 2 +- amanuensis/templates/macros.html | 34 +-- amanuensis/templates/page.html | 2 +- 10 files changed, 374 insertions(+), 341 deletions(-) diff --git a/amanuensis/cli/server.py b/amanuensis/cli/server.py index 85caee9..ad8e13e 100644 --- a/amanuensis/cli/server.py +++ b/amanuensis/cli/server.py @@ -69,14 +69,17 @@ def command_run(args): The default Flask server is not secure, and should only be used for development. """ - from amanuensis.server import app - from amanuensis.config import get, logger + from amanuensis.server import get_app - if get("secret_key") is None: - logger.error("Can't run server without a secret_key. Run generate-sec" - "ret first") - return -1 - app.run(host=args.address, port=args.port, debug=args.debug) + root: RootConfigDirectoryContext = args.root + + with root.read_config() as cfg: + if cfg.secret_key is None: + logger.error("Can't run server without a secret_key. " + "Run generate-secet first.") + return -1 + + get_app(root).run(host=args.address, port=args.port, debug=args.debug) return 0 diff --git a/amanuensis/resources/global.json b/amanuensis/resources/global.json index fbefaba..b33b20a 100644 --- a/amanuensis/resources/global.json +++ b/amanuensis/resources/global.json @@ -4,7 +4,7 @@ "address": "127.0.0.1", "port": "5000", "lexicon_data": "./lexicon", - "static_root": "./static", + "static_root": "../resources", "email": { "server": null, "port": null, diff --git a/amanuensis/server/__init__.py b/amanuensis/server/__init__.py index a80385b..f489b59 100644 --- a/amanuensis/server/__init__.py +++ b/amanuensis/server/__init__.py @@ -1,41 +1,38 @@ -import os +from flask import Flask -from flask import Flask, render_template -from flask_login import LoginManager - -from amanuensis.config import get, root -# from amanuensis.server.auth import get_bp as get_auth_bp -from amanuensis.server.home import get_bp as get_home_bp -from amanuensis.server.helpers import register_custom_filters -from amanuensis.server.lexicon import get_bp as get_lex_bp -from amanuensis.user import AnonymousUserModel +from amanuensis.config import RootConfigDirectoryContext from amanuensis.models import ModelFactory +from amanuensis.server.auth import get_login_manager, bp_auth +from amanuensis.server.helpers import register_custom_filters +from amanuensis.server.home import bp_home +# from amanuensis.server.lexicon import bp_lexicon -# Flask app init -static_root = os.path.abspath(get("static_root")) -app = Flask( - __name__, - template_folder="../templates", - static_folder=static_root) -app.secret_key = bytes.fromhex(get('secret_key')) -app.config['model_factory'] = ModelFactory(root) -app.jinja_options['trim_blocks'] = True -app.jinja_options['lstrip_blocks'] = True -register_custom_filters(app) -# Flask-Login init -login = LoginManager(app) -login.login_view = 'auth.login' -login.anonymous_user = AnonymousUserModel +def get_app(root: RootConfigDirectoryContext) -> Flask: + # Flask app init + with root.read_config() as cfg: + app = Flask( + __name__, + template_folder='../templates', + static_folder=cfg.static_root + ) + app.secret_key = bytes.fromhex(cfg.secret_key) + app.config['root'] = root + app.config['model_factory'] = ModelFactory(root) + app.jinja_options['trim_blocks'] = True + app.jinja_options['lstrip_blocks'] = True + register_custom_filters(app) -# Blueprint inits -from amanuensis.server.auth import bp as auth_bp -from amanuensis.server.auth import login_manager as login_manager -login_manager.init_app(app) -app.register_blueprint(auth_bp) + # Flask-Login init + login_manager = get_login_manager(root) + login_manager.init_app(app) -home_bp = get_home_bp() -app.register_blueprint(home_bp) + # Blueprint inits + app.register_blueprint(bp_auth) + app.register_blueprint(bp_home) + # app.register_blueprint(bp_lexicon) -lex_bp = get_lex_bp() -app.register_blueprint(lex_bp) + # import code + # code.interact(local=locals()) + + return app diff --git a/amanuensis/server/auth.py b/amanuensis/server/auth.py index 2f1d00d..6c01e3d 100644 --- a/amanuensis/server/auth.py +++ b/amanuensis/server/auth.py @@ -1,42 +1,64 @@ +import logging import time -from flask import Blueprint, render_template, redirect, url_for, flash, current_app -from flask_login import login_user, logout_user, login_required, LoginManager +from flask import ( + Blueprint, + render_template, + redirect, + url_for, + flash, + current_app) +from flask_login import ( + login_user, + logout_user, + login_required, + LoginManager) -from amanuensis.config import logger, json_rw +from amanuensis.config import RootConfigDirectoryContext from amanuensis.server.forms import LoginForm -from amanuensis.user import UserModel, AnonymousUserModel +from amanuensis.models import ModelFactory, AnonymousUserModel -# TODO refactor login init into a func that takes a root cdc +logger = logging.getLogger(__name__) -login_manager = LoginManager() -login_manager.login_view = 'auth.login' -login_manager.anonymous_user = AnonymousUserModel -bp = Blueprint('auth', __name__, url_prefix='/auth') +def get_login_manager(root: RootConfigDirectoryContext) -> LoginManager: + """ + Creates a login manager + """ + login_manager = LoginManager() + login_manager.login_view = 'auth.login' + login_manager.anonymous_user = AnonymousUserModel -@login_manager.user_loader -def load_user(uid): - return UserModel.by(uid=str(uid)) + @login_manager.user_loader + def load_user(uid): + return current_app.config['model_factory'].user(str(uid)) -@bp.route('/login/', methods=['GET', 'POST']) + return login_manager + + +bp_auth = Blueprint('auth', __name__, url_prefix='/auth') + + +@bp_auth.route('/login/', methods=['GET', 'POST']) def login(): + model_factory: ModelFactory = current_app.config['model_factory'] form = LoginForm() if form.validate_on_submit(): username = form.username.data - u = UserModel.by(name=username) - if u is not None and u.check_password(form.password.data): + user = model_factory.try_user(username) + if user is not None and user.check_password(form.password.data): remember_me = form.remember.data - login_user(u, remember=remember_me) - with json_rw(u.config_path) as cfg: + login_user(user, remember=remember_me) + with user.ctx.edit_config() as cfg: cfg.last_login = int(time.time()) - logger.info("Logged in user '{}' ({})".format( - u.username, u.uid)) + logger.info('Logged in user "{0.username}" ({0.uid})' + .format(user.cfg)) return redirect(url_for('home.home')) flash("Login not recognized") return render_template('auth/login.html', form=form) -@bp.route("/logout/", methods=['GET']) + +@bp_auth.route("/logout/", methods=['GET']) @login_required def logout(): logout_user() diff --git a/amanuensis/server/forms.py b/amanuensis/server/forms.py index 2717ed9..5e8386e 100644 --- a/amanuensis/server/forms.py +++ b/amanuensis/server/forms.py @@ -1,3 +1,4 @@ +from flask import current_app from flask_wtf import FlaskForm from wtforms import ( StringField, PasswordField, BooleanField, SubmitField, TextAreaField, @@ -5,9 +6,10 @@ from wtforms import ( from wtforms.validators import DataRequired, ValidationError, Optional from wtforms.widgets.html5 import NumberInput -from amanuensis.config import root -from amanuensis.lexicon.manage import add_character -from amanuensis.user import UserModel +# from amanuensis.config import root +# from amanuensis.lexicon.manage import add_character +# from amanuensis.user import UserModel +from amanuensis.config import RootConfigDirectoryContext # Custom validators @@ -15,10 +17,13 @@ def user(exists=True): template = 'User "{{}}" {}'.format( "not found" if exists else "already exists") should_exist = bool(exists) + def validate_user(form, field): - with root.user.index() as index: + root: RootConfigDirectoryContext = current_app.config['root'] + with root.user.read_index() as index: if (field.data in index.keys()) != should_exist: raise ValidationError(template.format(field.data)) + return validate_user @@ -26,17 +31,21 @@ def lexicon(exists=True): template = 'Lexicon "{{}}" {}'.format( "not found" if exists else "already exists") should_exist = bool(exists) + def validate_lexicon(form, field): - with root.lexicon.index() as index: + root: RootConfigDirectoryContext = current_app.config['root'] + with root.lexicon.read_index() as index: if (field.data in index.keys()) != should_exist: raise ValidationError(template.format(field.data)) + return validate_lexicon # Forms class LoginForm(FlaskForm): """/auth/login/""" - username = StringField('Username', validators=[DataRequired(), user(exists=True)]) + username = StringField('Username', + validators=[DataRequired(), user(exists=True)]) password = PasswordField('Password', validators=[DataRequired()]) remember = BooleanField('Stay logged in') submit = SubmitField('Log in') @@ -54,194 +63,199 @@ class LexiconCreateForm(FlaskForm): submit = SubmitField('Create') -class LexiconConfigForm(FlaskForm): - """/lexicon//session/settings/""" - # General - title = StringField('Title override', validators=[Optional()]) - editor = SelectField('Editor', validators=[DataRequired(), user(exists=True)]) - prompt = TextAreaField('Prompt', validators=[DataRequired()]) - # Turn - turnCurrent = IntegerField('Current turn', widget=NumberInput(), validators=[Optional()]) - turnMax = IntegerField('Number of turns', widget=NumberInput(), validators=[DataRequired()]) - # Join - joinPublic = BooleanField("Show game on public pages") - joinOpen = BooleanField("Allow players to join game") - joinPassword = StringField("Password to join game", validators=[Optional()]) - joinMaxPlayers = IntegerField( - "Maximum number of players", - widget=NumberInput(), - validators=[DataRequired()]) - joinCharsPerPlayer = IntegerField( - "Characters per player", - widget=NumberInput(), - validators=[DataRequired()]) - # Publish - publishNotifyEditorOnReady = BooleanField( - "Notify the editor when a player marks an article as ready") - publishNotifyPlayerOnReject = BooleanField( - "Notify a player when their article is rejected by the editor") - publishNotifyPlayerOnAccept = BooleanField( - "Notify a player when their article is accepted by the editor") - publishDeadlines = StringField( - "Turn deadline, as a crontab specification", validators=[Optional()]) - publishAsap = BooleanField( - "Publish the turn immediately when the last article is accepted") - publishQuorum = IntegerField( - "Quorum to publish incomplete turn", widget=NumberInput(), validators=[Optional()]) - publishBlockOnReady = BooleanField( - "Block turn publish if any articles are awaiting editor review") - # Article - articleIndexList = TextAreaField("Index specifications") - articleIndexCapacity = IntegerField( - "Index capacity override", widget=NumberInput(), validators=[Optional()]) - articleCitationAllowSelf = BooleanField( - "Allow players to cite themselves") - articleCitationMinExtant = IntegerField( - "Minimum number of extant articles to cite", widget=NumberInput(), validators=[Optional()]) - articleCitationMaxExtant = IntegerField( - "Maximum number of extant articles to cite", widget=NumberInput(), validators=[Optional()]) - articleCitationMinPhantom = IntegerField( - "Minimum number of phantom articles to cite", widget=NumberInput(), validators=[Optional()]) - articleCitationMaxPhantom = IntegerField( - "Maximum number of phantom articles to cite", widget=NumberInput(), validators=[Optional()]) - articleCitationMinTotal = IntegerField( - "Minimum number of articles to cite in total", widget=NumberInput(), validators=[Optional()]) - articleCitationMaxTotal = IntegerField( - "Maximum number of articles to cite in total", widget=NumberInput(), validators=[Optional()]) - articleCitationMinChars = IntegerField( - "Minimum number of characters to cite articles by", - widget=NumberInput(), validators=[Optional()]) - articleCitationMaxChars = IntegerField( - "Maximum number of characters to cite articles by", - widget=NumberInput(), validators=[Optional()]) - articleWordLimitSoft = IntegerField( - "Soft word limit", widget=NumberInput(), validators=[Optional()]) - articleWordLimitHard = IntegerField( - "Hard word limit", widget=NumberInput(), validators=[Optional()]) - articleAddendumAllowed = BooleanField("Allow addendum articles") - articleAddendumMax = IntegerField( - "Maximum number of addendum articles per character per turn", - widget=NumberInput(), validators=[Optional()]) - # And finally, the submit button - submit = SubmitField("Submit") +# class LexiconConfigForm(FlaskForm): +# """/lexicon//session/settings/""" +# # General +# title = StringField('Title override', validators=[Optional()]) +# editor = SelectField('Editor', validators=[DataRequired(), user(exists=True)]) +# prompt = TextAreaField('Prompt', validators=[DataRequired()]) +# # Turn +# turnCurrent = IntegerField('Current turn', widget=NumberInput(), validators=[Optional()]) +# turnMax = IntegerField('Number of turns', widget=NumberInput(), validators=[DataRequired()]) +# # Join +# joinPublic = BooleanField("Show game on public pages") +# joinOpen = BooleanField("Allow players to join game") +# joinPassword = StringField("Password to join game", validators=[Optional()]) +# joinMaxPlayers = IntegerField( +# "Maximum number of players", +# widget=NumberInput(), +# validators=[DataRequired()]) +# joinCharsPerPlayer = IntegerField( +# "Characters per player", +# widget=NumberInput(), +# validators=[DataRequired()]) +# # Publish +# publishNotifyEditorOnReady = BooleanField( +# "Notify the editor when a player marks an article as ready") +# publishNotifyPlayerOnReject = BooleanField( +# "Notify a player when their article is rejected by the editor") +# publishNotifyPlayerOnAccept = BooleanField( +# "Notify a player when their article is accepted by the editor") +# publishDeadlines = StringField( +# "Turn deadline, as a crontab specification", validators=[Optional()]) +# publishAsap = BooleanField( +# "Publish the turn immediately when the last article is accepted") +# publishQuorum = IntegerField( +# "Quorum to publish incomplete turn", widget=NumberInput(), validators=[Optional()]) +# publishBlockOnReady = BooleanField( +# "Block turn publish if any articles are awaiting editor review") +# # Article +# articleIndexList = TextAreaField("Index specifications") +# articleIndexCapacity = IntegerField( +# "Index capacity override", widget=NumberInput(), validators=[Optional()]) +# articleCitationAllowSelf = BooleanField( +# "Allow players to cite themselves") +# articleCitationMinExtant = IntegerField( +# "Minimum number of extant articles to cite", widget=NumberInput(), validators=[Optional()]) +# articleCitationMaxExtant = IntegerField( +# "Maximum number of extant articles to cite", widget=NumberInput(), validators=[Optional()]) +# articleCitationMinPhantom = IntegerField( +# "Minimum number of phantom articles to cite", widget=NumberInput(), validators=[Optional()]) +# articleCitationMaxPhantom = IntegerField( +# "Maximum number of phantom articles to cite", widget=NumberInput(), validators=[Optional()]) +# articleCitationMinTotal = IntegerField( +# "Minimum number of articles to cite in total", widget=NumberInput(), validators=[Optional()]) +# articleCitationMaxTotal = IntegerField( +# "Maximum number of articles to cite in total", widget=NumberInput(), validators=[Optional()]) +# articleCitationMinChars = IntegerField( +# "Minimum number of characters to cite articles by", +# widget=NumberInput(), validators=[Optional()]) +# articleCitationMaxChars = IntegerField( +# "Maximum number of characters to cite articles by", +# widget=NumberInput(), validators=[Optional()]) +# articleWordLimitSoft = IntegerField( +# "Soft word limit", widget=NumberInput(), validators=[Optional()]) +# articleWordLimitHard = IntegerField( +# "Hard word limit", widget=NumberInput(), validators=[Optional()]) +# articleAddendumAllowed = BooleanField("Allow addendum articles") +# articleAddendumMax = IntegerField( +# "Maximum number of addendum articles per character per turn", +# widget=NumberInput(), validators=[Optional()]) +# # And finally, the submit button +# submit = SubmitField("Submit") - def validate_publishDeadlines(form, field): - if form.publishAsap.data: - raise ValidationError('Cannot specify deadline if immediate publishing is enabled') +# def validate_publishDeadlines(form, field): +# if form.publishAsap.data: +# raise ValidationError('Cannot specify deadline if immediate publishing is enabled') - # TODO add validators that call into extant valid check methods +# # TODO add validators that call into extant valid check methods - def set_options(self, lexicon): - self.editor.choices = list(map(lambda x: (x, x), map( - lambda uid: UserModel.by(uid=uid).username, - lexicon.join.joined))) +# def set_options(self, lexicon): +# self.editor.choices = list(map(lambda x: (x, x), map( +# lambda uid: UserModel.by(uid=uid).username, +# lexicon.join.joined))) - def populate_from_lexicon(self, lexicon): - self.title.data = lexicon.title - self.editor.data = UserModel.by(uid=lexicon.editor).username - self.prompt.data = lexicon.prompt - self.turnCurrent.data = lexicon.turn.current - self.turnMax.data = lexicon.turn.max - self.joinPublic.data = lexicon.join.public - self.joinOpen.data = lexicon.join.open - self.joinPassword.data = lexicon.join.password - self.joinMaxPlayers.data = lexicon.join.max_players - self.joinCharsPerPlayer.data = lexicon.join.chars_per_player - self.publishNotifyEditorOnReady.data = lexicon.publish.notify.editor_on_ready - self.publishNotifyPlayerOnReject.data = lexicon.publish.notify.player_on_reject - self.publishNotifyPlayerOnAccept.data = lexicon.publish.notify.player_on_accept - self.publishDeadlines.data = lexicon.publish.deadlines - self.publishAsap.data = lexicon.publish.asap - self.publishQuorum.data = lexicon.publish.quorum - self.publishBlockOnReady.data = lexicon.publish.block_on_ready - self.articleIndexList.data = lexicon.article.index.list - self.articleIndexCapacity.data = lexicon.article.index.capacity - self.articleCitationAllowSelf.data = lexicon.article.citation.allow_self - self.articleCitationMinExtant.data = lexicon.article.citation.min_extant - self.articleCitationMaxExtant.data = lexicon.article.citation.max_extant - self.articleCitationMinPhantom.data = lexicon.article.citation.min_phantom - self.articleCitationMaxPhantom.data = lexicon.article.citation.max_phantom - self.articleCitationMinTotal.data = lexicon.article.citation.min_total - self.articleCitationMaxTotal.data = lexicon.article.citation.max_total - self.articleCitationMinChars.data = lexicon.article.citation.min_chars - self.articleCitationMaxChars.data = lexicon.article.citation.max_chars - self.articleWordLimitSoft.data = lexicon.article.word_limit.soft - self.articleWordLimitHard.data = lexicon.article.word_limit.hard - self.articleAddendumAllowed.data = lexicon.article.addendum.allowed - self.articleAddendumMax.data = lexicon.article.addendum.max +# def populate_from_lexicon(self, lexicon): +# self.title.data = lexicon.title +# self.editor.data = UserModel.by(uid=lexicon.editor).username +# self.prompt.data = lexicon.prompt +# self.turnCurrent.data = lexicon.turn.current +# self.turnMax.data = lexicon.turn.max +# self.joinPublic.data = lexicon.join.public +# self.joinOpen.data = lexicon.join.open +# self.joinPassword.data = lexicon.join.password +# self.joinMaxPlayers.data = lexicon.join.max_players +# self.joinCharsPerPlayer.data = lexicon.join.chars_per_player +# self.publishNotifyEditorOnReady.data = lexicon.publish.notify.editor_on_ready +# self.publishNotifyPlayerOnReject.data = lexicon.publish.notify.player_on_reject +# self.publishNotifyPlayerOnAccept.data = lexicon.publish.notify.player_on_accept +# self.publishDeadlines.data = lexicon.publish.deadlines +# self.publishAsap.data = lexicon.publish.asap +# self.publishQuorum.data = lexicon.publish.quorum +# self.publishBlockOnReady.data = lexicon.publish.block_on_ready +# self.articleIndexList.data = lexicon.article.index.list +# self.articleIndexCapacity.data = lexicon.article.index.capacity +# self.articleCitationAllowSelf.data = lexicon.article.citation.allow_self +# self.articleCitationMinExtant.data = lexicon.article.citation.min_extant +# self.articleCitationMaxExtant.data = lexicon.article.citation.max_extant +# self.articleCitationMinPhantom.data = lexicon.article.citation.min_phantom +# self.articleCitationMaxPhantom.data = lexicon.article.citation.max_phantom +# self.articleCitationMinTotal.data = lexicon.article.citation.min_total +# self.articleCitationMaxTotal.data = lexicon.article.citation.max_total +# self.articleCitationMinChars.data = lexicon.article.citation.min_chars +# self.articleCitationMaxChars.data = lexicon.article.citation.max_chars +# self.articleWordLimitSoft.data = lexicon.article.word_limit.soft +# self.articleWordLimitHard.data = lexicon.article.word_limit.hard +# self.articleAddendumAllowed.data = lexicon.article.addendum.allowed +# self.articleAddendumMax.data = lexicon.article.addendum.max - def update_lexicon(self, lexicon): - with lexicon.edit() as l: - l.title = self.title.data - l.editor = UserModel.by(name=self.editor.data).uid - l.prompt = self.prompt.data - l.turn.current = self.turnCurrent.data - l.turn.max = self.turnMax.data - l.join.public = self.joinPublic.data - l.join.open = self.joinOpen.data - l.join.password = self.joinPassword.data - l.join.max_players = self.joinMaxPlayers.data - l.join.chars_per_player = self.joinCharsPerPlayer.data - l.publish.notify.editor_on_ready = self.publishNotifyEditorOnReady.data - l.publish.notify.player_on_reject = self.publishNotifyPlayerOnReject.data - l.publish.notify.player_on_accept = self.publishNotifyPlayerOnAccept.data - l.publish.deadlines = self.publishDeadlines.data - l.publish.asap = self.publishAsap.data - l.publish.quorum = self.publishQuorum.data - l.publish.block_on_ready = self.publishBlockOnReady.data - l.article.index.list = self.articleIndexList.data - l.article.index.capacity = self.articleIndexCapacity.data - l.article.citation.allow_self = self.articleCitationAllowSelf.data - l.article.citation.min_extant = self.articleCitationMinExtant.data - l.article.citation.max_extant = self.articleCitationMaxExtant.data - l.article.citation.min_phantom = self.articleCitationMinPhantom.data - l.article.citation.max_phantom = self.articleCitationMaxPhantom.data - l.article.citation.min_total = self.articleCitationMinTotal.data - l.article.citation.max_total = self.articleCitationMaxTotal.data - l.article.citation.min_chars = self.articleCitationMinChars.data - l.article.citation.max_chars = self.articleCitationMaxChars.data - l.article.word_limit.soft = self.articleWordLimitSoft.data - l.article.word_limit.hard = self.articleWordLimitHard.data - l.article.addendum.allowed = self.articleAddendumAllowed.data - l.article.addendum.max = self.articleAddendumMax.data - return True +# def update_lexicon(self, lexicon): +# with lexicon.edit() as l: +# l.title = self.title.data +# l.editor = UserModel.by(name=self.editor.data).uid +# l.prompt = self.prompt.data +# l.turn.current = self.turnCurrent.data +# l.turn.max = self.turnMax.data +# l.join.public = self.joinPublic.data +# l.join.open = self.joinOpen.data +# l.join.password = self.joinPassword.data +# l.join.max_players = self.joinMaxPlayers.data +# l.join.chars_per_player = self.joinCharsPerPlayer.data +# l.publish.notify.editor_on_ready = self.publishNotifyEditorOnReady.data +# l.publish.notify.player_on_reject = self.publishNotifyPlayerOnReject.data +# l.publish.notify.player_on_accept = self.publishNotifyPlayerOnAccept.data +# l.publish.deadlines = self.publishDeadlines.data +# l.publish.asap = self.publishAsap.data +# l.publish.quorum = self.publishQuorum.data +# l.publish.block_on_ready = self.publishBlockOnReady.data +# l.article.index.list = self.articleIndexList.data +# l.article.index.capacity = self.articleIndexCapacity.data +# l.article.citation.allow_self = self.articleCitationAllowSelf.data +# l.article.citation.min_extant = self.articleCitationMinExtant.data +# l.article.citation.max_extant = self.articleCitationMaxExtant.data +# l.article.citation.min_phantom = self.articleCitationMinPhantom.data +# l.article.citation.max_phantom = self.articleCitationMaxPhantom.data +# l.article.citation.min_total = self.articleCitationMinTotal.data +# l.article.citation.max_total = self.articleCitationMaxTotal.data +# l.article.citation.min_chars = self.articleCitationMinChars.data +# l.article.citation.max_chars = self.articleCitationMaxChars.data +# l.article.word_limit.soft = self.articleWordLimitSoft.data +# l.article.word_limit.hard = self.articleWordLimitHard.data +# l.article.addendum.allowed = self.articleAddendumAllowed.data +# l.article.addendum.max = self.articleAddendumMax.data +# return True -class LexiconJoinForm(FlaskForm): - """/lexicon//join/""" - password = StringField('Password') - submit = SubmitField("Submit") +# class LexiconJoinForm(FlaskForm): +# """/lexicon//join/""" +# password = StringField('Password') +# submit = SubmitField("Submit") -class LexiconCharacterForm(FlaskForm): - """/lexicon//session/character/""" - characterName = StringField("Character name", validators=[DataRequired()]) - defaultSignature = TextAreaField("Default signature") - submit = SubmitField("Submit") +# class LexiconCharacterForm(FlaskForm): +# """/lexicon//session/character/""" +# characterName = StringField("Character name", validators=[DataRequired()]) +# defaultSignature = TextAreaField("Default signature") +# submit = SubmitField("Submit") - def for_new(self): - self.characterName.data = "" - self.defaultSignature.data = "~" +# def for_new(self): +# self.characterName.data = "" +# self.defaultSignature.data = "~" - def for_character(self, lexicon, cid): - char = lexicon.character.get(cid) - self.characterName.data = char.name - self.defaultSignature.data = char.signature +# def for_character(self, lexicon, cid): +# char = lexicon.character.get(cid) +# self.characterName.data = char.name +# self.defaultSignature.data = char.signature - def add_character(self, lexicon, user): - add_character(lexicon, user, { - 'name': self.characterName.data, - 'signature': self.defaultSignature.data, - }) +# def add_character(self, lexicon, user): +# add_character(lexicon, user, { +# 'name': self.characterName.data, +# 'signature': self.defaultSignature.data, +# }) - def update_character(self, lexicon, cid): - with lexicon.edit() as l: - char = l.character.get(cid) - char.name = self.characterName.data - char.signature = self.defaultSignature.data +# def update_character(self, lexicon, cid): +# with lexicon.edit() as l: +# char = l.character.get(cid) +# char.name = self.characterName.data +# char.signature = self.defaultSignature.data -class LexiconReviewForm(FlaskForm): - """/lexicon//session/review/""" - approved = RadioField("Buttons", choices=(("Y", "Approved"), ("N", "Rejected"))) - submit = SubmitField("Submit") \ No newline at end of file +# class LexiconReviewForm(FlaskForm): +# """/lexicon//session/review/""" +# approved = RadioField("Buttons", choices=(("Y", "Approved"), ("N", "Rejected"))) +# submit = SubmitField("Submit") + + +# class LexiconPublishTurnForm(FlaskForm): +# """/lexicon//session/""" +# submit = SubmitField("Publish turn") diff --git a/amanuensis/server/helpers.py b/amanuensis/server/helpers.py index 3270af4..eb8b03e 100644 --- a/amanuensis/server/helpers.py +++ b/amanuensis/server/helpers.py @@ -7,18 +7,18 @@ from flask import g, flash, redirect, url_for, current_app from flask_login import current_user # Module imports -from amanuensis.lexicon import LexiconModel from amanuensis.parser import filesafe_title -from amanuensis.user import UserModel -from amanuensis.models import ModelFactory +from amanuensis.models import ModelFactory, UserModel + def register_custom_filters(app): """Adds custom filters to the Flask app""" @app.template_filter("user_attr") def get_user_attr(uid, attr): - user = UserModel.by(uid=uid) - val = getattr(user, attr) + factory: ModelFactory = current_app.config['model_factory'] + user: UserModel = factory.user(uid) + val = getattr(user.cfg, attr) return val @app.template_filter("asdate") @@ -30,23 +30,26 @@ def register_custom_filters(app): @app.template_filter("articlelink") def article_link(title): - return url_for('lexicon.article', name=g.lexicon.name, title=filesafe_title(title)) + return url_for( + 'lexicon.article', + name=g.lexicon.name, + title=filesafe_title(title)) -def lexicon_param(route): - """Wrapper for loading a route's lexicon""" - @wraps(route) - def with_lexicon(**kwargs): - name = kwargs.get('name') - g.lexicon = LexiconModel.by(name=name) - if g.lexicon is None: - flash("Couldn't find a lexicon with the name '{}'".format(name)) - return redirect(url_for("home.home")) - # TODO transition to new model - model_factory: ModelFactory = current_app.config['model_factory'] - g.lexicon_ = model_factory.lexicon(name) - return route(**kwargs) - return with_lexicon +# def lexicon_param(route): +# """Wrapper for loading a route's lexicon""" +# @wraps(route) +# def with_lexicon(**kwargs): +# name = kwargs.get('name') +# g.lexicon = LexiconModel.by(name=name) +# if g.lexicon is None: +# flash("Couldn't find a lexicon with the name '{}'".format(name)) +# return redirect(url_for("home.home")) +# # TODO transition to new model +# model_factory: ModelFactory = current_app.config['model_factory'] +# g.lexicon_ = model_factory.lexicon(name) +# return route(**kwargs) +# return with_lexicon def admin_required(route): @@ -55,7 +58,7 @@ def admin_required(route): """ @wraps(route) def admin_route(*args, **kwargs): - if not current_user.is_admin: + if not current_user.cfg.is_admin: flash("You must be an admin to view this page") return redirect(url_for('home.home')) return route(*args, **kwargs) @@ -68,10 +71,10 @@ def player_required(route): """ @wraps(route) def player_route(*args, **kwargs): - if not current_user.in_lexicon(g.lexicon): + if current_user.uid not in g.lexicon.cfg.join.joined: flash("You must be a player to view this page") - return (redirect(url_for('lexicon.contents', name=g.lexicon.name)) - if g.lexicon.join.public + return (redirect(url_for('lexicon.contents', name=g.lexicon.cfg.name)) + if g.lexicon.cfg.join.public else redirect(url_for('home.home'))) return route(*args, **kwargs) return player_route @@ -84,8 +87,8 @@ def player_required_if_not_public(route): """ @wraps(route) def player_route(*args, **kwargs): - if ((not g.lexicon.join.public) - and not current_user.in_lexicon(g.lexicon)): + if ((not g.lexicon.cfg.join.public) + and current_user.uid not in g.lexicon.cfg.join.joined): flash("You must be a player to view this page") return redirect(url_for('home.home')) return route(*args, **kwargs) @@ -99,8 +102,8 @@ def editor_required(route): """ @wraps(route) def editor_route(*args, **kwargs): - if current_user.id != g.lexicon.editor: + if current_user.uid != g.lexicon.cfg.editor: flash("You must be the editor to view this page") return redirect(url_for('lexicon.contents', name=g.lexicon.name)) return route(*args, **kwargs) - return editor_route \ No newline at end of file + return editor_route diff --git a/amanuensis/server/home.py b/amanuensis/server/home.py index b519681..a9aa5a0 100644 --- a/amanuensis/server/home.py +++ b/amanuensis/server/home.py @@ -1,64 +1,58 @@ -from flask import Blueprint, render_template, redirect, url_for +from flask import Blueprint, render_template, redirect, url_for, current_app from flask_login import login_required, current_user -from amanuensis.config import json_ro, json_rw -from amanuensis.lexicon import LexiconModel -from amanuensis.lexicon.manage import create_lexicon, get_all_lexicons +from amanuensis.config import RootConfigDirectoryContext +from amanuensis.lexicon import create_lexicon, load_all_lexicons +from amanuensis.models import UserModel from amanuensis.server.forms import LexiconCreateForm from amanuensis.server.helpers import admin_required -from amanuensis.user import UserModel +from amanuensis.user import load_all_users -def get_bp(): - """Create a blueprint for pages outside of a specific lexicon""" - bp = Blueprint('home', __name__, url_prefix='/home') +bp_home = Blueprint('home', __name__, url_prefix='/home') - @bp.route('/', methods=['GET']) - def home(): - user_lexicons = [] - public_lexicons = [] - for lexicon in get_all_lexicons(): - if current_user.in_lexicon(lexicon): - user_lexicons.append(lexicon) - elif lexicon.join.public: - public_lexicons.append(lexicon) - return render_template( - 'home/home.html', - user_lexicons=user_lexicons, - public_lexicons=public_lexicons) - @bp.route('/admin/', methods=['GET']) - @login_required - @admin_required - def admin(): - users = [] - with json_ro('user', 'index.json') as index: - for name, uid in index.items(): - users.append(UserModel.by(uid=uid)) +@bp_home.route('/', methods=['GET']) +def home(): + root: RootConfigDirectoryContext = current_app.config['root'] + user: UserModel = current_user + user_lexicons = [] + public_lexicons = [] + for lexicon in load_all_lexicons(root): + if user.uid in lexicon.cfg.join.joined: + user_lexicons.append(lexicon) + elif lexicon.cfg.join.public: + public_lexicons.append(lexicon) + return render_template( + 'home/home.html', + user_lexicons=user_lexicons, + public_lexicons=public_lexicons) - lexicons = [] - with json_ro('lexicon', 'index.json') as index: - for name, lid in index.items(): - lexicons.append(LexiconModel.by(lid=lid)) - return render_template('home/admin.html', users=users, lexicons=lexicons) +@bp_home.route('/admin/', methods=['GET']) +@login_required +@admin_required +def admin(): + root: RootConfigDirectoryContext = current_app.config['root'] + users = list(load_all_users(root)) + lexicons = list(load_all_lexicons(root)) + return render_template('home/admin.html', users=users, lexicons=lexicons) - @bp.route("/admin/create/", methods=['GET', 'POST']) - @login_required - @admin_required - def admin_create(): - form = LexiconCreateForm() - if form.validate_on_submit(): - lexicon_name = form.lexiconName.data - editor_name = form.editorName.data - prompt = form.promptText.data - editor = UserModel.by(name=editor_name) - lexicon = create_lexicon(lexicon_name, editor) - with json_rw(lexicon.config_path) as cfg: - cfg.prompt = prompt - return redirect(url_for('lexicon.session', name=lexicon_name)) +@bp_home.route("/admin/create/", methods=['GET', 'POST']) +@login_required +@admin_required +def admin_create(): + form = LexiconCreateForm() - return render_template('home/create.html', form=form) + if form.validate_on_submit(): + lexicon_name = form.lexiconName.data + editor_name = form.editorName.data + prompt = form.promptText.data + editor = UserModel.by(name=editor_name) + lexicon = create_lexicon(lexicon_name, editor) + with lexicon.ctx.edit_config() as cfg: + cfg.prompt = prompt + return redirect(url_for('lexicon.session', name=lexicon_name)) - return bp + return render_template('home/create.html', form=form) diff --git a/amanuensis/templates/home/home.html b/amanuensis/templates/home/home.html index 555b0fb..d3657dc 100644 --- a/amanuensis/templates/home/home.html +++ b/amanuensis/templates/home/home.html @@ -34,7 +34,7 @@ {% endblock %} {% set template_content_blocks = [self.main()] %} -{% if current_user.is_admin %} +{% if current_user.cfg.is_admin %} {% block admin_dash %} Admin dashboard {% endblock %} diff --git a/amanuensis/templates/macros.html b/amanuensis/templates/macros.html index 21255e3..7fd248a 100644 --- a/amanuensis/templates/macros.html +++ b/amanuensis/templates/macros.html @@ -1,31 +1,31 @@ {% macro dashboard_lexicon_item(lexicon) %} -
+

- - Lexicon {{ lexicon.name }} + {# #} + Lexicon {{ lexicon.cfg.name }}{# #} - [{{ lexicon.status().capitalize() }}] + [{{ lexicon.status.capitalize() }}]

-

{{ lexicon.prompt }}

+

{{ lexicon.cfg.prompt }}

{% if current_user.is_authenticated %}

{% - if current_user.in_lexicon(lexicon) - or current_user.is_admin + if current_user.uid in lexicon.cfg.join.joined + or current_user.cfg.is_admin %} - Editor: {{ lexicon.editor|user_attr('username') }} / + Editor: {{ lexicon.cfg.editor|user_attr('username') }} / Players: - {% for uid in lexicon.join.joined %} + {% for uid in lexicon.cfg.join.joined %} {{ uid|user_attr('username') }}{% if not loop.last %}, {% endif %} {% endfor %} - ({{ lexicon.join.joined|count }}/{{ lexicon.join.max_players }}) + ({{ lexicon.cfg.join.joined|count }}/{{ lexicon.cfg.join.max_players }}) {% else %} - Players: {{ lexicon.join.joined|count }}/{{ lexicon.join.max_players }} - {% if lexicon.join.public and lexicon.join.open %} - / + Players: {{ lexicon.cfg.join.joined|count }}/{{ lexicon.cfg.join.max_players }} + {% if lexicon.cfg.join.public and lexicon.cfg.join.open %} + {# / #} Join game - + {# #} {% endif %} {% endif %}

@@ -36,10 +36,10 @@ {% macro dashboard_user_item(user) %}

- {{ user.username }} - {% if user.username != user.displayname %} / {{ user.displayname }}{% endif %} + {{ user.cfg.username }} + {% if user.cfg.username != user.cfg.displayname %} / {{ user.cfg.displayname }}{% endif %} ({{user.uid}})

-

Last activity: {{ user.last_activity|asdate }} — Last login: {{ user.last_login|asdate }}

+

Last activity: {{ user.cfg.last_activity|asdate }} — Last login: {{ user.cfg.last_login|asdate }}

{% endmacro %} \ No newline at end of file diff --git a/amanuensis/templates/page.html b/amanuensis/templates/page.html index dd96b7f..2a2bf03 100644 --- a/amanuensis/templates/page.html +++ b/amanuensis/templates/page.html @@ -11,7 +11,7 @@