Add character page and create/edit workflows #18

Merged
Jaculabilis merged 9 commits from tvb/character-page into develop 2021-08-29 15:15:45 +00:00
10 changed files with 167 additions and 100 deletions
Showing only changes of commit c6f3ae4779 - Show all commits

View File

@ -2,7 +2,7 @@
Character query interface Character query interface
""" """
from typing import Optional from typing import Optional, Sequence
from sqlalchemy import select, func from sqlalchemy import select, func
@ -68,3 +68,8 @@ def create(
db.session.add(new_character) db.session.add(new_character)
db.session.commit() db.session.commit()
return new_character return new_character
def get_in_lexicon(db: DbContext, lexicon_id: int) -> Sequence[Character]:
"""Get all characters in a lexicon."""
return db(select(Character).where(Character.lexicon_id == lexicon_id)).scalars()

View File

@ -2,6 +2,8 @@
Membership query interface Membership query interface
""" """
from typing import Sequence
from sqlalchemy import select, func from sqlalchemy import select, func
from amanuensis.db import DbContext, Membership from amanuensis.db import DbContext, Membership
@ -66,6 +68,11 @@ def create(
return new_membership return new_membership
def get_players_in_lexicon(db: DbContext, lexicon_id: int) -> Sequence[Membership]:
"""Get all users who are members of a lexicon."""
return db(select(Membership).where(Membership.lexicon_id == lexicon_id)).scalars()
def try_from_ids(db: DbContext, user_id: int, lexicon_id: int) -> Membership: def try_from_ids(db: DbContext, user_id: int, lexicon_id: int) -> Membership:
"""Get a membership by the user and lexicon ids, or None if no such membership was found.""" """Get a membership by the user and lexicon ids, or None if no such membership was found."""
return db( return db(

View File

@ -126,6 +126,19 @@ div.dashboard-lexicon-item {
padding: 0 10px; padding: 0 10px;
border-left: 3px solid black; border-left: 3px solid black;
} }
ul.blockitem-list {
list-style: none;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0.5em;
margin-inline-end: 0.5em;
padding-inline-start: 0;
padding-inline-end: 0;
}
ul.blockitem-list li {
border-inline-start: 3px solid black;
padding-inline-start: 0.5em;
}
div.dashboard-lexicon-unstarted { div.dashboard-lexicon-unstarted {
border-left-color: blue; border-left-color: blue;
} }

View File

@ -4,7 +4,7 @@ import os
from flask import Flask, g, url_for, redirect from flask import Flask, g, url_for, redirect
from amanuensis.backend import lexiq, userq, memq from amanuensis.backend import *
from amanuensis.config import AmanuensisConfig, CommandLineConfig from amanuensis.config import AmanuensisConfig, CommandLineConfig
from amanuensis.db import DbContext from amanuensis.db import DbContext
from amanuensis.parser import filesafe_title from amanuensis.parser import filesafe_title
@ -68,7 +68,7 @@ def get_app(
# Configure jinja options # Configure jinja options
def include_backend(): def include_backend():
return {"db": db, "lexiq": lexiq, "userq": userq, "memq": memq} return {"db": db, "lexiq": lexiq, "userq": userq, "memq": memq, "charq": charq}
app.jinja_options.update(trim_blocks=True, lstrip_blocks=True) app.jinja_options.update(trim_blocks=True, lstrip_blocks=True)
app.template_filter("date")(date_format) app.template_filter("date")(date_format)

View File

@ -9,6 +9,10 @@
{% block sb_logo %}{% endblock %} {% block sb_logo %}{% endblock %}
{% block sb_home %}<a href="{{ url_for('home.home') }}">Home</a> {% block sb_home %}<a href="{{ url_for('home.home') }}">Home</a>
{% endblock %} {% endblock %}
{% block sb_characters %}<a
{% if current_page == "characters" %}class="current-page"
{% else %}href="{{ url_for('lexicon.characters.characters', name=g.lexicon.name) }}"
{% endif %}>Characters</a>{% endblock %}
{% block sb_contents %}<a {% block sb_contents %}<a
{% if current_page == "contents" %}class="current-page" {% if current_page == "contents" %}class="current-page"
{% else %}href="{{ url_for('lexicon.contents', name=g.lexicon.name) }}" {% else %}href="{{ url_for('lexicon.contents', name=g.lexicon.name) }}"
@ -33,6 +37,7 @@
{# self.sb_logo(), #} {# self.sb_logo(), #}
{% set template_sidebar_rows = [ {% set template_sidebar_rows = [
self.sb_home(), self.sb_home(),
self.sb_characters(),
self.sb_contents(), self.sb_contents(),
self.sb_rules(), self.sb_rules(),
self.sb_session(), self.sb_session(),
@ -41,6 +46,7 @@
{# self.sb_logo(), #} {# self.sb_logo(), #}
{% set template_sidebar_rows = [ {% set template_sidebar_rows = [
self.sb_home(), self.sb_home(),
self.sb_characters(),
self.sb_contents(), self.sb_contents(),
self.sb_rules(), self.sb_rules(),
self.sb_stats()] %} self.sb_stats()] %}

View File

@ -6,10 +6,12 @@ from amanuensis.db import DbContext, Lexicon, User
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError
from amanuensis.server.helpers import lexicon_param, player_required_if_not_public from amanuensis.server.helpers import lexicon_param, player_required_if_not_public
from .characters import bp as characters_bp
from .forms import LexiconJoinForm from .forms import LexiconJoinForm
bp = Blueprint("lexicon", __name__, url_prefix="/lexicon/<name>", template_folder=".") bp = Blueprint("lexicon", __name__, url_prefix="/lexicon/<name>", template_folder=".")
bp.register_blueprint(characters_bp)
@bp.route("/join/", methods=["GET", "POST"]) @bp.route("/join/", methods=["GET", "POST"])

View File

@ -0,0 +1,94 @@
from flask import Blueprint, render_template, url_for
from werkzeug.utils import redirect
from amanuensis.server.helpers import lexicon_param, player_required
bp = Blueprint("characters", __name__, url_prefix="/characters", template_folder=".")
@bp.get('/')
@lexicon_param
@player_required
def characters(name):
return render_template('characters.jinja')
@bp.post('/')
@lexicon_param
@player_required
def update(name):
return redirect(url_for('lexicon.statistics', name=name))
# @bp.route('/', methods=['GET', 'POST'])
# @lexicon_param
# @player_required
# def characters(name):
# return render_template("characters.jinja")
# form = LexiconCharacterForm()
# cid = request.args.get('cid')
# if not cid:
# # No character specified, creating a new character
# return create_character(name, form)
# character = g.lexicon.cfg.character.get(cid)
# if not character:
# # Bad character id, abort
# flash('Character not found')
# return redirect(url_for('session.session', name=name))
# if current_user.uid not in (character.player, g.lexicon.cfg.editor):
# # Only its owner and the editor can edit a character
# flash('Access denied')
# return redirect(url_for('session.session', name=name))
# # Edit allowed
# return edit_character(name, form, character)
# def edit_character(name, form, character):
# if not form.is_submitted():
# # GET, populate with values
# return render_template(
# 'session.character.jinja', form=form.for_character(character))
# if not form.validate():
# # POST with invalid data, return unchanged
# return render_template('session.character.jinja', form=form)
# # POST with valid data, update character
# with g.lexicon.ctx.edit_config() as cfg:
# char = cfg.character[character.cid]
# char.name = form.characterName.data
# char.signature = form.defaultSignature.data
# flash('Character updated')
# return redirect(url_for('session.session', name=name))
# def create_character(name: str, form: LexiconCharacterForm):
# # Characters can't be created if the game has already started
# if g.lexicon.status != LexiconModel.PREGAME:
# flash("Characters can't be added after the game has started")
# return redirect(url_for('session.session', name=name))
# # Characters can't be created beyond the per-player limit
# player_characters = get_player_characters(g.lexicon, current_user.uid)
# if len(list(player_characters)) >= g.lexicon.cfg.join.chars_per_player:
# flash("Can't create more characters")
# return redirect(url_for('session.session', name=name))
# if not form.is_submitted():
# # GET, populate with default values
# return render_template(
# 'session.character.jinja', form=form.for_new())
# if not form.validate():
# # POST with invalid data, return unchanged
# return render_template('session.character.jinja', form=form)
# # POST with valid data, create character
# char_name = form.characterName.data
# cid = create_character_in_lexicon(current_user, g.lexicon, char_name)
# with g.lexicon.ctx.edit_config() as cfg:
# cfg.character[cid].signature = form.defaultSignature.data
# flash('Character created')
# return redirect(url_for('session.session', name=name))

View File

@ -0,0 +1,37 @@
{% extends "lexicon.jinja" %}
{% block title %}Character | {{ lexicon_title }}{% endblock %}
{% block main %}
<h1>Characters</h1>
{% set players = memq.get_players_in_lexicon(db, g.lexicon.id)|list %}
{% set characters = charq.get_in_lexicon(db, g.lexicon.id)|list %}
<p>This lexicon has <b>{{ players|count }}</b> player{% if players|count > 1 %}s{% endif %} and <b>{{ characters|count }}</b> character{% if characters|count > 1 %}s{% endif %}.</p>
<ul class="blockitem-list">
{% for character in characters %}
<li>
<h2>{{ character.name }}</h2>
<p>Player: {{ character.user.username }}</p>
</li>
{% endfor %}
</ul>
{# <form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.characterName.label }}<br>{{ form.characterName(size=32) }}
</p>
{% for error in form.characterName.errors %}
<span style="color: #ff0000">{{ error }}</span><br>
{% endfor %}</p>
<p>
{{ form.defaultSignature.label }}<br>{{ form.defaultSignature(class_='fullwidth') }}
</p>
<p>{{ form.submit() }}</p>
</form> #}
{# {% for message in get_flashed_messages() %}
<span style="color:#ff0000">{{ message }}</span><br>
{% endfor %} #}
{% endblock %}
{% set template_content_blocks = [self.main()] %}

View File

@ -68,77 +68,6 @@ def session(name):
publish_form=form) publish_form=form)
def edit_character(name, form, character):
if not form.is_submitted():
# GET, populate with values
return render_template(
'session.character.jinja', form=form.for_character(character))
if not form.validate():
# POST with invalid data, return unchanged
return render_template('session.character.jinja', form=form)
# POST with valid data, update character
with g.lexicon.ctx.edit_config() as cfg:
char = cfg.character[character.cid]
char.name = form.characterName.data
char.signature = form.defaultSignature.data
flash('Character updated')
return redirect(url_for('session.session', name=name))
def create_character(name: str, form: LexiconCharacterForm):
# Characters can't be created if the game has already started
if g.lexicon.status != LexiconModel.PREGAME:
flash("Characters can't be added after the game has started")
return redirect(url_for('session.session', name=name))
# Characters can't be created beyond the per-player limit
player_characters = get_player_characters(g.lexicon, current_user.uid)
if len(list(player_characters)) >= g.lexicon.cfg.join.chars_per_player:
flash("Can't create more characters")
return redirect(url_for('session.session', name=name))
if not form.is_submitted():
# GET, populate with default values
return render_template(
'session.character.jinja', form=form.for_new())
if not form.validate():
# POST with invalid data, return unchanged
return render_template('session.character.jinja', form=form)
# POST with valid data, create character
char_name = form.characterName.data
cid = create_character_in_lexicon(current_user, g.lexicon, char_name)
with g.lexicon.ctx.edit_config() as cfg:
cfg.character[cid].signature = form.defaultSignature.data
flash('Character created')
return redirect(url_for('session.session', name=name))
@bp_session.route('/character/', methods=['GET', 'POST'])
@lexicon_param
@player_required
def character(name):
form = LexiconCharacterForm()
cid = request.args.get('cid')
if not cid:
# No character specified, creating a new character
return create_character(name, form)
character = g.lexicon.cfg.character.get(cid)
if not character:
# Bad character id, abort
flash('Character not found')
return redirect(url_for('session.session', name=name))
if current_user.uid not in (character.player, g.lexicon.cfg.editor):
# Only its owner and the editor can edit a character
flash('Access denied')
return redirect(url_for('session.session', name=name))
# Edit allowed
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

View File

@ -1,26 +0,0 @@
{% extends "lexicon.jinja" %}
{% block title %}Character | {{ lexicon_title }}{% endblock %}
{% block main %}
<h1>Character</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.characterName.label }}<br>{{ form.characterName(size=32) }}
</p>
{% for error in form.characterName.errors %}
<span style="color: #ff0000">{{ error }}</span><br>
{% endfor %}</p>
<p>
{{ form.defaultSignature.label }}<br>{{ form.defaultSignature(class_='fullwidth') }}
</p>
<p>{{ form.submit() }}</p>
</form>
{% for message in get_flashed_messages() %}
<span style="color:#ff0000">{{ message }}</span><br>
{% endfor %}
{% endblock %}
{% set template_content_blocks = [self.main()] %}