Kick off the server refactor with home and auth
This commit is contained in:
parent
07e62b9665
commit
3077b02508
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
return editor_route
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 }} — Last login: {{ user.last_login|asdate }}</p>
|
||||
<p>Last activity: {{ user.cfg.last_activity|asdate }} — Last login: {{ user.cfg.last_login|asdate }}</p>
|
||||
</div>
|
||||
{% endmacro %}
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue