Compare commits

..

4 Commits

5 changed files with 210 additions and 209 deletions

View File

@ -16,6 +16,7 @@ class BackendArgumentTypeError(ArgumentError):
A call to a backend function was made with a value of an invalid type for the parameter. A call to a backend function was made with a value of an invalid type for the parameter.
Specify the invalid parameter and value as a kwarg. Specify the invalid parameter and value as a kwarg.
""" """
def __init__(self, obj_type, **kwarg): def __init__(self, obj_type, **kwarg):
if not kwarg: if not kwarg:
raise ValueError("Missing kwarg") raise ValueError("Missing kwarg")

View File

@ -11,6 +11,7 @@ from amanuensis.parser import filesafe_title
import amanuensis.server.auth as auth import amanuensis.server.auth as auth
import amanuensis.server.home as home import amanuensis.server.home as home
import amanuensis.server.lexicon as lexicon import amanuensis.server.lexicon as lexicon
import amanuensis.server.session as session
def date_format(dt: datetime, formatstr="%Y-%m-%d %H:%M:%S%z") -> str: def date_format(dt: datetime, formatstr="%Y-%m-%d %H:%M:%S%z") -> str:
@ -82,6 +83,7 @@ def get_app(
app.register_blueprint(auth.bp) app.register_blueprint(auth.bp)
app.register_blueprint(home.bp) app.register_blueprint(home.bp)
app.register_blueprint(lexicon.bp) app.register_blueprint(lexicon.bp)
app.register_blueprint(session.bp)
# Add a root redirect # Add a root redirect
def root(): def root():

View File

@ -26,7 +26,10 @@
{% else %}href="{{ url_for('lexicon.stats', name=g.lexicon.name) }}" {% else %}href="{{ url_for('lexicon.stats', name=g.lexicon.name) }}"
{% endif %}>Statistics</a>{% endblock %} {% endif %}>Statistics</a>{% endblock %}
{% if current_user.is_authenticated and memq.try_from_ids(g.db, current_user.id, g.lexicon.id) %} {% if current_user.is_authenticated and (
current_user.is_site_admin
or memq.try_from_ids(g.db, current_user.id, g.lexicon.id)
) %}
{# self.sb_logo(), #} {# self.sb_logo(), #}
{% set template_sidebar_rows = [ {% set template_sidebar_rows = [
self.sb_home(), self.sb_home(),

View File

@ -1,245 +1,240 @@
from flask import ( from flask import (
Blueprint, Blueprint,
render_template, )
url_for,
redirect,
g,
flash,
request,
Markup)
from flask_login import current_user
from amanuensis.lexicon import ( # from flask_login import current_user
attempt_publish,
get_player_characters,
create_character_in_lexicon,
get_draft)
from amanuensis.models import LexiconModel
from amanuensis.parser import parse_raw_markdown
from amanuensis.server.helpers import (
lexicon_param,
player_required,
editor_required)
from .forms import ( # from amanuensis.lexicon import (
LexiconCharacterForm, # attempt_publish,
LexiconReviewForm, # get_player_characters,
LexiconPublishTurnForm, # create_character_in_lexicon,
LexiconConfigForm) # get_draft)
# from amanuensis.models import LexiconModel
# from amanuensis.parser import parse_raw_markdown
# from amanuensis.server.helpers import (
# lexicon_param,
# player_required,
# editor_required)
from .editor import load_editor, new_draft, update_draft, PreviewHtmlRenderer # from .forms import (
# LexiconCharacterForm,
# LexiconReviewForm,
# LexiconPublishTurnForm,
# LexiconConfigForm)
# from .editor import load_editor, new_draft, update_draft, PreviewHtmlRenderer
bp_session = Blueprint('session', __name__, bp = Blueprint(
url_prefix='/lexicon/<name>/session', "session", __name__, url_prefix="/lexicon/<name>/session", template_folder="."
template_folder='.') )
@bp_session.route('/', methods=['GET', 'POST']) # @bp_session.route('/', methods=['GET', 'POST'])
@lexicon_param # @lexicon_param
@player_required # @player_required
def session(name): # def session(name):
drafts = [] # drafts = []
approved = [] # approved = []
draft_ctx = g.lexicon.ctx.draft # draft_ctx = g.lexicon.ctx.draft
draft_filenames = draft_ctx.ls() # draft_filenames = draft_ctx.ls()
for draft_filename in draft_filenames: # for draft_filename in draft_filenames:
with draft_ctx.read(draft_filename) as draft: # with draft_ctx.read(draft_filename) as draft:
if draft.status.ready and not draft.status.approved: # if draft.status.ready and not draft.status.approved:
drafts.append(draft) # drafts.append(draft)
if draft.status.approved: # if draft.status.approved:
approved.append(draft) # approved.append(draft)
characters = [] # characters = []
for char in g.lexicon.cfg.character.values(): # for char in g.lexicon.cfg.character.values():
if char.player == current_user.uid: # if char.player == current_user.uid:
characters.append(char) # characters.append(char)
form = LexiconPublishTurnForm() # form = LexiconPublishTurnForm()
if form.validate_on_submit(): # if form.validate_on_submit():
if attempt_publish(g.lexicon): # if attempt_publish(g.lexicon):
return redirect(url_for('lexicon.contents', name=name)) # return redirect(url_for('lexicon.contents', name=name))
else: # else:
flash('Publish failed') # flash('Publish failed')
return redirect(url_for('session.session', name=name)) # return redirect(url_for('session.session', name=name))
return render_template( # return render_template(
'session.root.jinja', # 'session.root.jinja',
ready_articles=drafts, # ready_articles=drafts,
approved_articles=approved, # approved_articles=approved,
characters=characters, # characters=characters,
publish_form=form) # publish_form=form)
def edit_character(name, form, character): # def edit_character(name, form, character):
if not form.is_submitted(): # if not form.is_submitted():
# GET, populate with values # # GET, populate with values
return render_template( # return render_template(
'session.character.jinja', form=form.for_character(character)) # 'session.character.jinja', form=form.for_character(character))
if not form.validate(): # if not form.validate():
# POST with invalid data, return unchanged # # POST with invalid data, return unchanged
return render_template('session.character.jinja', form=form) # return render_template('session.character.jinja', form=form)
# POST with valid data, update character # # POST with valid data, update character
with g.lexicon.ctx.edit_config() as cfg: # with g.lexicon.ctx.edit_config() as cfg:
char = cfg.character[character.cid] # char = cfg.character[character.cid]
char.name = form.characterName.data # char.name = form.characterName.data
char.signature = form.defaultSignature.data # char.signature = form.defaultSignature.data
flash('Character updated') # flash('Character updated')
return redirect(url_for('session.session', name=name)) # return redirect(url_for('session.session', name=name))
def create_character(name: str, form: LexiconCharacterForm): # def create_character(name: str, form: LexiconCharacterForm):
# Characters can't be created if the game has already started # # Characters can't be created if the game has already started
if g.lexicon.status != LexiconModel.PREGAME: # if g.lexicon.status != LexiconModel.PREGAME:
flash("Characters can't be added after the game has started") # flash("Characters can't be added after the game has started")
return redirect(url_for('session.session', name=name)) # return redirect(url_for('session.session', name=name))
# Characters can't be created beyond the per-player limit # # Characters can't be created beyond the per-player limit
player_characters = get_player_characters(g.lexicon, current_user.uid) # player_characters = get_player_characters(g.lexicon, current_user.uid)
if len(list(player_characters)) >= g.lexicon.cfg.join.chars_per_player: # if len(list(player_characters)) >= g.lexicon.cfg.join.chars_per_player:
flash("Can't create more characters") # flash("Can't create more characters")
return redirect(url_for('session.session', name=name)) # return redirect(url_for('session.session', name=name))
if not form.is_submitted(): # if not form.is_submitted():
# GET, populate with default values # # GET, populate with default values
return render_template( # return render_template(
'session.character.jinja', form=form.for_new()) # 'session.character.jinja', form=form.for_new())
if not form.validate(): # if not form.validate():
# POST with invalid data, return unchanged # # POST with invalid data, return unchanged
return render_template('session.character.jinja', form=form) # return render_template('session.character.jinja', form=form)
# POST with valid data, create character # # POST with valid data, create character
char_name = form.characterName.data # char_name = form.characterName.data
cid = create_character_in_lexicon(current_user, g.lexicon, char_name) # cid = create_character_in_lexicon(current_user, g.lexicon, char_name)
with g.lexicon.ctx.edit_config() as cfg: # with g.lexicon.ctx.edit_config() as cfg:
cfg.character[cid].signature = form.defaultSignature.data # cfg.character[cid].signature = form.defaultSignature.data
flash('Character created') # flash('Character created')
return redirect(url_for('session.session', name=name)) # return redirect(url_for('session.session', name=name))
@bp_session.route('/character/', methods=['GET', 'POST']) # @bp_session.route('/character/', methods=['GET', 'POST'])
@lexicon_param # @lexicon_param
@player_required # @player_required
def character(name): # def character(name):
form = LexiconCharacterForm() # form = LexiconCharacterForm()
cid = request.args.get('cid') # cid = request.args.get('cid')
if not cid: # if not cid:
# No character specified, creating a new character # # No character specified, creating a new character
return create_character(name, form) # return create_character(name, form)
character = g.lexicon.cfg.character.get(cid) # character = g.lexicon.cfg.character.get(cid)
if not character: # if not character:
# Bad character id, abort # # Bad character id, abort
flash('Character not found') # flash('Character not found')
return redirect(url_for('session.session', name=name)) # return redirect(url_for('session.session', name=name))
if current_user.uid not in (character.player, g.lexicon.cfg.editor): # if current_user.uid not in (character.player, g.lexicon.cfg.editor):
# Only its owner and the editor can edit a character # # Only its owner and the editor can edit a character
flash('Access denied') # flash('Access denied')
return redirect(url_for('session.session', name=name)) # return redirect(url_for('session.session', name=name))
# Edit allowed # # Edit allowed
return edit_character(name, form, character) # return edit_character(name, form, character)
@bp_session.route('/settings/', methods=['GET', 'POST']) # @bp_session.route('/settings/', methods=['GET', 'POST'])
@lexicon_param # @lexicon_param
@editor_required # @editor_required
def settings(name): # def settings(name):
form: LexiconConfigForm = LexiconConfigForm(g.lexicon) # form: LexiconConfigForm = LexiconConfigForm(g.lexicon)
if not form.is_submitted(): # if not form.is_submitted():
# GET # # GET
form.load(g.lexicon) # form.load(g.lexicon)
return render_template('session.settings.jinja', form=form) # return render_template('session.settings.jinja', form=form)
if not form.validate(): # if not form.validate():
# POST with invalid data # # POST with invalid data
flash('Validation error') # flash('Validation error')
return render_template('session.settings.jinja', form=form) # return render_template('session.settings.jinja', form=form)
# POST with valid data # # POST with valid data
form.save(g.lexicon) # form.save(g.lexicon)
flash('Settings updated') # flash('Settings updated')
return redirect(url_for('session.session', name=name)) # return redirect(url_for('session.session', name=name))
@bp_session.route('/review/', methods=['GET', 'POST']) # @bp_session.route('/review/', methods=['GET', 'POST'])
@lexicon_param # @lexicon_param
@editor_required # @editor_required
def review(name): # def review(name):
# Ensure the article exists # # Ensure the article exists
draft = get_draft(g.lexicon, request.args.get('aid')) # draft = get_draft(g.lexicon, request.args.get('aid'))
if not draft: # if not draft:
flash("Unknown article id") # flash("Unknown article id")
return redirect(url_for('session.session', name=name)) # return redirect(url_for('session.session', name=name))
draft_filename = f'{draft.character}.{draft.aid}' # draft_filename = f'{draft.character}.{draft.aid}'
with g.lexicon.ctx.draft.edit(draft_filename) as draft: # with g.lexicon.ctx.draft.edit(draft_filename) as draft:
# If the article was unreadied in the meantime, abort # # If the article was unreadied in the meantime, abort
if not draft.status.ready: # if not draft.status.ready:
flash("Article was rescinded") # flash("Article was rescinded")
return redirect(url_for('session.session', name=name)) # return redirect(url_for('session.session', name=name))
parsed_draft = parse_raw_markdown(draft.contents) # parsed_draft = parse_raw_markdown(draft.contents)
preview = parsed_draft.render(PreviewHtmlRenderer(g.lexicon)) # preview = parsed_draft.render(PreviewHtmlRenderer(g.lexicon))
rendered_html = preview.contents # rendered_html = preview.contents
citations = preview.citations # citations = preview.citations
# If the article was already reviewed, just preview it # # If the article was already reviewed, just preview it
if draft.status.approved: # if draft.status.approved:
return render_template( # return render_template(
"session.review.jinja", # "session.review.jinja",
article_html=Markup(rendered_html), # article_html=Markup(rendered_html),
citations=citations) # citations=citations)
# Otherwise, prepare the review form # # Otherwise, prepare the review form
form = LexiconReviewForm() # form = LexiconReviewForm()
if not form.validate_on_submit(): # if not form.validate_on_submit():
# GET or POST with invalid data # # GET or POST with invalid data
return render_template( # return render_template(
"session.review.jinja", # "session.review.jinja",
form=form, # form=form,
article_html=Markup(rendered_html), # article_html=Markup(rendered_html),
citations=citations) # citations=citations)
# POST with valid data # # POST with valid data
if form.approved.data == LexiconReviewForm.REJECTED: # if form.approved.data == LexiconReviewForm.REJECTED:
draft.status.ready = False # draft.status.ready = False
draft.status.approved = False # draft.status.approved = False
g.lexicon.log(f"Article '{draft.title}' rejected ({draft.aid})") # g.lexicon.log(f"Article '{draft.title}' rejected ({draft.aid})")
return redirect(url_for('session.session', name=name)) # return redirect(url_for('session.session', name=name))
else: # else:
draft.status.ready = True # draft.status.ready = True
draft.status.approved = True # draft.status.approved = True
g.lexicon.log(f"Article '{draft.title}' approved ({draft.aid})") # g.lexicon.log(f"Article '{draft.title}' approved ({draft.aid})")
# Draft was approved, check for asap publishing # # Draft was approved, check for asap publishing
if g.lexicon.cfg.publish.asap: # if g.lexicon.cfg.publish.asap:
if attempt_publish(g.lexicon): # if attempt_publish(g.lexicon):
redirect(url_for('lexicon.contents', name=name)) # redirect(url_for('lexicon.contents', name=name))
return redirect(url_for('session.session', name=name)) # return redirect(url_for('session.session', name=name))
@bp_session.route('/editor/', methods=['GET']) # @bp_session.route('/editor/', methods=['GET'])
@lexicon_param # @lexicon_param
@player_required # @player_required
def editor(name): # def editor(name):
lexicon: LexiconModel = g.lexicon # lexicon: LexiconModel = g.lexicon
aid: str = request.args.get('aid') # aid: str = request.args.get('aid')
return load_editor(lexicon, aid) # return load_editor(lexicon, aid)
@bp_session.route('/editor/new', methods=['GET']) # @bp_session.route('/editor/new', methods=['GET'])
@lexicon_param # @lexicon_param
@player_required # @player_required
def editor_new(name): # def editor_new(name):
lexicon: LexiconModel = g.lexicon # lexicon: LexiconModel = g.lexicon
cid: str = request.args.get('cid') # cid: str = request.args.get('cid')
return new_draft(lexicon, cid) # return new_draft(lexicon, cid)
@bp_session.route('/editor/update', methods=['POST']) # @bp_session.route('/editor/update', methods=['POST'])
@lexicon_param # @lexicon_param
@player_required # @player_required
def editor_update(name): # def editor_update(name):
lexicon: LexiconModel = g.lexicon # lexicon: LexiconModel = g.lexicon
article_json = request.json['article'] # article_json = request.json['article']
return update_draft(lexicon, article_json) # return update_draft(lexicon, article_json)

View File

@ -9,5 +9,5 @@ def test_app_testing(app: Flask):
def test_client(app: Flask): def test_client(app: Flask):
"""Test that the test client works.""" """Test that the test client works."""
with app.test_client() as client: with app.test_client() as client:
response = client.get("/") response = client.get("/home/")
assert b"world" in response.data assert b"Amanuensis" in response.data