diff --git a/amanuensis/lexicon/__init__.py b/amanuensis/lexicon/__init__.py
index 5bdc579..acf44e5 100644
--- a/amanuensis/lexicon/__init__.py
+++ b/amanuensis/lexicon/__init__.py
@@ -52,6 +52,9 @@ class LexiconModel():
def __repr__(self):
return ''.format(self)
+ def edit(self):
+ return json_rw(self.config_path)
+
def add_log(self, message):
now = int(time.time())
with json_rw(self.config_path) as j:
diff --git a/amanuensis/resources/page.css b/amanuensis/resources/page.css
index 7d0b6ec..38e18a3 100644
--- a/amanuensis/resources/page.css
+++ b/amanuensis/resources/page.css
@@ -111,6 +111,9 @@ textarea.fullwidth {
width: 100%;
box-sizing: border-box;
}
+input.smallnumber {
+ width: 4em;
+}
div.dashboard-lexicon-item {
margin: 0 10px;
padding: 0 10px;
diff --git a/amanuensis/server/forms.py b/amanuensis/server/forms.py
index 7c2e0f4..a6684f5 100644
--- a/amanuensis/server/forms.py
+++ b/amanuensis/server/forms.py
@@ -1,9 +1,12 @@
from flask_wtf import FlaskForm
from wtforms import (
- StringField, PasswordField, BooleanField, SubmitField, TextAreaField)
-from wtforms.validators import DataRequired, ValidationError
+ StringField, PasswordField, BooleanField, SubmitField, TextAreaField,
+ IntegerField, SelectField)
+from wtforms.validators import DataRequired, ValidationError, Optional
+from wtforms.widgets.html5 import NumberInput
from amanuensis.config import json_ro
+from amanuensis.user import UserModel
# Custom validators
@@ -52,9 +55,146 @@ class LexiconCreateForm(FlaskForm):
class LexiconConfigForm(FlaskForm):
"""/lexicon//session/settings/"""
- configText = TextAreaField("Config file")
+ # 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()])
+ # 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")
+ # 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 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.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.publish.notify.editor_on_ready = self.publishNotifyEditorOnReady.data
+ l.publish.notify.player_on_reject = self.publishNotifyPlayerOnReject.data
+ l.publish.notify.player_on_accept = self.publishNotifyPlayerOnAccept.data
+ l.publish.deadlines = self.publishDeadlines.data
+ l.publish.asap = self.publishAsap.data
+ l.publish.quorum = self.publishQuorum.data
+ l.publish.block_on_ready = self.publishBlockOnReady.data
+ l.article.index.list = self.articleIndexList.data
+ l.article.index.capacity = self.articleIndexCapacity.data
+ l.article.citation.allow_self = self.articleCitationAllowSelf.data
+ l.article.citation.min_extant = self.articleCitationMinExtant.data
+ l.article.citation.max_extant = self.articleCitationMaxExtant.data
+ l.article.citation.min_phantom = self.articleCitationMinPhantom.data
+ l.article.citation.max_phantom = self.articleCitationMaxPhantom.data
+ l.article.citation.min_total = self.articleCitationMinTotal.data
+ l.article.citation.max_total = self.articleCitationMaxTotal.data
+ l.article.citation.min_chars = self.articleCitationMinChars.data
+ l.article.citation.max_chars = self.articleCitationMaxChars.data
+ l.article.word_limit.soft = self.articleWordLimitSoft.data
+ l.article.word_limit.hard = self.articleWordLimitHard.data
+ l.article.addendum.allowed = self.articleAddendumAllowed.data
+ l.article.addendum.max = self.articleAddendumMax.data
+ return True
+
class LexiconJoinForm(FlaskForm):
"""/lexicon//join/"""
diff --git a/amanuensis/server/lexicon.py b/amanuensis/server/lexicon.py
index 87f17bf..f8b6cad 100644
--- a/amanuensis/server/lexicon.py
+++ b/amanuensis/server/lexicon.py
@@ -65,36 +65,23 @@ def get_bp():
@lexicon_param
@editor_required
def settings(name):
- # Restrict to editor
- if not current_user.id == g.lexicon.editor:
- flash("Access is forbidden")
- return redirect(url_for('lexicon.session', name=name))
-
form = LexiconConfigForm()
+ form.set_options(g.lexicon)
# Load the config for the lexicon on load
if not form.is_submitted():
- with json_ro(g.lexicon.config_path) as cfg:
- form.configText.data = json.dumps(cfg, indent=2)
+ form.populate_from_lexicon(g.lexicon)
return render_template("lexicon/settings.html", form=form)
if form.validate():
- # Check input is valid json
- try:
- cfg = json.loads(form.configText.data,
- object_pairs_hook=ReadOnlyOrderedDict)
- except json.decoder.JsonDecodeError:
- flash("Invalid JSON")
+ if not form.update_lexicon(g.lexicon):
+ flash("Error updating settings")
return render_template("lexicon/settings.html", form=form)
- # Check input has all the required fields
- # TODO
- # Write the new config
form.submit.submitted = False
- with open_ex(g.lexicon.config_path, mode='w') as f:
- json.dump(cfg, f, indent='\t')
- flash("Config updated")
- return redirect(url_for('lexicon.settings', name=name))
+ flash("Settings updated")
+ return redirect(url_for('lexicon.session', name=name))
+ flash("Validation error")
return render_template("lexicon/settings.html", form=form)
@bp.route('/statistics/', methods=['GET'])
diff --git a/amanuensis/templates/lexicon/session.html b/amanuensis/templates/lexicon/session.html
index 430a906..2fb7494 100644
--- a/amanuensis/templates/lexicon/session.html
+++ b/amanuensis/templates/lexicon/session.html
@@ -15,6 +15,9 @@
{% endif %}
{% block main %}
+{% for message in get_flashed_messages() %}
+{{ message }}
+{% endfor %}
Placeholder text for session page
{% endblock %}
{% set template_content_blocks = template_content_blocks + [self.main()] %}
\ No newline at end of file
diff --git a/amanuensis/templates/lexicon/settings.html b/amanuensis/templates/lexicon/settings.html
index 08002a7..a606957 100644
--- a/amanuensis/templates/lexicon/settings.html
+++ b/amanuensis/templates/lexicon/settings.html
@@ -1,14 +1,109 @@
{% extends "lexicon/lexicon.html" %}
{% block title %}Edit | {{ lexicon_title }}{% endblock %}
-{% block main %}
+{% block info %}
+
+ Id: {{ g.lexicon.id }}
+ Name: {{ g.lexicon.name }}
+ Created: {{ g.lexicon.time.created|asdate }}
+ Completed: {{ g.lexicon.time.completed|asdate }}
+ Players:
+ {% for uid in g.lexicon.join.joined[:-1] %}
+ {{ uid|user_attr('username') }},
+ {% endfor %}
+ {{ g.lexicon.join.joined[-1]|user_attr('username') }}
+ Log:
+
+ {% for log_entry in g.lexicon.log %}
+ [{{ log_entry[0]|asdate }}] {{ log_entry[1] }}
+ {% endfor %}
+
+
+{% endblock %}
+
+{% macro number_setting(field) %}
+{{ field(autocomplete="off", class_="smallnumber") }}
+{{ field.label }}
+{% for error in field.errors %}
+{{ error }}
+{% endfor %}
+
+{% endmacro %}
+{% macro flag_setting(field) %}
+{{ field() }}
+{{ field.label }}
+{% endmacro %}
+
+{% block settings %}
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}
{% endblock %}
-{% set template_content_blocks = [self.main()] %}
\ No newline at end of file
+{% set template_content_blocks = [self.info(), self.settings()] %}
\ No newline at end of file