Improvements on polls support
This commit is contained in:
parent
d67a44bb59
commit
31807233c4
4 changed files with 77 additions and 41 deletions
13
app/admin.py
13
app/admin.py
|
@ -281,7 +281,7 @@ async def admin_inbox(
|
||||||
) -> templates.TemplateResponse:
|
) -> templates.TemplateResponse:
|
||||||
where = [
|
where = [
|
||||||
models.InboxObject.ap_type.not_in(
|
models.InboxObject.ap_type.not_in(
|
||||||
["Accept", "Delete", "Create", "Update", "Undo", "Read"]
|
["Accept", "Delete", "Create", "Update", "Undo", "Read", "Add", "Remove"]
|
||||||
),
|
),
|
||||||
models.InboxObject.is_deleted.is_(False),
|
models.InboxObject.is_deleted.is_(False),
|
||||||
]
|
]
|
||||||
|
@ -695,12 +695,11 @@ async def admin_actions_vote(
|
||||||
form_data = await request.form()
|
form_data = await request.form()
|
||||||
names = form_data.getlist("name")
|
names = form_data.getlist("name")
|
||||||
logger.info(f"{names=}")
|
logger.info(f"{names=}")
|
||||||
for name in names:
|
await boxes.send_vote(
|
||||||
await boxes.send_vote(
|
db_session,
|
||||||
db_session,
|
in_reply_to=in_reply_to,
|
||||||
in_reply_to=in_reply_to,
|
names=names,
|
||||||
name=name,
|
)
|
||||||
)
|
|
||||||
return RedirectResponse(redirect_url, status_code=302)
|
return RedirectResponse(redirect_url, status_code=302)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ from app.actor import LOCAL_ACTOR
|
||||||
from app.actor import Actor
|
from app.actor import Actor
|
||||||
from app.actor import RemoteActor
|
from app.actor import RemoteActor
|
||||||
from app.media import proxied_media_url
|
from app.media import proxied_media_url
|
||||||
|
from app.utils.datetime import now
|
||||||
from app.utils.datetime import parse_isoformat
|
from app.utils.datetime import parse_isoformat
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,6 +191,20 @@ class Object:
|
||||||
def has_ld_signature(self) -> bool:
|
def has_ld_signature(self) -> bool:
|
||||||
return bool(self.ap_object.get("signature"))
|
return bool(self.ap_object.get("signature"))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_poll_ended(self) -> bool:
|
||||||
|
if "endTime" in self.ap_object:
|
||||||
|
return now() > parse_isoformat(self.ap_object["endTime"])
|
||||||
|
return False
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def poll_items(self) -> list[ap.RawObject] | None:
|
||||||
|
return self.ap_object.get("oneOf") or self.ap_object.get("anyOf")
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def is_one_of_poll(self) -> bool:
|
||||||
|
return bool(self.ap_object.get("oneOf"))
|
||||||
|
|
||||||
|
|
||||||
def _to_camel(string: str) -> str:
|
def _to_camel(string: str) -> str:
|
||||||
cased = "".join(word.capitalize() for word in string.split("_"))
|
cased = "".join(word.capitalize() for word in string.split("_"))
|
||||||
|
|
54
app/boxes.py
54
app/boxes.py
|
@ -401,42 +401,48 @@ async def send_create(
|
||||||
async def send_vote(
|
async def send_vote(
|
||||||
db_session: AsyncSession,
|
db_session: AsyncSession,
|
||||||
in_reply_to: str,
|
in_reply_to: str,
|
||||||
name: str,
|
names: list[str],
|
||||||
) -> str:
|
) -> str:
|
||||||
logger.info(f"Send vote {name}")
|
logger.info(f"Send vote {names}")
|
||||||
vote_id = allocate_outbox_id()
|
|
||||||
published = now().replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
published = now().replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
||||||
|
|
||||||
in_reply_to_object = await get_anybox_object_by_ap_id(db_session, in_reply_to)
|
in_reply_to_object = await get_inbox_object_by_ap_id(db_session, in_reply_to)
|
||||||
if not in_reply_to_object:
|
if not in_reply_to_object:
|
||||||
raise ValueError(f"Invalid in reply to {in_reply_to=}")
|
raise ValueError(f"Invalid in reply to {in_reply_to=}")
|
||||||
if not in_reply_to_object.ap_context:
|
if not in_reply_to_object.ap_context:
|
||||||
raise ValueError("Object has no context")
|
raise ValueError("Object has no context")
|
||||||
context = in_reply_to_object.ap_context
|
context = in_reply_to_object.ap_context
|
||||||
|
|
||||||
|
# TODO: ensure the name are valid?
|
||||||
|
|
||||||
|
# Save the answers
|
||||||
|
in_reply_to_object.voted_for_answers = names
|
||||||
|
|
||||||
to = [in_reply_to_object.actor.ap_id]
|
to = [in_reply_to_object.actor.ap_id]
|
||||||
|
|
||||||
note = {
|
for name in names:
|
||||||
"@context": ap.AS_EXTENDED_CTX,
|
vote_id = allocate_outbox_id()
|
||||||
"type": "Note",
|
note = {
|
||||||
"id": outbox_object_id(vote_id),
|
"@context": ap.AS_EXTENDED_CTX,
|
||||||
"attributedTo": ID,
|
"type": "Note",
|
||||||
"name": name,
|
"id": outbox_object_id(vote_id),
|
||||||
"to": to,
|
"attributedTo": ID,
|
||||||
"cc": [],
|
"name": name,
|
||||||
"published": published,
|
"to": to,
|
||||||
"context": context,
|
"cc": [],
|
||||||
"conversation": context,
|
"published": published,
|
||||||
"url": outbox_object_id(vote_id),
|
"context": context,
|
||||||
"inReplyTo": in_reply_to,
|
"conversation": context,
|
||||||
}
|
"url": outbox_object_id(vote_id),
|
||||||
outbox_object = await save_outbox_object(db_session, vote_id, note)
|
"inReplyTo": in_reply_to,
|
||||||
if not outbox_object.id:
|
}
|
||||||
raise ValueError("Should never happen")
|
outbox_object = await save_outbox_object(db_session, vote_id, note)
|
||||||
|
if not outbox_object.id:
|
||||||
|
raise ValueError("Should never happen")
|
||||||
|
|
||||||
recipients = await _compute_recipients(db_session, note)
|
recipients = await _compute_recipients(db_session, note)
|
||||||
for rcp in recipients:
|
for rcp in recipients:
|
||||||
await new_outgoing_activity(db_session, rcp, outbox_object.id)
|
await new_outgoing_activity(db_session, rcp, outbox_object.id)
|
||||||
|
|
||||||
await db_session.commit()
|
await db_session.commit()
|
||||||
return vote_id
|
return vote_id
|
||||||
|
|
|
@ -292,24 +292,36 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if object.ap_type == "Question" %}
|
{% if object.ap_type == "Question" %}
|
||||||
{% if object.is_from_inbox %}
|
{% if is_admin and object.is_from_inbox and not object.is_poll_ended and not object.voted_for_answers %}
|
||||||
<form action="{{ request.url_for("admin_actions_vote") }}" method="POST">
|
<form action="{{ request.url_for("admin_actions_vote") }}" method="POST">
|
||||||
{{ embed_csrf_token() }}
|
{{ embed_csrf_token() }}
|
||||||
{{ embed_redirect_url(object.permalink_id) }}
|
{{ embed_redirect_url(object.permalink_id) }}
|
||||||
<input type="hidden" name="in_reply_to" value="{{ object.ap_id }}">
|
<input type="hidden" name="in_reply_to" value="{{ object.ap_id }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if object.ap_object.oneOf %}
|
{% if object.poll_items %}
|
||||||
<ul style="list-style-type: none;padding:0;">
|
<ul style="list-style-type: none;padding:0;">
|
||||||
{% set items = object.ap_object.oneOf or object.ap_object.anyOf %}
|
{% for item in object.poll_items %}
|
||||||
{% for item in object.ap_object.oneOf %}
|
|
||||||
<li style="display:block;">
|
<li style="display:block;">
|
||||||
{% set pct = item | poll_item_pct(object.ap_object.votersCount) %}
|
{% set pct = item | poll_item_pct(object.ap_object.votersCount) %}
|
||||||
|
{% set can_vote = is_admin and object.is_from_inbox and not object.is_poll_ended and not object.voted_for_answers %}
|
||||||
<p style="margin:20px 0 10px 0;">
|
<p style="margin:20px 0 10px 0;">
|
||||||
{% if object.is_from_inbox %}
|
{% if can_vote %}
|
||||||
<input type="radio" name="name" value="{{ item.name }}">
|
<input type="{% if object.is_one_of_poll %}radio{% else %}checkbox{% endif %}" name="name" value="{{ item.name }}" id="{{object.permalink_id}}-{{item.name}}">
|
||||||
|
<label for="{{object.permalink_id}}-{{item.name}}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ item.name | clean_html(object) | safe }} <span style="float:right;">{{ pct }}% <span class="muted">({{ item.replies.totalItems }} votes)</span></span>
|
|
||||||
|
{{ item.name | clean_html(object) | safe }}
|
||||||
|
|
||||||
|
{% if object.voted_for_answers and item.name in object.voted_for_answers %}
|
||||||
|
<span class="muted" style="padding-left:20px;">you voted for this answer</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if can_vote %}
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span style="float:right;">{{ pct }}% <span class="muted">({{ item.replies.totalItems }} votes)</span></span>
|
||||||
</p>
|
</p>
|
||||||
<svg class="poll-bar">
|
<svg class="poll-bar">
|
||||||
<line x1="0" y1="10px" x2="{{ pct or 1 }}%" y2="10px" style="stroke-width: 20px;"></line>
|
<line x1="0" y1="10px" x2="{{ pct or 1 }}%" y2="10px" style="stroke-width: 20px;"></line>
|
||||||
|
@ -319,7 +331,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if object.is_from_inbox %}
|
{% if can_vote %}
|
||||||
<p class="form">
|
<p class="form">
|
||||||
<input type="submit" value="vote">
|
<input type="submit" value="vote">
|
||||||
</p>
|
</p>
|
||||||
|
@ -345,8 +357,12 @@
|
||||||
</li>
|
</li>
|
||||||
{% if object.ap_type == "Question" %}
|
{% if object.ap_type == "Question" %}
|
||||||
{% set endAt = object.ap_object.endTime | parse_datetime %}
|
{% set endAt = object.ap_object.endTime | parse_datetime %}
|
||||||
<li>ends <time title="{{ endAt.replace(microsecond=0).isoformat() }}">{{ endAt | timeago }}</time></li>
|
<li>
|
||||||
<li>{{ object.ap_object.votersCount }} voters</li>
|
{% if object.is_poll_ended %}ended{% else %}ends{% endif %} <time title="{{ endAt.replace(microsecond=0).isoformat() }}">{{ endAt | timeago }}</time>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ object.ap_object.votersCount }} voters
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_admin %}
|
{% if is_admin %}
|
||||||
<li>
|
<li>
|
||||||
|
|
Loading…
Reference in a new issue