Finish support for multiple answers polls
This commit is contained in:
parent
c125891681
commit
49ffe3ab75
7 changed files with 67 additions and 38 deletions
|
@ -493,11 +493,13 @@ def api_new_question() -> _Response:
|
|||
)
|
||||
}
|
||||
of = _user_api_arg("of")
|
||||
print(of)
|
||||
if of == "anyOf":
|
||||
choices["anyOf"] = answers
|
||||
else:
|
||||
choices["oneOf"] = answers
|
||||
|
||||
print(choices)
|
||||
raw_question = dict(
|
||||
attributedTo=MY_PERSON.id,
|
||||
cc=list(set(cc)),
|
||||
|
|
|
@ -20,7 +20,6 @@ from core.activitypub import SIG_AUTH
|
|||
from core.activitypub import Box
|
||||
from core.activitypub import _actor_hash
|
||||
from core.activitypub import _add_answers_to_question
|
||||
from core.activitypub import no_cache
|
||||
from core.activitypub import post_to_outbox
|
||||
from core.activitypub import update_cached_actor
|
||||
from core.db import update_one_activity
|
||||
|
@ -142,8 +141,7 @@ def task_cache_object() -> _Response:
|
|||
obj = activity.get_object()
|
||||
|
||||
# Refetch the object actor (without cache)
|
||||
with no_cache():
|
||||
obj_actor = ap.fetch_remote_activity(obj.get_actor().id)
|
||||
obj_actor = ap.fetch_remote_activity(obj.get_actor().id, no_cache=True)
|
||||
|
||||
cache = {MetaKey.OBJECT: obj.to_dict(embed=True)}
|
||||
|
||||
|
@ -269,8 +267,7 @@ def task_cache_actor() -> _Response:
|
|||
app.logger.info(f"activity={activity!r}")
|
||||
|
||||
# Reload the actor without caching (in case it got upated)
|
||||
with no_cache():
|
||||
actor = ap.fetch_remote_activity(activity.get_actor().id)
|
||||
actor = ap.fetch_remote_activity(activity.get_actor().id, no_cache=True)
|
||||
|
||||
# Fetch the Open Grah metadata if it's a `Create`
|
||||
if activity.has_type(ap.ActivityType.CREATE):
|
||||
|
|
|
@ -2,12 +2,11 @@ import binascii
|
|||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
import time
|
||||
from datetime import datetime
|
||||
from datetime import timezone
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from urllib.parse import urljoin
|
||||
|
@ -45,22 +44,10 @@ _NewMeta = Dict[str, Any]
|
|||
|
||||
SIG_AUTH = HTTPSigAuth(KEY)
|
||||
|
||||
_ACTIVITY_CACHE_ENABLED = True
|
||||
ACTORS_CACHE = LRUCache(maxsize=256)
|
||||
MY_PERSON = ap.Person(**ME)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def no_cache() -> Iterator[None]:
|
||||
"""Context manager for disabling the "DB cache" when fetching AP activities."""
|
||||
global _ACTIVITY_CACHE_ENABLED
|
||||
_ACTIVITY_CACHE_ENABLED = False
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_ACTIVITY_CACHE_ENABLED = True
|
||||
|
||||
|
||||
def _remove_id(doc: ap.ObjectType) -> ap.ObjectType:
|
||||
"""Helper for removing MongoDB's `_id` field."""
|
||||
doc = doc.copy()
|
||||
|
@ -177,6 +164,7 @@ def post_to_inbox(activity: ap.BaseActivity) -> None:
|
|||
return
|
||||
|
||||
save(Box.INBOX, activity)
|
||||
time.sleep(1)
|
||||
logger.info(f"spawning tasks for {activity!r}")
|
||||
if not activity.has_type([ap.ActivityType.DELETE, ap.ActivityType.UPDATE]):
|
||||
Tasks.cache_actor(activity.id)
|
||||
|
@ -202,6 +190,7 @@ def post_to_outbox(activity: ap.BaseActivity) -> str:
|
|||
activity.reset_object_cache()
|
||||
|
||||
save(Box.OUTBOX, activity)
|
||||
time.sleep(5)
|
||||
Tasks.cache_actor(activity.id)
|
||||
Tasks.finish_post_to_outbox(activity.id)
|
||||
return activity.id
|
||||
|
@ -361,8 +350,8 @@ class MicroblogPubBackend(Backend):
|
|||
logger.info(f"dereference {iri} via HTTP")
|
||||
return super().fetch_iri(iri)
|
||||
|
||||
def fetch_iri(self, iri: str, no_cache=False) -> ap.ObjectType:
|
||||
if not no_cache and _ACTIVITY_CACHE_ENABLED:
|
||||
def fetch_iri(self, iri: str, **kwargs: Any) -> ap.ObjectType:
|
||||
if not kwargs.pop("no_cache", False):
|
||||
# Fetch the activity by checking the local DB first
|
||||
data = self._fetch_iri(iri)
|
||||
logger.debug(f"_fetch_iri({iri!r}) == {data!r}")
|
||||
|
@ -397,21 +386,26 @@ class MicroblogPubBackend(Backend):
|
|||
logger.info("invalid choice")
|
||||
return
|
||||
|
||||
# Hash the choice/answer (so we can use it as a key)
|
||||
answer_key = _answer_key(choice)
|
||||
|
||||
is_single_choice = bool(question._data.get("oneOf", []))
|
||||
dup_query = {
|
||||
"activity.object.actor": create.get_actor().id,
|
||||
"meta.answer_to": question.id,
|
||||
**({} if is_single_choice else {"meta.poll_answer_choice": choice}),
|
||||
}
|
||||
|
||||
print(f"dup_q={dup_query}")
|
||||
# Check for duplicate votes
|
||||
if DB.activities.find_one(
|
||||
{
|
||||
"activity.object.actor": create.get_actor().id,
|
||||
"meta.answer_to": question.id,
|
||||
}
|
||||
):
|
||||
if DB.activities.find_one(dup_query):
|
||||
logger.info("duplicate response")
|
||||
return
|
||||
|
||||
# Update the DB
|
||||
answer_key = _answer_key(choice)
|
||||
|
||||
DB.activities.update_one(
|
||||
{"activity.object.id": question.id},
|
||||
{"meta.object_id": question.id},
|
||||
{
|
||||
"$inc": {
|
||||
"meta.question_replies": 1,
|
||||
|
@ -425,6 +419,7 @@ class MicroblogPubBackend(Backend):
|
|||
{
|
||||
"$set": {
|
||||
"meta.answer_to": question.id,
|
||||
"meta.poll_answer_choice": choice,
|
||||
"meta.stream": False,
|
||||
"meta.poll_answer": True,
|
||||
}
|
||||
|
@ -462,7 +457,11 @@ class MicroblogPubBackend(Backend):
|
|||
# Keep track of our own votes
|
||||
DB.activities.update_one(
|
||||
{"activity.object.id": reply.id, "box": "inbox"},
|
||||
{"$set": {"meta.voted_for": create.get_object().name}},
|
||||
{
|
||||
"$set": {
|
||||
f"meta.poll_answers_sent.{_answer_key(create.get_object().name)}": True
|
||||
}
|
||||
},
|
||||
)
|
||||
return None
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ from little_boxes.errors import NotAnActivityError
|
|||
|
||||
import config
|
||||
from core.activitypub import _answer_key
|
||||
from core.activitypub import no_cache
|
||||
from core.activitypub import post_to_outbox
|
||||
from core.activitypub import update_cached_actor
|
||||
from core.db import DB
|
||||
|
@ -93,8 +92,7 @@ def _update_process_inbox(update: ap.Update, new_meta: _NewMeta) -> None:
|
|||
)
|
||||
|
||||
elif obj.has_type(ap.ACTOR_TYPES):
|
||||
with no_cache():
|
||||
actor = ap.fetch_remote_activity(obj.get_actor().id)
|
||||
actor = ap.fetch_remote_activity(obj.get_actor().id, no_cache=True)
|
||||
update_cached_actor(actor)
|
||||
|
||||
else:
|
||||
|
|
|
@ -49,12 +49,10 @@
|
|||
<option value="10080">7 days</option>
|
||||
</select></p>
|
||||
|
||||
<input type="hidden" name="of" value="oneOf" />
|
||||
<!--
|
||||
<p><select name="of">
|
||||
<option value="oneOf">Single choice</option>
|
||||
<option value="anyOf">Multiple choices</option>
|
||||
</select></p>-->
|
||||
</select></p>
|
||||
|
||||
{% for i in range(4) %}
|
||||
<p><input type="text" name="answer{{i}}" placeholder="Answer #{{i+1}}"></p>
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
{% set pct = cnt * 100.0 / total_votes %}
|
||||
{% endif %}
|
||||
<li class="answer">
|
||||
{% if session.logged_in and not meta.voted_for and not (real_end_time | gtnow) and not (obj.id | is_from_outbox) %}
|
||||
{% if session.logged_in and not meta.poll_answers_sent and not (real_end_time | gtnow) and not (obj.id | is_from_outbox) %}
|
||||
<span><form action="/api/vote" class="action-form" method="POST">
|
||||
<input type="hidden" name="redirect" value="{{ redir }}">
|
||||
<input type="hidden" name="id" value="{{ obj.id }}">
|
||||
|
@ -100,10 +100,40 @@
|
|||
<span class="answer-bar color-menu-background" style="width:{{pct}}%;"></span>
|
||||
<span class="answer-text">
|
||||
<span>{{ '%0.0f'| format(pct) }}%</span>
|
||||
{{ oneOf.name }} {% if oneOf.name == meta.voted_for %}(your vote){% endif %}
|
||||
{{ oneOf.name }} {% if oneOf.name | poll_answer_key in meta.poll_answers_sent %}(your vote){% endif %}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if obj.anyOf %}
|
||||
|
||||
{% for anyOf in obj.anyOf %}
|
||||
{% set pct = 0 %}
|
||||
{% if total_votes > 0 %}
|
||||
{% set cnt = anyOf.name | get_answer_count(obj, meta) %}
|
||||
{% set pct = cnt * 100.0 / total_votes %}
|
||||
{% endif %}
|
||||
<li class="answer">
|
||||
{% set already_voted = anyOf.name | poll_answer_key in meta.poll_answers_sent %}
|
||||
{% if session.logged_in and not already_voted and not (real_end_time | gtnow) and not (obj.id | is_from_outbox) %}
|
||||
<span><form action="/api/vote" class="action-form" method="POST">
|
||||
<input type="hidden" name="redirect" value="{{ redir }}">
|
||||
<input type="hidden" name="id" value="{{ obj.id }}">
|
||||
<input type="hidden" name="choice" value="{{ anyOf.name }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="bar-item">vote</button>
|
||||
</form></span>
|
||||
{% elif session.logged_in and already_voted %}
|
||||
<span style="position:relative;top:5px;height:10px;width:50px;display:inline-block;"></span>
|
||||
{% endif %}
|
||||
<span class="answer-bar color-menu-background" style="width:{{pct}}%;"></span>
|
||||
<span class="answer-text">
|
||||
<span>{{ '%0.0f'| format(pct) }}%</span>
|
||||
{{ anyOf.name }} {% if anyOf.name | poll_answer_key in meta.poll_answers_sent %}(your vote){% endif %}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
</ul>
|
||||
<p><small>
|
||||
{% if real_end_time | gtnow %}
|
||||
|
|
|
@ -206,6 +206,11 @@ def get_actor(url):
|
|||
return f"Error<{url}/{exc!r}>"
|
||||
|
||||
|
||||
@filters.app_template_filter()
|
||||
def poll_answer_key(choice: str) -> str:
|
||||
return _answer_key(choice)
|
||||
|
||||
|
||||
@filters.app_template_filter()
|
||||
def get_answer_count(choice, obj, meta):
|
||||
count_from_meta = meta.get("question_answers", {}).get(_answer_key(choice), 0)
|
||||
|
|
Loading…
Reference in a new issue