Track Ersatz status with flag instead of null character #21

Merged
Jaculabilis merged 1 commits from tvb/ersatz into develop 2021-09-15 07:24:36 +00:00
4 changed files with 25 additions and 66 deletions

View File

@ -2,8 +2,6 @@
Article query interface Article query interface
""" """
from typing import Optional
from sqlalchemy import select from sqlalchemy import select
from amanuensis.db import * from amanuensis.db import *
@ -13,8 +11,8 @@ from amanuensis.errors import ArgumentError, BackendArgumentTypeError
def create( def create(
db: DbContext, db: DbContext,
lexicon_id: int, lexicon_id: int,
user_id: int, character_id: int,
character_id: Optional[int], ersatz: bool = False,
) -> Article: ) -> Article:
""" """
Create a new article in a lexicon. Create a new article in a lexicon.
@ -22,39 +20,21 @@ def create(
# Verify argument types are correct # Verify argument types are correct
if not isinstance(lexicon_id, int): if not isinstance(lexicon_id, int):
raise BackendArgumentTypeError(int, lexicon_id=lexicon_id) raise BackendArgumentTypeError(int, lexicon_id=lexicon_id)
if not isinstance(user_id, int):
raise BackendArgumentTypeError(int, user_id=user_id)
if character_id is not None and not isinstance(character_id, int): if character_id is not None and not isinstance(character_id, int):
raise BackendArgumentTypeError(int, character_id=character_id) raise BackendArgumentTypeError(int, character_id=character_id)
# Check that the user is a member of this lexicon # Check that the character belongs to the lexicon
mem: Membership = db( character: Character = db(
select(Membership) select(Character).where(Character.id == character_id)
.where(Membership.user_id == user_id)
.where(Membership.lexicon_id == lexicon_id)
).scalar_one_or_none() ).scalar_one_or_none()
if not mem: if not character:
raise ArgumentError("User is not a member of lexicon") raise ArgumentError("Character does not exist")
if character.lexicon.id != lexicon_id:
# If the character id is provided, check that the user owns the character raise ArgumentError("Character belongs to the wrong lexicon")
# and the character belongs to the lexicon signature = character.signature if not ersatz else "~Ersatz Scrivener"
if character_id is not None:
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.user.id != user_id:
raise ArgumentError("Character is owned by the wrong player")
if character.lexicon.id != lexicon_id:
raise ArgumentError("Character belongs to the wrong lexicon")
signature = character.signature
else:
signature = "~Ersatz Scrivener"
new_article = Article( new_article = Article(
lexicon_id=lexicon_id, lexicon_id=lexicon_id,
user_id=user_id,
character_id=character_id, character_id=character_id,
title="Article title", title="Article title",
body=f"\n\n{signature}", body=f"\n\n{signature}",

View File

@ -83,7 +83,6 @@ def get_for_lexicon(db: DbContext, lexicon_id: int) -> Sequence[ArticleIndex]:
).scalars() ).scalars()
def update(db: DbContext, lexicon_id: int, indices: Sequence[ArticleIndex]) -> None: def update(db: DbContext, lexicon_id: int, indices: Sequence[ArticleIndex]) -> None:
""" """
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.

View File

@ -99,7 +99,6 @@ class User(ModelBase):
memberships = relationship("Membership", back_populates="user") memberships = relationship("Membership", back_populates="user")
characters = relationship("Character", back_populates="user") characters = relationship("Character", back_populates="user")
articles = relationship("Article", back_populates="user")
posts = relationship("Post", back_populates="user") posts = relationship("Post", back_populates="user")
######################### #########################
@ -393,11 +392,7 @@ class Article(ModelBase):
lexicon_id = Column(Integer, ForeignKey("lexicon.id"), nullable=False) lexicon_id = Column(Integer, ForeignKey("lexicon.id"), nullable=False)
# The character who is the author of this article # The character who is the author of this article
# If this is NULL, the article is written by Ersatz Scrivener character_id = Column(Integer, ForeignKey("character.id"), nullable=False)
character_id = Column(Integer, ForeignKey("character.id"), nullable=True)
# The user who owns this article
user_id = Column(Integer, ForeignKey("user.id"), nullable=False)
# The article to which this is an addendum # The article to which this is an addendum
addendum_to = Column(Integer, ForeignKey("article.id"), nullable=True) addendum_to = Column(Integer, ForeignKey("article.id"), nullable=True)
@ -416,6 +411,9 @@ class Article(ModelBase):
# The number of times the article has been submitted # The number of times the article has been submitted
submit_nonce = Column(Integer, nullable=False, default=0) submit_nonce = Column(Integer, nullable=False, default=0)
# Whether the article is an Ersatz Scrivener article
ersatz = Column(Boolean, nullable=False, default=False)
#################### ####################
# History tracking # # History tracking #
#################### ####################
@ -449,7 +447,6 @@ class Article(ModelBase):
lexicon = relationship("Lexicon", back_populates="articles") lexicon = relationship("Lexicon", back_populates="articles")
character = relationship("Character", back_populates="articles") character = relationship("Character", back_populates="articles")
user = relationship("User", back_populates="articles")
addenda = relationship("Article", backref=backref("parent", remote_side=[id])) addenda = relationship("Article", backref=backref("parent", remote_side=[id]))

View File

@ -16,43 +16,26 @@ def test_create_article(db: DbContext, make: ObjectFactory):
lexicon1: Lexicon = make.lexicon() lexicon1: Lexicon = make.lexicon()
make.membership(user_id=user1.id, lexicon_id=lexicon1.id) make.membership(user_id=user1.id, lexicon_id=lexicon1.id)
make.membership(user_id=user2.id, lexicon_id=lexicon1.id) make.membership(user_id=user2.id, lexicon_id=lexicon1.id)
char1_1: Character = make.character(lexicon_id=lexicon1.id, user_id=user1.id) char_l1_u1: Character = make.character(lexicon_id=lexicon1.id, user_id=user1.id)
char1_2: Character = make.character(lexicon_id=lexicon1.id, user_id=user2.id) char_l1_u2: Character = make.character(lexicon_id=lexicon1.id, user_id=user2.id)
# Create a lexicon that only one user is in # Create a lexicon that only one user is in
lexicon2: Lexicon = make.lexicon() lexicon2: Lexicon = make.lexicon()
make.membership(user_id=user2.id, lexicon_id=lexicon2.id) make.membership(user_id=user2.id, lexicon_id=lexicon2.id)
char2_2: Character = make.character(lexicon_id=lexicon2.id, user_id=user2.id) char_l2_u2: Character = make.character(lexicon_id=lexicon2.id, user_id=user2.id)
# User cannot create article for another user's character # Characters can't own articles in other lexicons
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
artiq.create(db, lexicon1.id, user1.id, char1_2.id) artiq.create(db, lexicon1.id, char_l2_u2.id)
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
artiq.create(db, lexicon1.id, user2.id, char1_1.id) artiq.create(db, lexicon2.id, char_l1_u1.id)
# User cannot create article for their character in the wrong lexicon
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
artiq.create(db, lexicon1.id, user2.id, char2_2.id) artiq.create(db, lexicon2.id, char_l1_u2.id)
with pytest.raises(ArgumentError):
artiq.create(db, lexicon2.id, user2.id, char1_2.id)
# User cannot create article in a lexicon they aren't in
with pytest.raises(ArgumentError):
artiq.create(db, lexicon2.id, user1.id, char1_1.id)
# User cannot create anonymous articles in a lexicon they aren't in
with pytest.raises(ArgumentError):
artiq.create(db, lexicon2.id, user1.id, character_id=None)
# Users can create character-owned articles # Users can create character-owned articles
assert artiq.create(db, lexicon1.id, user1.id, char1_1.id) assert artiq.create(db, lexicon1.id, char_l1_u1.id)
assert artiq.create(db, lexicon1.id, user2.id, char1_2.id) assert artiq.create(db, lexicon1.id, char_l1_u2.id)
assert artiq.create(db, lexicon2.id, user2.id, char2_2.id) assert artiq.create(db, lexicon2.id, char_l2_u2.id)
# Users can create anonymous articles
assert artiq.create(db, lexicon1.id, user1.id, character_id=None)
assert artiq.create(db, lexicon1.id, user2.id, character_id=None)
assert artiq.create(db, lexicon2.id, user2.id, character_id=None)
def test_article_update_ts(db: DbContext, make: ObjectFactory): def test_article_update_ts(db: DbContext, make: ObjectFactory):
@ -61,7 +44,7 @@ def test_article_update_ts(db: DbContext, make: ObjectFactory):
lexicon: Lexicon = make.lexicon() lexicon: Lexicon = make.lexicon()
make.membership(user_id=user.id, lexicon_id=lexicon.id) make.membership(user_id=user.id, lexicon_id=lexicon.id)
char: Character = make.character(lexicon_id=lexicon.id, user_id=user.id) char: Character = make.character(lexicon_id=lexicon.id, user_id=user.id)
article = artiq.create(db, lexicon.id, user.id, char.id) article = artiq.create(db, lexicon.id, char.id)
created = article.last_updated created = article.last_updated
time.sleep(1) # The update timestamp has only second-level precision time.sleep(1) # The update timestamp has only second-level precision
article.title = "New title, who dis" article.title = "New title, who dis"