Compare commits

..

1 Commits

Author SHA1 Message Date
Tim Van Baak 5e5b29b3e7 tmp 2021-09-15 22:17:13 -07:00
8 changed files with 99 additions and 218 deletions

View File

@ -89,16 +89,17 @@ def update(db: DbContext, lexicon_id: int, indices: Sequence[ArticleIndex]) -> N
An extant index not matched to an input is deleted, and an input index not
matched to a an extant index is created. Matched indices are updated with
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))
s = lambda i: f"{i.index_type}:{i.pattern}"
for extant_index in extant_indices:
match = None
for new_index in indices:
if extant_index.name == new_index.name:
is_match = (
extant_index.index_type == new_index.index_type
and extant_index.pattern == new_index.pattern
)
if is_match:
match = new_index
break
if match:
@ -110,9 +111,14 @@ def update(db: DbContext, lexicon_id: int, indices: Sequence[ArticleIndex]) -> N
for new_index in indices:
match = None
for extant_index in extant_indices:
if extant_index.name == new_index.name:
is_match = (
extant_index.index_type == new_index.index_type
and extant_index.pattern == new_index.pattern
)
if is_match:
match = extant_index
break
if not match:
new_index.lexicon_id = lexicon_id
db.session.add(new_index)
db.session.commit()

View File

@ -34,18 +34,9 @@ def create(
).scalar_one_or_none()
if not character:
raise ArgumentError("Character does not exist")
if character.lexicon_id != lexicon_id:
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,
@ -60,41 +51,5 @@ def create(
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)
select(ArticleIndexRule).where(ArticleIndexRule.lexicon_id == lexicon_id)
).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()

View File

@ -505,10 +505,6 @@ class ArticleIndex(ModelBase):
lexicon = relationship("Lexicon", back_populates="indices")
index_rules = relationship("ArticleIndexRule", back_populates="index")
@property
def name(self):
return f"{self.index_type}:{self.pattern}"
class ArticleIndexRule(ModelBase):
"""
@ -518,7 +514,6 @@ class ArticleIndexRule(ModelBase):
"""
__tablename__ = "article_index_rule"
__table_args__ = (UniqueConstraint("character_id", "index_id", "turn"),)
###################
# Index rule info #

View File

@ -12,12 +12,7 @@ from amanuensis.server.helpers import (
current_lexicon,
)
from .forms import (
PlayerSettingsForm,
SetupSettingsForm,
IndexSchemaForm,
IndexAssignmentsForm,
)
from .forms import PlayerSettingsForm, SetupSettingsForm, IndexSchemaForm, IndexAssignmentsForm
bp = Blueprint("settings", __name__, url_prefix="/settings", template_folder=".")
@ -166,14 +161,7 @@ def index_post(lexicon_name):
if form.validate():
# Valid data, strip out all indices with the blank type
indices = [
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,
)
index_def.to_model()
for index_def in form.indices.entries
if index_def.index_type.data
]
@ -194,84 +182,27 @@ def index_post(lexicon_name):
@editor_required
def assign(lexicon_name):
# Get the current assignments
rules: Sequence[ArticleIndexRule] = list(
irq.get_for_lexicon(g.db, current_lexicon.id)
)
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),
"index": rule.index.pattern,
"character": rule.character.public_id,
}
for rule in rules
]
# Add a blank rule to allow for adding rules
rule_data.append(
{
"turn": 0,
"index": "",
"character": "",
}
)
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,
"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/")
@lexicon_param
@editor_required

View File

@ -1,5 +1,3 @@
import uuid
from flask_wtf import FlaskForm
from wtforms import (
BooleanField,
@ -15,7 +13,8 @@ from wtforms import (
from wtforms.validators import Optional, DataRequired, ValidationError
from wtforms.widgets.html5 import NumberInput
from amanuensis.db import IndexType, Lexicon
from amanuensis.db import ArticleIndex, IndexType, Lexicon
from amanuensis.server.helpers import current_lexicon
class PlayerSettingsForm(FlaskForm):
@ -82,6 +81,15 @@ class IndexDefinitionForm(FlaskForm):
if form.index_type.data and not field.data:
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):
"""/lexicon/<name>/settings/index/"""
@ -90,12 +98,6 @@ class IndexSchemaForm(FlaskForm):
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/"""
@ -106,7 +108,13 @@ class AssignmentDefinitionForm(FlaskForm):
turn = IntegerField(widget=NumberInput(min=0, max=99))
index = SelectField()
character = SelectField(coerce=parse_uuid)
character = SelectField()
def __init__(self, **kwargs):
lexicon: Lexicon = current_lexicon
self.index.choices = [i.pattern for i in lexicon.indices]
self.character.choices = [(c.public_id, c.name) for c in lexicon.characters]
super().__init__(**kwargs)
class IndexAssignmentsForm(FlaskForm):
@ -114,15 +122,3 @@ class IndexAssignmentsForm(FlaskForm):
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

View File

@ -137,7 +137,7 @@
</details>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<table id="index-definition-table2">
<table id="index-definition-table">
<tr>
<th>Turn</th>
<th>Index</th>
@ -166,12 +166,63 @@
{% endif %}
{% if page_name == "publish" %}
<h3>Turn Publishing</h3>
<h3>Turn Publishing</h3>
{# <h3>Turn Publishing</h3>
<p>
{{ form.publishDeadlines(autocomplete="off") }}
{{ form.publishDeadlines.label }}<br>
{{ flag_setting(form.publishAsap) }}
{% for error in form.publishDeadlines.errors %}
<span style="color: #ff0000">{{ error }}</span><br>
{% endfor %}
{{ flag_setting(form.publishBlockOnReady) }}
{{ number_setting(form.publishQuorum) }}
<p>
{{ form.turnAssignment.label }}:<br>
{{ form.turnAssignment(class_="fullwidth", rows=10) }}
Transfer editorial control
</p> #}
{% endif %}
{% if page_name == "article" %}
<h3>Article Requirements</h3>
<h3>Article Requirements</h3>
{# <h3>Article Requirements</h3>
<p>
{{ flag_setting(form.articleCitationAllowSelf) }}
{{ number_setting(form.articleCitationMinExtant)}}
{{ number_setting(form.articleCitationMaxExtant)}}
{{ number_setting(form.articleCitationMinPhantom)}}
{{ number_setting(form.articleCitationMaxPhantom)}}
{{ number_setting(form.articleCitationMinTotal)}}
{{ number_setting(form.articleCitationMaxTotal)}}
{{ number_setting(form.articleCitationMinChars)}}
{{ number_setting(form.articleCitationMaxChars)}}
{{ number_setting(form.articleWordLimitSoft)}}
{{ number_setting(form.articleWordLimitHard)}}
{{ flag_setting(form.articleAddendumAllowed) }}
{{ number_setting(form.articleAddendumMax) }}
</p> #}
{% endif %}
{# <p>
Id: {{ g.lexicon.lid }}<br>
Name: {{ g.lexicon.cfg.name }}<br>
Created: {{ g.lexicon.cfg.time.created|asdate }}<br>
Completed: {{ g.lexicon.cfg.time.completed|asdate }}<br>
Players:
{% for uid in g.lexicon.cfg.join.joined %}
{{ uid|user_attr('username') }}{% if not loop.last %},{% endif %}
{% endfor %}<br>
Characters:
{% for char in g.lexicon.cfg.character.values() %}
{{ char.name }}{% if char.player %}
({{ char.player|user_attr('username') }}){% endif %}
{% if not loop.last %},{% endif %}
{% endfor %}<br>
</p> #}
{% endblock %}
{% set template_content_blocks = [self.main()] %}

View File

@ -1,40 +0,0 @@
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)

View File

@ -10,9 +10,9 @@ from bs4 import BeautifulSoup
from flask.testing import FlaskClient
from sqlalchemy.orm.session import close_all_sessions
from amanuensis.backend import *
from amanuensis.backend import charq, lexiq, memq, userq
from amanuensis.config import AmanuensisConfig
from amanuensis.db import *
from amanuensis.db import DbContext, User, Lexicon, Membership, Character
from amanuensis.server import get_app
@ -122,19 +122,6 @@ class ObjectFactory:
updated_kwargs: dict = {**default_kwargs, **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:
"""Factory function for user test clients."""
return UserClient(self.db, user_id)