Semantic HTML and post feed #23
|
@ -2,12 +2,13 @@
|
||||||
Post query interface
|
Post query interface
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional, Sequence, Tuple
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select, update, func
|
||||||
|
from sqlalchemy.sql.sqltypes import DateTime
|
||||||
|
|
||||||
from amanuensis.db import DbContext, Post
|
from amanuensis.db import DbContext, Post
|
||||||
from amanuensis.db.models import Lexicon
|
from amanuensis.db.models import Lexicon, Membership
|
||||||
from amanuensis.errors import ArgumentError, BackendArgumentTypeError
|
from amanuensis.errors import ArgumentError, BackendArgumentTypeError
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,3 +48,42 @@ def create(
|
||||||
db.session.add(new_post)
|
db.session.add(new_post)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return new_post
|
return new_post
|
||||||
|
|
||||||
|
|
||||||
|
def get_posts_for_membership(
|
||||||
|
db: DbContext, membership_id: int
|
||||||
|
) -> Tuple[Sequence[Post], Sequence[Post]]:
|
||||||
|
"""
|
||||||
|
Returns posts for the membership's lexicon, split into posts that
|
||||||
|
are new since the last view and posts that were previously seen.
|
||||||
|
"""
|
||||||
|
# Save the current timestamp, so we don't miss posts created between now
|
||||||
|
# and when we finish looking stuff up
|
||||||
|
now: DateTime = db(select(func.now())).scalar_one()
|
||||||
|
|
||||||
|
# Save the previous last-seen timestamp for splitting new from old posts,
|
||||||
|
# then update the membership with the current time
|
||||||
|
last_seen: DateTime = db(
|
||||||
|
select(Membership.last_post_seen).where(Membership.id == membership_id)
|
||||||
|
).scalar_one()
|
||||||
|
db(
|
||||||
|
update(Membership)
|
||||||
|
.where(Membership.id == membership_id)
|
||||||
|
.values(last_post_seen=now)
|
||||||
|
)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Fetch posts in two groups, new ones after the last-seen time and old ones
|
||||||
|
# If last-seen is null, then just return everything as new
|
||||||
|
new_posts = db(
|
||||||
|
select(Post)
|
||||||
|
.where(last_seen is None or Post.created > last_seen)
|
||||||
|
.order_by(Post.created.desc())
|
||||||
|
).scalars()
|
||||||
|
old_posts = db(
|
||||||
|
select(Post)
|
||||||
|
.where(last_seen is not None and Post.created <= last_seen)
|
||||||
|
.order_by(Post.created.desc())
|
||||||
|
).scalars()
|
||||||
|
|
||||||
|
return new_posts, old_posts
|
||||||
|
|
|
@ -251,7 +251,9 @@ class Lexicon(ModelBase):
|
||||||
indices = 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", order_by="Post.created.desc()")
|
posts = relationship(
|
||||||
|
"Post", back_populates="lexicon", order_by="Post.created.desc()"
|
||||||
|
)
|
||||||
|
|
||||||
#######################
|
#######################
|
||||||
# Derived information #
|
# Derived information #
|
||||||
|
@ -654,7 +656,7 @@ class Post(ModelBase):
|
||||||
################
|
################
|
||||||
|
|
||||||
# The timestamp the post was created
|
# The timestamp the post was created
|
||||||
created = Column(DateTime, nullable=False, server_default=func.now())
|
created = Column(DateTime, nullable=False, server_default=func.utcnow())
|
||||||
|
|
||||||
# The body of the post
|
# The body of the post
|
||||||
body = Column(Text, nullable=False)
|
body = Column(Text, nullable=False)
|
||||||
|
|
|
@ -215,6 +215,10 @@ details.setting-help {
|
||||||
#index-definition-table td input[type=number] {
|
#index-definition-table td input[type=number] {
|
||||||
width: 4em;
|
width: 4em;
|
||||||
}
|
}
|
||||||
|
section.new-post {
|
||||||
|
padding: 9px;
|
||||||
|
border: 1px dashed red;
|
||||||
|
}
|
||||||
p.post-byline {
|
p.post-byline {
|
||||||
color: #606060;
|
color: #606060;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
|
@ -7,7 +7,12 @@ from amanuensis.backend import postq
|
||||||
from amanuensis.db import Post
|
from amanuensis.db import Post
|
||||||
from amanuensis.parser import RenderableVisitor, parse_raw_markdown
|
from amanuensis.parser import RenderableVisitor, parse_raw_markdown
|
||||||
from amanuensis.parser.core import *
|
from amanuensis.parser.core import *
|
||||||
from amanuensis.server.helpers import lexicon_param, player_required, current_lexicon
|
from amanuensis.server.helpers import (
|
||||||
|
lexicon_param,
|
||||||
|
player_required,
|
||||||
|
current_lexicon,
|
||||||
|
current_membership,
|
||||||
|
)
|
||||||
|
|
||||||
from .forms import CreatePostForm
|
from .forms import CreatePostForm
|
||||||
|
|
||||||
|
@ -22,10 +27,10 @@ class PostFormatter(RenderableVisitor):
|
||||||
return span.innertext
|
return span.innertext
|
||||||
|
|
||||||
def LineBreak(self, span: LineBreak):
|
def LineBreak(self, span: LineBreak):
|
||||||
return '<br>'
|
return "<br>"
|
||||||
|
|
||||||
def ParsedArticle(self, span: ParsedArticle):
|
def ParsedArticle(self, span: ParsedArticle):
|
||||||
return '\n'.join(span.recurse(self))
|
return "\n".join(span.recurse(self))
|
||||||
|
|
||||||
def BodyParagraph(self, span: BodyParagraph):
|
def BodyParagraph(self, span: BodyParagraph):
|
||||||
return f'<p>{"".join(span.recurse(self))}</p>'
|
return f'<p>{"".join(span.recurse(self))}</p>'
|
||||||
|
@ -34,7 +39,7 @@ class PostFormatter(RenderableVisitor):
|
||||||
return (
|
return (
|
||||||
'<hr><span class="signature"><p>'
|
'<hr><span class="signature"><p>'
|
||||||
f'{"".join(span.recurse(self))}'
|
f'{"".join(span.recurse(self))}'
|
||||||
'</p></span>'
|
"</p></span>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def BoldSpan(self, span: BoldSpan):
|
def BoldSpan(self, span: BoldSpan):
|
||||||
|
@ -59,11 +64,14 @@ def render_post_body(post: Post) -> str:
|
||||||
@player_required
|
@player_required
|
||||||
def list(lexicon_name):
|
def list(lexicon_name):
|
||||||
form = CreatePostForm()
|
form = CreatePostForm()
|
||||||
|
new_posts, old_posts = postq.get_posts_for_membership(g.db, current_membership.id)
|
||||||
return render_template(
|
return render_template(
|
||||||
"posts.jinja",
|
"posts.jinja",
|
||||||
lexicon_name=lexicon_name,
|
lexicon_name=lexicon_name,
|
||||||
form=form,
|
form=form,
|
||||||
render_post_body=render_post_body,
|
render_post_body=render_post_body,
|
||||||
|
new_posts=new_posts,
|
||||||
|
old_posts=old_posts,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,13 @@
|
||||||
{% set current_page = "characters" %}
|
{% set current_page = "characters" %}
|
||||||
{% block title %}Character | {{ lexicon_title }}{% endblock %}
|
{% block title %}Character | {{ lexicon_title }}{% endblock %}
|
||||||
|
|
||||||
|
{% macro make_post(post, is_new) %}
|
||||||
|
<section{% if is_new %} class="new-post"{% endif %}>
|
||||||
|
<p>{{ render_post_body(post) }}</p>
|
||||||
|
<p class="post-byline">Posted {% if post.user_id %}by {{ post.user.display_name }} {% endif %}at {{ post.created }}</p>
|
||||||
|
</section>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<section>
|
<section>
|
||||||
<form action="" method="post" novalidate>
|
<form action="" method="post" novalidate>
|
||||||
|
@ -11,10 +18,10 @@
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{% for post in current_lexicon.posts %}
|
{% for post in new_posts %}
|
||||||
<section>
|
{{ make_post(post, True) }}
|
||||||
<p>{{ render_post_body(post) }}</p>
|
{% endfor %}
|
||||||
<p class="post-byline">Posted {% if post.user_id %}by {{ post.user.display_name }} {% endif %}at {{ post.created }}</p>
|
{% for post in old_posts %}
|
||||||
</section>
|
{{ make_post(post, False) }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in New Issue