Kick off the server refactor with home and auth

This commit is contained in:
Tim Van Baak 2020-04-24 11:37:55 -07:00
parent 07e62b9665
commit 3077b02508
10 changed files with 374 additions and 341 deletions

View File

@ -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")
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
app.run(host=args.address, port=args.port, debug=args.debug)
get_app(root).run(host=args.address, port=args.port, debug=args.debug)
return 0

View File

@ -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,

View File

@ -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(
def get_app(root: RootConfigDirectoryContext) -> Flask:
# Flask app init
with root.read_config() as cfg:
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)
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)
# Flask-Login init
login = LoginManager(app)
login.login_view = 'auth.login'
login.anonymous_user = AnonymousUserModel
# Flask-Login init
login_manager = get_login_manager(root)
login_manager.init_app(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)
# Blueprint inits
app.register_blueprint(bp_auth)
app.register_blueprint(bp_home)
# app.register_blueprint(bp_lexicon)
home_bp = get_home_bp()
app.register_blueprint(home_bp)
# import code
# code.interact(local=locals())
lex_bp = get_lex_bp()
app.register_blueprint(lex_bp)
return app

View File

@ -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()

View File

@ -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/<name>/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/<name>/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/<name>/join/"""
password = StringField('Password')
submit = SubmitField("Submit")
# class LexiconJoinForm(FlaskForm):
# """/lexicon/<name>/join/"""
# password = StringField('Password')
# submit = SubmitField("Submit")
class LexiconCharacterForm(FlaskForm):
"""/lexicon/<name>/session/character/"""
characterName = StringField("Character name", validators=[DataRequired()])
defaultSignature = TextAreaField("Default signature")
submit = SubmitField("Submit")
# class LexiconCharacterForm(FlaskForm):
# """/lexicon/<name>/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/<name>/session/review/"""
approved = RadioField("Buttons", choices=(("Y", "Approved"), ("N", "Rejected")))
submit = SubmitField("Submit")
# class LexiconReviewForm(FlaskForm):
# """/lexicon/<name>/session/review/"""
# approved = RadioField("Buttons", choices=(("Y", "Approved"), ("N", "Rejected")))
# submit = SubmitField("Submit")
# class LexiconPublishTurnForm(FlaskForm):
# """/lexicon/<name>/session/"""
# submit = SubmitField("Publish turn")

View File

@ -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,7 +102,7 @@ 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)

View File

@ -1,52 +1,48 @@
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():
@bp_home.route('/', methods=['GET'])
def home():
root: RootConfigDirectoryContext = current_app.config['root']
user: UserModel = current_user
user_lexicons = []
public_lexicons = []
for lexicon in get_all_lexicons():
if current_user.in_lexicon(lexicon):
for lexicon in load_all_lexicons(root):
if user.uid in lexicon.cfg.join.joined:
user_lexicons.append(lexicon)
elif lexicon.join.public:
elif lexicon.cfg.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))
lexicons = []
with json_ro('lexicon', 'index.json') as index:
for name, lid in index.items():
lexicons.append(LexiconModel.by(lid=lid))
@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():
@bp_home.route("/admin/create/", methods=['GET', 'POST'])
@login_required
@admin_required
def admin_create():
form = LexiconCreateForm()
if form.validate_on_submit():
@ -55,10 +51,8 @@ def get_bp():
prompt = form.promptText.data
editor = UserModel.by(name=editor_name)
lexicon = create_lexicon(lexicon_name, editor)
with json_rw(lexicon.config_path) as cfg:
with lexicon.ctx.edit_config() as cfg:
cfg.prompt = prompt
return redirect(url_for('lexicon.session', name=lexicon_name))
return render_template('home/create.html', form=form)
return bp

View File

@ -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 %}
<a href="{{ url_for('home.admin') }}" style="display:block; text-align:center;">Admin dashboard</a>
{% endblock %}

View File

@ -1,31 +1,31 @@
{% macro dashboard_lexicon_item(lexicon) %}
<div class="dashboard-lexicon-item dashboard-lexicon-{{ lexicon.status() }}">
<div class="dashboard-lexicon-item dashboard-lexicon-{{ lexicon.status }}">
<p>
<span class="dashboard-lexicon-item-title">
<a href="{{ url_for('lexicon.contents', name=lexicon.name) }}">
Lexicon {{ lexicon.name }}</a>
{# <a href="{{ url_for('lexicon.contents', name=lexicon.cfg.name) }}"> #}
Lexicon {{ lexicon.cfg.name }}{# </a> #}
</span>
[{{ lexicon.status().capitalize() }}]
[{{ lexicon.status.capitalize() }}]
</p>
<p><i>{{ lexicon.prompt }}</i></p>
<p><i>{{ lexicon.cfg.prompt }}</i></p>
{% if current_user.is_authenticated %}
<p>
{%
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 %}
/ <a href="{{ url_for('lexicon.join', name=lexicon.name) }}">
Players: {{ lexicon.cfg.join.joined|count }}/{{ lexicon.cfg.join.max_players }}
{% if lexicon.cfg.join.public and lexicon.cfg.join.open %}
{# / <a href="{{ url_for('lexicon.join', name=lexicon.cfg.name) }}"> #}
Join game
</a>
{# </a> #}
{% endif %}
{% endif %}
</p>
@ -36,10 +36,10 @@
{% macro dashboard_user_item(user) %}
<div class="dashboard-lexicon-item">
<p>
<b>{{ user.username }}</b>
{% if user.username != user.displayname %} / {{ user.displayname }}{% endif %}
<b>{{ user.cfg.username }}</b>
{% if user.cfg.username != user.cfg.displayname %} / {{ user.cfg.displayname }}{% endif %}
({{user.uid}})
</p>
<p>Last activity: {{ user.last_activity|asdate }} &mdash; Last login: {{ user.last_login|asdate }}</p>
<p>Last activity: {{ user.cfg.last_activity|asdate }} &mdash; Last login: {{ user.cfg.last_login|asdate }}</p>
</div>
{% endmacro %}

View File

@ -11,7 +11,7 @@
<div id="header">
<div id="login-status" {% block login_status_attr %}{% endblock %}>
{% if current_user.is_authenticated %}
<b>{{ current_user.username -}}</b>
<b>{{ current_user.cfg.username -}}</b>
(<a href="{{ url_for('auth.logout') }}">Logout</a>)
{% else %}
<a href="{{ url_for('auth.login') }}">Login</a>