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
4 changed files with 96 additions and 13 deletions
Showing only changes of commit a9c97430de - Show all commits

View File

@ -78,4 +78,6 @@ def get_in_lexicon(db: DbContext, lexicon_id: int) -> Sequence[Character]:
def try_from_public_id(db: DbContext, public_id: UUID) -> Optional[Character]: def try_from_public_id(db: DbContext, public_id: UUID) -> Optional[Character]:
"""Get a character by its public id.""" """Get a character by its public id."""
return db(select(Character).where(Character.public_id == public_id)).scalar_one_or_none() return db(
select(Character).where(Character.public_id == public_id)
).scalar_one_or_none()

View File

@ -30,6 +30,7 @@ class Uuid(TypeDecorator):
""" """
impl = CHAR(32) impl = CHAR(32)
cache_ok = True
def process_bind_param(self, value, dialect): def process_bind_param(self, value, dialect):
if value is None: if value is None:

View File

@ -2,7 +2,7 @@ from typing import Optional
import uuid import uuid
from flask import Blueprint, render_template, url_for, g, flash from flask import Blueprint, render_template, url_for, g, flash
from flask_login import current_user from flask_login import current_user
from werkzeug.utils import redirect from werkzeug.utils import redirect
from amanuensis.backend import charq from amanuensis.backend import charq
@ -15,14 +15,14 @@ from .forms import CharacterCreateForm
bp = Blueprint("characters", __name__, url_prefix="/characters", template_folder=".") bp = Blueprint("characters", __name__, url_prefix="/characters", template_folder=".")
@bp.get('/') @bp.get("/")
@lexicon_param @lexicon_param
@player_required @player_required
def list(name): def list(name):
return render_template('characters.jinja', name=name) return render_template("characters.jinja", name=name)
@bp.route('/edit/<character_id>', methods=['GET', 'POST']) @bp.route("/edit/<character_id>", methods=["GET", "POST"])
@lexicon_param @lexicon_param
@player_required @player_required
def edit(name, character_id): def edit(name, character_id):
@ -30,11 +30,11 @@ def edit(name, character_id):
char_uuid = uuid.UUID(character_id) char_uuid = uuid.UUID(character_id)
except: except:
flash("Character not found") flash("Character not found")
return redirect(url_for('lexicon.characters.list', name=name)) return redirect(url_for("lexicon.characters.list", name=name))
character: Optional[Character] = charq.try_from_public_id(g.db, char_uuid) character: Optional[Character] = charq.try_from_public_id(g.db, char_uuid)
if not character: if not character:
flash("Character not found") flash("Character not found")
return redirect(url_for('lexicon.characters.list', name=name)) return redirect(url_for("lexicon.characters.list", name=name))
form = CharacterCreateForm() form = CharacterCreateForm()
@ -42,7 +42,7 @@ def edit(name, character_id):
# GET # GET
form.name.data = character.name form.name.data = character.name
form.signature.data = character.signature form.signature.data = character.signature
return render_template('characters.edit.jinja', character=character, form=form) return render_template("characters.edit.jinja", character=character, form=form)
else: else:
# POST # POST
@ -51,18 +51,24 @@ def edit(name, character_id):
character.name = form.name.data character.name = form.name.data
character.signature = form.signature.data character.signature = form.signature.data
g.db.session.commit() g.db.session.commit()
return redirect(url_for('lexicon.characters.list', name=name)) return redirect(url_for("lexicon.characters.list", name=name))
else: else:
# POST submitted invalid data # POST submitted invalid data
return render_template('characters.edit.jinja', character=character, form=form) return render_template(
"characters.edit.jinja", character=character, form=form
)
@bp.get('/new/') @bp.get("/new/")
@lexicon_param @lexicon_param
@player_required @player_required
def new(name): def new(name):
dummy_name = f"{current_user.username}'s new character" dummy_name = f"{current_user.username}'s new character"
dummy_signature = "~" dummy_signature = "~"
charq.create(g.db, g.lexicon.id, current_user.id, dummy_name, dummy_signature) char = charq.create(
return redirect(url_for('lexicon.characters.list', name=name)) g.db, g.lexicon.id, current_user.id, dummy_name, dummy_signature
)
return redirect(
url_for("lexicon.characters.edit", name=name, character_id=char.public_id)
)

74
tests/test_character.py Normal file
View File

@ -0,0 +1,74 @@
import os
from urllib.parse import urlsplit
from bs4 import BeautifulSoup
from flask import Flask, url_for
from amanuensis.backend import memq, charq
from amanuensis.db import DbContext
from tests.conftest import ObjectFactory
def test_character_view(db: DbContext, app: Flask, make: ObjectFactory):
"""Test the lexicon character list, create, and edit pages."""
username: str = f"user_{os.urandom(8).hex()}"
charname: str = f"char_{os.urandom(8).hex()}"
char_sig: str = f"signature_{os.urandom(8).hex()}"
# ub: bytes = username.encode("utf8")
with app.test_client() as client:
# Create the user and log in
user = make.user(username=username, password=username)
assert user
user_client = make.client(user.id)
assert client
user_client.login(client)
# Create a lexicon and join
lexicon = make.lexicon()
assert lexicon
mem = memq.create(db, user.id, lexicon.id, is_editor=False)
assert mem
# The character page exists
list_url = url_for("lexicon.characters.list", name=lexicon.name)
response = client.get(list_url)
assert response.status_code == 200
assert charname.encode("utf8") not in response.data
assert char_sig.encode("utf8") not in response.data
new_url = url_for("lexicon.characters.new", name=lexicon.name)
assert new_url.encode("utf8") in response.data
# The character creation endpoint works
response = client.get(new_url)
assert response.status_code == 302
chars = list(charq.get_in_lexicon(db, lexicon.id))
assert len(chars) == 1
assert chars[0].user_id == user.id
created_redirect = response.location
assert str(chars[0].public_id) in created_redirect
# The character edit page works
response = client.get(created_redirect)
assert chars[0].name.encode("utf8") in response.data
assert chars[0].signature.encode("utf8") in response.data
assert b"csrf_token" in response.data
# Submitting the edit page works
soup = BeautifulSoup(response.data, features="html.parser")
csrf_token = soup.find(id="csrf_token")["value"]
assert csrf_token
response = client.post(
created_redirect,
data={"name": charname, "signature": char_sig, "csrf_token": csrf_token},
)
print(response.data.decode("utf8"))
assert 300 <= response.status_code <= 399
# The character is updated
chars = list(charq.get_in_lexicon(db, lexicon.id))
assert len(chars) == 1
assert chars[0].user_id == user.id
assert chars[0].name == charname
assert chars[0].signature == char_sig