Compare commits
4 Commits
abbe6e6b2e
...
0d022af335
Author | SHA1 | Date |
---|---|---|
Tim Van Baak | 0d022af335 | |
Tim Van Baak | a91be8bc87 | |
Tim Van Baak | b94a2224c0 | |
Tim Van Baak | 6f73c58d23 |
|
@ -1,9 +1,10 @@
|
||||||
import amanuensis.backend.article as artiq
|
import amanuensis.backend.article as artiq
|
||||||
import amanuensis.backend.character as charq
|
import amanuensis.backend.character as charq
|
||||||
import amanuensis.backend.index as indq
|
import amanuensis.backend.index as indq
|
||||||
|
import amanuensis.backend.indexrule as irq
|
||||||
import amanuensis.backend.lexicon as lexiq
|
import amanuensis.backend.lexicon as lexiq
|
||||||
import amanuensis.backend.membership as memq
|
import amanuensis.backend.membership as memq
|
||||||
import amanuensis.backend.post as postq
|
import amanuensis.backend.post as postq
|
||||||
import amanuensis.backend.user as userq
|
import amanuensis.backend.user as userq
|
||||||
|
|
||||||
__all__ = ["artiq", "charq", "indq", "lexiq", "memq", "postq", "userq"]
|
__all__ = ["artiq", "charq", "indq", "irq", "lexiq", "memq", "postq", "userq"]
|
||||||
|
|
|
@ -87,19 +87,18 @@ def update(db: DbContext, lexicon_id: int, indices: Sequence[ArticleIndex]) -> N
|
||||||
"""
|
"""
|
||||||
Update the indices for a lexicon. Indices are matched by type and pattern.
|
Update the indices for a lexicon. Indices are matched by type and pattern.
|
||||||
An extant index not matched to an input is deleted, and an input index not
|
An extant index not matched to an input is deleted, and an input index not
|
||||||
matched to a an extant index is created. Matched indexes are updated with
|
matched to a an extant index is created. Matched indices are updated with
|
||||||
the input logical and display orders and capacity.
|
the input logical and display orders and capacity.
|
||||||
|
|
||||||
|
Note that this scheme does not allow for an existing index to have its type
|
||||||
|
or pattern updated: such an operation will always result in the deletion of
|
||||||
|
the old index and the creation of a new index.
|
||||||
"""
|
"""
|
||||||
extant_indices: Sequence[ArticleIndex] = list(get_for_lexicon(db, lexicon_id))
|
extant_indices: Sequence[ArticleIndex] = list(get_for_lexicon(db, lexicon_id))
|
||||||
s = lambda i: f"{i.index_type}:{i.pattern}"
|
|
||||||
for extant_index in extant_indices:
|
for extant_index in extant_indices:
|
||||||
match = None
|
match = None
|
||||||
for new_index in indices:
|
for new_index in indices:
|
||||||
is_match = (
|
if extant_index.name == new_index.name:
|
||||||
extant_index.index_type == new_index.index_type
|
|
||||||
and extant_index.pattern == new_index.pattern
|
|
||||||
)
|
|
||||||
if is_match:
|
|
||||||
match = new_index
|
match = new_index
|
||||||
break
|
break
|
||||||
if match:
|
if match:
|
||||||
|
@ -111,14 +110,9 @@ def update(db: DbContext, lexicon_id: int, indices: Sequence[ArticleIndex]) -> N
|
||||||
for new_index in indices:
|
for new_index in indices:
|
||||||
match = None
|
match = None
|
||||||
for extant_index in extant_indices:
|
for extant_index in extant_indices:
|
||||||
is_match = (
|
if extant_index.name == new_index.name:
|
||||||
extant_index.index_type == new_index.index_type
|
|
||||||
and extant_index.pattern == new_index.pattern
|
|
||||||
)
|
|
||||||
if is_match:
|
|
||||||
match = extant_index
|
match = extant_index
|
||||||
break
|
break
|
||||||
if not match:
|
if not match:
|
||||||
new_index.lexicon_id = lexicon_id
|
|
||||||
db.session.add(new_index)
|
db.session.add(new_index)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
"""
|
||||||
|
Index rule query interface
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
from amanuensis.db import *
|
||||||
|
from amanuensis.errors import ArgumentError, BackendArgumentTypeError
|
||||||
|
|
||||||
|
|
||||||
|
def create(
|
||||||
|
db: DbContext,
|
||||||
|
lexicon_id: int,
|
||||||
|
character_id: int,
|
||||||
|
index_id: int,
|
||||||
|
turn: int,
|
||||||
|
) -> ArticleIndexRule:
|
||||||
|
"""Create an index assignment."""
|
||||||
|
# Verify argument types are correct
|
||||||
|
if not isinstance(lexicon_id, int):
|
||||||
|
raise BackendArgumentTypeError(int, lexicon_id=lexicon_id)
|
||||||
|
if character_id is not None and not isinstance(character_id, int):
|
||||||
|
raise BackendArgumentTypeError(int, character_id=character_id)
|
||||||
|
if not isinstance(index_id, int):
|
||||||
|
raise BackendArgumentTypeError(int, index_id=index_id)
|
||||||
|
if not isinstance(turn, int):
|
||||||
|
raise BackendArgumentTypeError(int, turn=turn)
|
||||||
|
|
||||||
|
# Verify the character belongs to the lexicon
|
||||||
|
character: Character = db(
|
||||||
|
select(Character).where(Character.id == character_id)
|
||||||
|
).scalar_one_or_none()
|
||||||
|
if not character:
|
||||||
|
raise ArgumentError("Character does not exist")
|
||||||
|
if character.lexicon_id != lexicon_id:
|
||||||
|
raise ArgumentError("Character belongs to the wrong lexicon")
|
||||||
|
|
||||||
|
# Verify the index belongs to the lexicon
|
||||||
|
index: ArticleIndex = db(
|
||||||
|
select(ArticleIndex).where(ArticleIndex.id == index_id)
|
||||||
|
).scalar_one_or_none()
|
||||||
|
if not index:
|
||||||
|
raise ArgumentError("Index does not exist")
|
||||||
|
if index.lexicon_id != lexicon_id:
|
||||||
|
raise ArgumentError("Index belongs to the wrong lexicon")
|
||||||
|
|
||||||
|
new_assignment: ArticleIndexRule = ArticleIndexRule(
|
||||||
|
lexicon_id=lexicon_id,
|
||||||
|
character_id=character_id,
|
||||||
|
index_id=index_id,
|
||||||
|
turn=turn,
|
||||||
|
)
|
||||||
|
db.session.add(new_assignment)
|
||||||
|
db.session.commit()
|
||||||
|
return new_assignment
|
||||||
|
|
||||||
|
|
||||||
|
def get_for_lexicon(db: DbContext, lexicon_id: int) -> Sequence[ArticleIndex]:
|
||||||
|
"""Returns all index rules for a lexicon."""
|
||||||
|
return db(
|
||||||
|
select(ArticleIndexRule)
|
||||||
|
.join(ArticleIndexRule.index)
|
||||||
|
.join(ArticleIndexRule.character)
|
||||||
|
.where(ArticleIndexRule.lexicon_id == lexicon_id)
|
||||||
|
.order_by(ArticleIndexRule.turn, ArticleIndex.pattern, Character.name)
|
||||||
|
).scalars()
|
||||||
|
|
||||||
|
|
||||||
|
def update(db: DbContext, lexicon_id: int, rules: Sequence[ArticleIndexRule]) -> None:
|
||||||
|
"""
|
||||||
|
Update the index assignments for a lexicon. An index assignment is a tuple
|
||||||
|
of turn, index, and character. Unlike indices themselves, assignments have
|
||||||
|
no other attributes that can be updated, so they are simply created or
|
||||||
|
deleted based on their presence or absence in the desired rule list.
|
||||||
|
"""
|
||||||
|
print(rules)
|
||||||
|
extant_rules: Sequence[ArticleIndexRule] = list(get_for_lexicon(db, lexicon_id))
|
||||||
|
for extant_rule in extant_rules:
|
||||||
|
if not any(
|
||||||
|
[
|
||||||
|
extant_rule.character_id == new_rule.character_id
|
||||||
|
and extant_rule.index_id == new_rule.index_id
|
||||||
|
and extant_rule.turn == new_rule.turn
|
||||||
|
for new_rule in rules
|
||||||
|
]
|
||||||
|
):
|
||||||
|
db.session.delete(extant_rule)
|
||||||
|
for new_rule in rules:
|
||||||
|
if not any(
|
||||||
|
[
|
||||||
|
extant_rule.character_id == new_rule.character_id
|
||||||
|
and extant_rule.index_id == new_rule.index_id
|
||||||
|
and extant_rule.turn == new_rule.turn
|
||||||
|
for extant_rule in extant_rules
|
||||||
|
]
|
||||||
|
):
|
||||||
|
db.session.add(new_rule)
|
||||||
|
db.session.commit()
|
|
@ -1,4 +1,3 @@
|
||||||
import enum
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from amanuensis.backend import *
|
from amanuensis.backend import *
|
||||||
|
@ -8,7 +7,7 @@ from .helpers import add_argument
|
||||||
|
|
||||||
|
|
||||||
COMMAND_NAME = "index"
|
COMMAND_NAME = "index"
|
||||||
COMMAND_HELP = "Interact with indexes."
|
COMMAND_HELP = "Interact with indices."
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -40,3 +39,26 @@ def command_create(args) -> int:
|
||||||
)
|
)
|
||||||
LOG.info(f"Created {index.index_type}:{index.pattern} in {lexicon.full_title}")
|
LOG.info(f"Created {index.index_type}:{index.pattern} in {lexicon.full_title}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@add_argument("--lexicon", required=True, help="The lexicon's name")
|
||||||
|
@add_argument("--character", help="The character's public id")
|
||||||
|
@add_argument("--index", required=True, help="The index pattern")
|
||||||
|
@add_argument("--turn", required=True, type=int)
|
||||||
|
def command_assign(args) -> int:
|
||||||
|
"""
|
||||||
|
Create a turn assignment for a lexicon.
|
||||||
|
"""
|
||||||
|
db: DbContext = args.get_db()
|
||||||
|
lexicon = lexiq.try_from_name(db, args.lexicon)
|
||||||
|
if not lexicon:
|
||||||
|
raise ValueError("Lexicon does not exist")
|
||||||
|
char = charq.try_from_public_id(db, args.character)
|
||||||
|
assert char
|
||||||
|
indices = indq.get_for_lexicon(db, lexicon.id)
|
||||||
|
index = [i for i in indices if i.pattern == args.index]
|
||||||
|
if not index:
|
||||||
|
raise ValueError("Index not found")
|
||||||
|
assignment = irq.create(db, lexicon.id, char.id, index[0].id, args.turn)
|
||||||
|
LOG.info("Created")
|
||||||
|
return 0
|
||||||
|
|
|
@ -248,7 +248,7 @@ class Lexicon(ModelBase):
|
||||||
memberships = relationship("Membership", back_populates="lexicon")
|
memberships = relationship("Membership", back_populates="lexicon")
|
||||||
characters = relationship("Character", back_populates="lexicon")
|
characters = relationship("Character", back_populates="lexicon")
|
||||||
articles = relationship("Article", back_populates="lexicon")
|
articles = relationship("Article", back_populates="lexicon")
|
||||||
indexes = relationship("ArticleIndex", back_populates="lexicon")
|
indices = relationship("ArticleIndex", back_populates="lexicon")
|
||||||
index_rules = relationship("ArticleIndexRule", back_populates="lexicon")
|
index_rules = relationship("ArticleIndexRule", back_populates="lexicon")
|
||||||
content_rules = relationship("ArticleContentRule", back_populates="lexicon")
|
content_rules = relationship("ArticleContentRule", back_populates="lexicon")
|
||||||
posts = relationship("Post", back_populates="lexicon")
|
posts = relationship("Post", back_populates="lexicon")
|
||||||
|
@ -502,9 +502,13 @@ class ArticleIndex(ModelBase):
|
||||||
# Foreign key relationships #
|
# Foreign key relationships #
|
||||||
#############################
|
#############################
|
||||||
|
|
||||||
lexicon = relationship("Lexicon", back_populates="indexes")
|
lexicon = relationship("Lexicon", back_populates="indices")
|
||||||
index_rules = relationship("ArticleIndexRule", back_populates="index")
|
index_rules = relationship("ArticleIndexRule", back_populates="index")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return f"{self.index_type}:{self.pattern}"
|
||||||
|
|
||||||
|
|
||||||
class ArticleIndexRule(ModelBase):
|
class ArticleIndexRule(ModelBase):
|
||||||
"""
|
"""
|
||||||
|
@ -514,6 +518,7 @@ class ArticleIndexRule(ModelBase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "article_index_rule"
|
__tablename__ = "article_index_rule"
|
||||||
|
__table_args__ = (UniqueConstraint("character_id", "index_id", "turn"),)
|
||||||
|
|
||||||
###################
|
###################
|
||||||
# Index rule info #
|
# Index rule info #
|
||||||
|
|
|
@ -392,7 +392,7 @@ def sort_by_index_spec(articles, index_specs, key=None):
|
||||||
indexed = OrderedDict()
|
indexed = OrderedDict()
|
||||||
for index in index_list_order:
|
for index in index_list_order:
|
||||||
indexed[index.pattern] = []
|
indexed[index.pattern] = []
|
||||||
# Sort articles into indexes
|
# Sort articles into indices
|
||||||
for article in articles_titlesorted:
|
for article in articles_titlesorted:
|
||||||
for index in index_eval_order:
|
for index in index_eval_order:
|
||||||
if index_match(index, key(article)):
|
if index_match(index, key(article)):
|
||||||
|
|
|
@ -201,7 +201,7 @@ ul.unordered-tabs li a[href]:hover {
|
||||||
background-color: var(--button-hover);
|
background-color: var(--button-hover);
|
||||||
border-color: var(--button-hover);
|
border-color: var(--button-hover);
|
||||||
}
|
}
|
||||||
#index-definition-help {
|
details.setting-help {
|
||||||
margin-block-start: 1em;
|
margin-block-start: 1em;
|
||||||
margin-block-end: 1em;
|
margin-block-end: 1em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,12 @@ from amanuensis.server.helpers import (
|
||||||
current_lexicon,
|
current_lexicon,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .forms import PlayerSettingsForm, SetupSettingsForm, IndexSchemaForm
|
from .forms import (
|
||||||
|
PlayerSettingsForm,
|
||||||
|
SetupSettingsForm,
|
||||||
|
IndexSchemaForm,
|
||||||
|
IndexAssignmentsForm,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint("settings", __name__, url_prefix="/settings", template_folder=".")
|
bp = Blueprint("settings", __name__, url_prefix="/settings", template_folder=".")
|
||||||
|
@ -159,9 +164,16 @@ def index_post(lexicon_name):
|
||||||
# Initialize the form
|
# Initialize the form
|
||||||
form = IndexSchemaForm()
|
form = IndexSchemaForm()
|
||||||
if form.validate():
|
if form.validate():
|
||||||
# Valid data, strip out all indexes with the blank type
|
# Valid data, strip out all indices with the blank type
|
||||||
indices = [
|
indices = [
|
||||||
index_def.to_model()
|
ArticleIndex(
|
||||||
|
lexicon_id=current_lexicon.id,
|
||||||
|
index_type=index_def.index_type.data,
|
||||||
|
pattern=index_def.pattern.data,
|
||||||
|
logical_order=index_def.logical_order.data,
|
||||||
|
display_order=index_def.display_order.data,
|
||||||
|
capacity=index_def.capacity.data,
|
||||||
|
)
|
||||||
for index_def in form.indices.entries
|
for index_def in form.indices.entries
|
||||||
if index_def.index_type.data
|
if index_def.index_type.data
|
||||||
]
|
]
|
||||||
|
@ -177,6 +189,89 @@ def index_post(lexicon_name):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.get("/assign/")
|
||||||
|
@lexicon_param
|
||||||
|
@editor_required
|
||||||
|
def assign(lexicon_name):
|
||||||
|
# Get the current assignments
|
||||||
|
rules: Sequence[ArticleIndexRule] = list(
|
||||||
|
irq.get_for_lexicon(g.db, current_lexicon.id)
|
||||||
|
)
|
||||||
|
rule_data = [
|
||||||
|
{
|
||||||
|
"turn": rule.turn,
|
||||||
|
"index": rule.index.name,
|
||||||
|
"character": str(rule.character.public_id),
|
||||||
|
}
|
||||||
|
for rule in rules
|
||||||
|
]
|
||||||
|
# Add a blank rule to allow for adding rules
|
||||||
|
rule_data.append(
|
||||||
|
{
|
||||||
|
"turn": 0,
|
||||||
|
"index": "",
|
||||||
|
"character": "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
form = IndexAssignmentsForm(rules=rule_data)
|
||||||
|
form.populate(current_lexicon)
|
||||||
|
return render_template(
|
||||||
|
"settings.jinja",
|
||||||
|
lexicon_name=lexicon_name,
|
||||||
|
page_name=assign.__name__,
|
||||||
|
form=form,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post("/assign/")
|
||||||
|
@lexicon_param
|
||||||
|
@editor_required
|
||||||
|
def assign_post(lexicon_name):
|
||||||
|
# Initialize the form
|
||||||
|
form = IndexAssignmentsForm()
|
||||||
|
form.populate(current_lexicon)
|
||||||
|
if form.validate():
|
||||||
|
# Valid data
|
||||||
|
indices = list(current_lexicon.indices)
|
||||||
|
characters = list(current_lexicon.characters)
|
||||||
|
rules = []
|
||||||
|
for rule_def in form.rules.entries:
|
||||||
|
# Strip out all assignments with no character
|
||||||
|
if not rule_def.character.data:
|
||||||
|
continue
|
||||||
|
# Look up the necessary ids from the public representations
|
||||||
|
character = [
|
||||||
|
c for c in characters if c.public_id == rule_def.character.data
|
||||||
|
]
|
||||||
|
if not character:
|
||||||
|
return redirect(
|
||||||
|
url_for("lexicon.settings.assign", lexicon_name=lexicon_name)
|
||||||
|
)
|
||||||
|
index = [i for i in indices if i.name == rule_def.index.data]
|
||||||
|
if not index:
|
||||||
|
return redirect(
|
||||||
|
url_for("lexicon.settings.assign", lexicon_name=lexicon_name)
|
||||||
|
)
|
||||||
|
rules.append(
|
||||||
|
ArticleIndexRule(
|
||||||
|
lexicon_id=current_lexicon.id,
|
||||||
|
character_id=character[0].id,
|
||||||
|
index_id=index[0].id,
|
||||||
|
turn=rule_def.turn.data,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
irq.update(g.db, current_lexicon.id, rules)
|
||||||
|
return redirect(url_for("lexicon.settings.assign", lexicon_name=lexicon_name))
|
||||||
|
else:
|
||||||
|
# Invalid data
|
||||||
|
return render_template(
|
||||||
|
"settings.jinja",
|
||||||
|
lexicon_name=lexicon_name,
|
||||||
|
page_name=assign.__name__,
|
||||||
|
form=form,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/publish/")
|
@bp.get("/publish/")
|
||||||
@lexicon_param
|
@lexicon_param
|
||||||
@editor_required
|
@editor_required
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import (
|
from wtforms import (
|
||||||
BooleanField,
|
BooleanField,
|
||||||
|
@ -13,7 +15,7 @@ from wtforms import (
|
||||||
from wtforms.validators import Optional, DataRequired, ValidationError
|
from wtforms.validators import Optional, DataRequired, ValidationError
|
||||||
from wtforms.widgets.html5 import NumberInput
|
from wtforms.widgets.html5 import NumberInput
|
||||||
|
|
||||||
from amanuensis.db import ArticleIndex, IndexType
|
from amanuensis.db import IndexType, Lexicon
|
||||||
|
|
||||||
|
|
||||||
class PlayerSettingsForm(FlaskForm):
|
class PlayerSettingsForm(FlaskForm):
|
||||||
|
@ -80,18 +82,47 @@ class IndexDefinitionForm(FlaskForm):
|
||||||
if form.index_type.data and not field.data:
|
if form.index_type.data and not field.data:
|
||||||
raise ValidationError("Pattern must be defined")
|
raise ValidationError("Pattern must be defined")
|
||||||
|
|
||||||
def to_model(self):
|
|
||||||
return ArticleIndex(
|
|
||||||
index_type=self.index_type.data,
|
|
||||||
pattern=self.pattern.data,
|
|
||||||
logical_order=self.logical_order.data,
|
|
||||||
display_order=self.display_order.data,
|
|
||||||
capacity=self.capacity.data,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class IndexSchemaForm(FlaskForm):
|
class IndexSchemaForm(FlaskForm):
|
||||||
"""/lexicon/<name>/settings/index/"""
|
"""/lexicon/<name>/settings/index/"""
|
||||||
|
|
||||||
indices = FieldList(FormField(IndexDefinitionForm))
|
indices = FieldList(FormField(IndexDefinitionForm))
|
||||||
submit = SubmitField("Submit")
|
submit = SubmitField("Submit")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_uuid(uuid_str):
|
||||||
|
if not uuid_str:
|
||||||
|
return None
|
||||||
|
return uuid.UUID(uuid_str)
|
||||||
|
|
||||||
|
|
||||||
|
class AssignmentDefinitionForm(FlaskForm):
|
||||||
|
"""/lexicon/<name>/settings/assign/"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
# Disable CSRF on the individual assignment definitions, since the
|
||||||
|
# schema form will have one
|
||||||
|
csrf = False
|
||||||
|
|
||||||
|
turn = IntegerField(widget=NumberInput(min=0, max=99))
|
||||||
|
index = SelectField()
|
||||||
|
character = SelectField(coerce=parse_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
class IndexAssignmentsForm(FlaskForm):
|
||||||
|
"""/lexicon/<name>/settings/assign/"""
|
||||||
|
|
||||||
|
rules = FieldList(FormField(AssignmentDefinitionForm))
|
||||||
|
submit = SubmitField("Submit")
|
||||||
|
|
||||||
|
def populate(self, lexicon: Lexicon):
|
||||||
|
"""Populate the select fields with indices and characters"""
|
||||||
|
index_choices = []
|
||||||
|
for i in lexicon.indices:
|
||||||
|
index_choices.append((i.name, i.pattern))
|
||||||
|
char_choices = [("", "")]
|
||||||
|
for c in lexicon.characters:
|
||||||
|
char_choices.append((str(c.public_id), c.name))
|
||||||
|
for rule in self.rules:
|
||||||
|
rule.index.choices = index_choices
|
||||||
|
rule.character.choices = char_choices
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
<li>{{ settings_page_link("player", "Player Settings") }}</li>
|
<li>{{ settings_page_link("player", "Player Settings") }}</li>
|
||||||
<li>{{ settings_page_link("setup", "Game Setup") }}</li>
|
<li>{{ settings_page_link("setup", "Game Setup") }}</li>
|
||||||
<li>{{ settings_page_link("index", "Article Indices") }}</li>
|
<li>{{ settings_page_link("index", "Article Indices") }}</li>
|
||||||
|
<li>{{ settings_page_link("assign", "Index Assignments") }}</li>
|
||||||
<li>{{ settings_page_link("publish", "Turn Publishing") }}</li>
|
<li>{{ settings_page_link("publish", "Turn Publishing") }}</li>
|
||||||
<li>{{ settings_page_link("article", "Article Requirements") }}</li>
|
<li>{{ settings_page_link("article", "Article Requirements") }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -86,7 +87,7 @@
|
||||||
|
|
||||||
{% if page_name == "index" %}
|
{% if page_name == "index" %}
|
||||||
<h3>Article Indexes</h3>
|
<h3>Article Indexes</h3>
|
||||||
<details id="index-definition-help">
|
<details class="setting-help">
|
||||||
<summary>Index definition help</summary>
|
<summary>Index definition help</summary>
|
||||||
<p>An index is a rule that matches the title of a lexicon article based on its <em>index type</em> and <em>pattern</em>. A <em>char</em> index matches a title if the first letter of the title (excluding "A", "An", and "The") is one of the letters in the pattern. A <em>range</em> index has a pattern denoting a range of letters, such as "A-F", and matches a title if the first letter of the title is in the range. A <em>prefix</em> index matches any title that begins with the pattern. An <em>etc</em> index always matches a title.</p>
|
<p>An index is a rule that matches the title of a lexicon article based on its <em>index type</em> and <em>pattern</em>. A <em>char</em> index matches a title if the first letter of the title (excluding "A", "An", and "The") is one of the letters in the pattern. A <em>range</em> index has a pattern denoting a range of letters, such as "A-F", and matches a title if the first letter of the title is in the range. A <em>prefix</em> index matches any title that begins with the pattern. An <em>etc</em> index always matches a title.</p>
|
||||||
<p>When a title is to be sorted under an index, indices are checked in order, sorted first by descending order of <em>logical priority</em>, and then by alphabetical order of index pattern. The title is sorted under the first index that matches it.</p>
|
<p>When a title is to be sorted under an index, indices are checked in order, sorted first by descending order of <em>logical priority</em>, and then by alphabetical order of index pattern. The title is sorted under the first index that matches it.</p>
|
||||||
|
@ -128,6 +129,42 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if page_name == "assign" %}
|
||||||
|
<h3>Index Assignments</h3>
|
||||||
|
<details class="setting-help">
|
||||||
|
<summary>Index assignment help</summary>
|
||||||
|
<p>An index assignment is a rule that requires a player to write an article under certain indices for a particular turn. If more than one rule applies to a player, any index satisfying one of those rules is permitted. If no rule applies to a player, any index is permitted.</p>
|
||||||
|
</details>
|
||||||
|
<form action="" method="post" novalidate>
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
<table id="index-definition-table2">
|
||||||
|
<tr>
|
||||||
|
<th>Turn</th>
|
||||||
|
<th>Index</th>
|
||||||
|
<th>Character</th>
|
||||||
|
</tr>
|
||||||
|
{% for rule_form in form.rules %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ rule_form.turn() }}</td>
|
||||||
|
<td>{{ rule_form.index() }}</td>
|
||||||
|
<td>{{ rule_form.character() }}</td>
|
||||||
|
</tr>
|
||||||
|
{% for field in index_form %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5"><span style="color: #ff0000">{{ error }}</span></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
<p>{{ form.submit() }}</p>
|
||||||
|
</form>
|
||||||
|
{% for message in get_flashed_messages() %}
|
||||||
|
<span style="color:#ff0000">{{ message }}</span><br>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if page_name == "publish" %}
|
{% if page_name == "publish" %}
|
||||||
<h3>Turn Publishing</h3>
|
<h3>Turn Publishing</h3>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -4,5 +4,6 @@ pkgs.mkShell {
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
pkgs.python3
|
pkgs.python3
|
||||||
pkgs.poetry
|
pkgs.poetry
|
||||||
|
pkgs.sqlite
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from amanuensis.backend import irq
|
||||||
|
from amanuensis.db import *
|
||||||
|
from amanuensis.errors import ArgumentError
|
||||||
|
|
||||||
|
from tests.conftest import ObjectFactory
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_assign(db: DbContext, make: ObjectFactory):
|
||||||
|
"""Test new index assignment creation"""
|
||||||
|
lexicon: Lexicon = make.lexicon()
|
||||||
|
user: User = make.user()
|
||||||
|
mem: Membership = make.membership(lexicon_id=lexicon.id, user_id=user.id)
|
||||||
|
char: Character = make.character(lexicon_id=lexicon.id, user_id=user.id)
|
||||||
|
ind1: ArticleIndex = make.index(lexicon_id=lexicon.id)
|
||||||
|
|
||||||
|
defaults: dict = {
|
||||||
|
"db": db,
|
||||||
|
"lexicon_id": lexicon.id,
|
||||||
|
"character_id": char.id,
|
||||||
|
"index_id": ind1.id,
|
||||||
|
"turn": 1,
|
||||||
|
}
|
||||||
|
kwargs: dict
|
||||||
|
|
||||||
|
# Index assignments must key to objects in the same lexicon
|
||||||
|
lexicon2: Lexicon = make.lexicon()
|
||||||
|
mem2: Membership = make.membership(lexicon_id=lexicon2.id, user_id=user.id)
|
||||||
|
char2: Character = make.character(lexicon_id=lexicon2.id, user_id=user.id)
|
||||||
|
ind2: ArticleIndex = make.index(lexicon_id=lexicon2.id)
|
||||||
|
with pytest.raises(ArgumentError):
|
||||||
|
kwargs = {**defaults, "index_id": ind2.id}
|
||||||
|
irq.create(**kwargs)
|
||||||
|
with pytest.raises(ArgumentError):
|
||||||
|
kwargs = {**defaults, "character_id": char2.id, "index_id": ind2.id}
|
||||||
|
irq.create(**kwargs)
|
||||||
|
with pytest.raises(ArgumentError):
|
||||||
|
kwargs = {**defaults, "character_id": char2.id}
|
||||||
|
irq.create(**kwargs)
|
|
@ -10,9 +10,9 @@ from bs4 import BeautifulSoup
|
||||||
from flask.testing import FlaskClient
|
from flask.testing import FlaskClient
|
||||||
from sqlalchemy.orm.session import close_all_sessions
|
from sqlalchemy.orm.session import close_all_sessions
|
||||||
|
|
||||||
from amanuensis.backend import charq, lexiq, memq, userq
|
from amanuensis.backend import *
|
||||||
from amanuensis.config import AmanuensisConfig
|
from amanuensis.config import AmanuensisConfig
|
||||||
from amanuensis.db import DbContext, User, Lexicon, Membership, Character
|
from amanuensis.db import *
|
||||||
from amanuensis.server import get_app
|
from amanuensis.server import get_app
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,6 +122,19 @@ class ObjectFactory:
|
||||||
updated_kwargs: dict = {**default_kwargs, **kwargs}
|
updated_kwargs: dict = {**default_kwargs, **kwargs}
|
||||||
return charq.create(self.db, **updated_kwargs)
|
return charq.create(self.db, **updated_kwargs)
|
||||||
|
|
||||||
|
def index(self, state={"nonce": ord("A")}, **kwargs) -> ArticleIndex:
|
||||||
|
"""Factory function for creating indices, with valid defaut values."""
|
||||||
|
default_kwargs: dict = {
|
||||||
|
"index_type": IndexType.CHAR,
|
||||||
|
"pattern": chr(state["nonce"]),
|
||||||
|
"logical_order": 0,
|
||||||
|
"display_order": 0,
|
||||||
|
"capacity": None,
|
||||||
|
}
|
||||||
|
state["nonce"] += 1
|
||||||
|
updated_kwargs = {**default_kwargs, **kwargs}
|
||||||
|
return indq.create(self.db, **updated_kwargs)
|
||||||
|
|
||||||
def client(self, user_id: int) -> UserClient:
|
def client(self, user_id: int) -> UserClient:
|
||||||
"""Factory function for user test clients."""
|
"""Factory function for user test clients."""
|
||||||
return UserClient(self.db, user_id)
|
return UserClient(self.db, user_id)
|
||||||
|
|
Loading…
Reference in New Issue