Skip to content

Commit

Permalink
Merge branch 'develop' into bugfix/restart-experiment
Browse files Browse the repository at this point in the history
  • Loading branch information
BeritJanssen committed Nov 8, 2024
2 parents 21d9806 + 5a833c0 commit a8edec2
Show file tree
Hide file tree
Showing 17 changed files with 1,966 additions and 8,824 deletions.
68 changes: 45 additions & 23 deletions backend/experiment/actions/final.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from django.utils.translation import gettext_lazy as _

from experiment.serializers import serialize_social_media_config
from session.models import Session

from .base_action import BaseAction


Expand All @@ -21,18 +24,27 @@ class Final(BaseAction): # pylint: disable=too-few-public-methods
'DIAMOND': {'text': _('diamond'), 'class': 'diamond'}
}

def __init__(self, session, title=_("Final score"), final_text=None,
button=None, points=None, rank=None, social=None,
show_profile_link=False, show_participant_link=False,
show_participant_id_only=False, feedback_info=None, total_score=None, logo=None
):
def __init__(
self,
session: Session,
title: str = _("Final score"),
final_text: str = None,
button: dict = None,
points: str = None,
rank: str = None,
show_profile_link: bool = False,
show_participant_link: bool = False,
show_participant_id_only: bool = False,
feedback_info: dict = None,
total_score: float = None,
logo: dict = None,
):

self.session = session
self.title = title
self.final_text = final_text
self.button = button
self.rank = rank
self.social = social
self.show_profile_link = show_profile_link
self.show_participant_link = show_participant_link
self.show_participant_id_only = show_participant_id_only
Expand All @@ -50,22 +62,32 @@ def __init__(self, session, title=_("Final score"), final_text=None,
def action(self):
"""Get data for final action"""
return {
'view': self.ID,
'score': self.total_score,
'rank': self.rank,
'final_text': self.final_text,
'button': self.button,
'points': self.points,
'action_texts': {
'play_again': _('Play again'),
'profile': _('My profile'),
'all_experiments': _('All experiments')
"view": self.ID,
"score": self.total_score,
"rank": self.rank,
"final_text": self.final_text,
"button": self.button,
"points": self.points,
"action_texts": {
"play_again": _("Play again"),
"profile": _("My profile"),
"all_experiments": _("All experiments"),
},
'title': self.title,
'social': self.social,
'show_profile_link': self.show_profile_link,
'show_participant_link': self.show_participant_link,
'feedback_info': self.feedback_info,
'participant_id_only': self.show_participant_id_only,
'logo': self.logo,
"title": self.title,
"social": self.get_social_media_config(self.session),
"show_profile_link": self.show_profile_link,
"show_participant_link": self.show_participant_link,
"feedback_info": self.feedback_info,
"participant_id_only": self.show_participant_id_only,
"logo": self.logo,
}

def get_social_media_config(self, session: Session) -> dict:
experiment = session.block.phase.experiment
if (
hasattr(experiment, "social_media_config")
and experiment.social_media_config
):
return serialize_social_media_config(
experiment.social_media_config, session.total_score()
)
75 changes: 75 additions & 0 deletions backend/experiment/actions/tests/test_actions_final.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from django.test import TestCase
from django.utils.translation import activate

from experiment.models import (
Block,
BlockTranslatedContent,
Experiment,
ExperimentTranslatedContent,
Phase,
SocialMediaConfig,
)
from participant.models import Participant
from result.models import Result
from session.models import Session

from experiment.actions import Final


class FinalTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.experiment = Experiment.objects.create(
slug="final_countdown",
)
ExperimentTranslatedContent.objects.create(
experiment=cls.experiment, name="Final Countdown", language="en", index=1
)
ExperimentTranslatedContent.objects.create(
experiment=cls.experiment,
name="Laatste Telaf",
social_media_message="Ik heb {points} punten gescoord op {experiment_name}. Kan jij het beter?",
language="nl",
index=0,
)
phase = Phase.objects.create(experiment=cls.experiment)
block = Block.objects.create(phase=phase, rules="HOOKED", rounds=6)
BlockTranslatedContent.objects.create(
block=block, name="Test block", language="en"
)
participant = Participant.objects.create()
cls.session = Session.objects.create(block=block, participant=participant)
Result.objects.create(session=cls.session, score=28)
Result.objects.create(session=cls.session, score=14)

def test_final_action_without_social(self):
final = Final(self.session)
serialized = final.action()
self.assertEqual(serialized.get("title"), "Final score")
self.assertIsNone(serialized.get("social"))

def test_final_action_with_social(self):
SocialMediaConfig.objects.create(
experiment=self.experiment,
channels=["Facebook"],
tags=["amazing"],
url="example.com",
)
final = Final(self.session)
serialized = final.action()
social_info = serialized.get("social")
self.assertIsNotNone(social_info)
self.assertEqual(social_info.get("channels"), ["Facebook"])
self.assertEqual(social_info.get("url"), "example.com")
self.assertEqual(social_info.get("tags"), ["amazing"])
self.assertEqual(
social_info.get("content"),
"Ik heb 42.0 punten gescoord op Laatste Telaf. Kan jij het beter?",
)
activate("en")
final = Final(self.session)
serialized = final.action()
social_info = serialized.get("social")
self.assertEqual(
social_info.get("content"), "I scored 42.0 points in Final Countdown!"
)
56 changes: 56 additions & 0 deletions backend/experiment/migrations/0059_add_social_media_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Generated by Django 4.2.16 on 2024-10-28 16:06

from django.db import migrations
from django.conf import settings
from django.utils import translation


def add_social_media_config(apps, schema_editor):
"""add information from rules files to database"""
Block = apps.get_model("experiment", "Block")
ExperimentTranslatedContent = apps.get_model(
"experiment", "ExperimentTranslatedContent"
)
SocialMediaConfig = apps.get_model("experiment", "SocialMediaConfig")
blocks = Block.objects.all()
for block in blocks:
if block.rules in [
"HOOKED",
"EUROVISION_2020",
"KUIPER_2020",
"HUANG_2022",
"MUSICAL_PREFERENCES",
"MATCHING_PAIRS",
]:
channels = ["facebook", "twitter"]
if block.rules == "MATCHING_PAIRS":
channels.append("clipboard")
elif block.rules == "MUSICAL_PREFERENCES":
channels = ["weibo", "share"]
if not SocialMediaConfig.objects.filter(
experiment=block.phase.experiment
).exists():
SocialMediaConfig.objects.create(
experiment=block.phase.experiment,
tags=["amsterdammusiclab", "citizenscience"],
url=f"{settings.RELOAD_PARTICIPANT_TARGET}/{block.slug}",
channels=channels,
)
if not ExperimentTranslatedContent.objects.filter(
experiment=block.phase.experiment
).exists():
ExperimentTranslatedContent.objects.create(
experiment=block.phase.experiment,
language="en",
name=block.phase.experiment.slug,
)

class Migration(migrations.Migration):

dependencies = [
("experiment", "0058_remove_socialmediaconfig_content_and_more"),
]

operations = [
migrations.RunPython(add_social_media_config, migrations.RunPython.noop)
]
23 changes: 15 additions & 8 deletions backend/experiment/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class Experiment(models.Model):
slug (str): Slug
translated_content (Queryset[ExperimentTranslatedContent]): Translated content
theme_config (theme.ThemeConfig): ThemeConfig instance
dashboard (bool): Show dashboard?
active (bool): Set experiment active
social_media_config (SocialMediaConfig): SocialMediaConfig instance
phases (Queryset[Phase]): Queryset of Phase instances
Expand Down Expand Up @@ -155,7 +154,7 @@ class Phase(models.Model):
experiment (Experiment): Instance of an Experiment
index (int): Index of the phase
dashboard (bool): Should the dashbopard be displayed for this phase?
randomize (bool): Should the block of this phase be randomized?
randomize (bool): Should the blocks of this phase be randomized?
"""

experiment = models.ForeignKey(Experiment, on_delete=models.CASCADE, related_name="phases")
Expand Down Expand Up @@ -494,6 +493,7 @@ class ExperimentTranslatedContent(models.Model):
description (str): Description
consent (FileField): Consent text markdown or html
about_content (str): About text
social_media_message (str): Message to post with on social media. Can contain {points} and {experiment_name} placeholders
"""

experiment = models.ForeignKey(Experiment, on_delete=models.CASCADE, related_name="translated_content")
Expand Down Expand Up @@ -555,7 +555,6 @@ class SocialMediaConfig(models.Model):
experiment (Experiment): Experiment instance
tags (list[str]): Tags
url (str): Url to be shared
content (str): Shared text
channels (list[str]): Social media channel
"""

Expand Down Expand Up @@ -584,12 +583,12 @@ class SocialMediaConfig(models.Model):
help_text=_("Selected social media channels for sharing"),
)

def get_content(self, score: int | None = None, experiment_name: str | None = None) -> str:
def get_content(self, score: float) -> str:
"""Get social media share content
Args:
score: Score
experiment_name: Block name
experiment_name: Experiment name
Returns:
Social media shared text
Expand All @@ -599,9 +598,12 @@ def get_content(self, score: int | None = None, experiment_name: str | None = No
"""
translated_content = self.experiment.get_current_content()
social_message = translated_content.social_media_message
experiment_name = translated_content.name

if social_message:
has_placeholders = "{points}" in social_message and "{experiment_name}" in social_message
has_placeholders = (
"{points}" in social_message and "{experiment_name}" in social_message
)

if not has_placeholders:
return social_message
Expand All @@ -612,9 +614,14 @@ def get_content(self, score: int | None = None, experiment_name: str | None = No
return social_message.format(points=score, experiment_name=experiment_name)

if score is None or experiment_name is None:
raise ValueError("score and experiment_name are required when no social media message is provided")
raise ValueError(
"score and name are required when no social media message is provided"
)

return _("I scored {points} points in {experiment_name}").format(score=score, experiment_name=experiment_name)
return _("I scored %(score)d points in %(experiment_name)s") % {
"score": score,
"experiment_name": experiment_name,
}

def __str__(self):
fallback_content = self.experiment.get_fallback_content()
Expand Down
24 changes: 0 additions & 24 deletions backend/experiment/rules/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,30 +154,6 @@ def get_open_questions(self, session, randomize=False, cutoff_index=None) -> Uni
)
return trials

def social_media_info(self, session: Session):
"""
⚠️ Note: You can use this method to customize the social media message for a Final action in a block,
but the content will come from the experiment's social media config model
"""

block = session.block

current_url = f"{settings.RELOAD_PARTICIPANT_TARGET}/{block.slug}"
score = session.final_score
experiment = block.phase.experiment
experiment_name = experiment.get_current_content().name
social_media_config = experiment.social_media_config
tags = social_media_config.tags if social_media_config.tags else ["amsterdammusiclab", "citizenscience"]
url = social_media_config.url or current_url
message = social_media_config.get_content(score, experiment_name)

return {
"channels": social_media_config.channels or ["facebook", "twitter"],
"content": message,
"url": url,
"tags": tags,
}

def validate_playlist(self, playlist: None):
errors = []
# Common validations across blocks
Expand Down
1 change: 0 additions & 1 deletion backend/experiment/rules/hooked.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ def next_round(self, session: Session):
session=session,
final_text=self.final_score_message(session),
rank=self.rank(session),
social=self.social_media_info(session),
show_profile_link=True,
button={
"text": _("Play again"),
Expand Down
3 changes: 0 additions & 3 deletions backend/experiment/rules/matching_pairs.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,12 @@ def next_round(self, session):
return actions
else:
# final score saves the result from the cleared board into account
social_info = self.social_media_info(session)
social_info["channels"].append("clipboard")
score = Final(
session,
title="Score",
final_text="Can you score higher than your friends and family? Share and let them try!",
button={"text": "Play again", "link": self.get_play_again_url(session)},
rank=self.rank(session, exclude_unfinished=False),
social=social_info,
feedback_info=self.feedback_info(),
)
return [score]
Expand Down
6 changes: 3 additions & 3 deletions backend/experiment/rules/musical_preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,13 +238,13 @@ def calculate_score(self, result, data):

def get_final_view(self, session, top_participant, known_songs, n_songs, top_all):
# finalize block
social_info = self.social_media_info(session)
view = Final(
session,
title=_("End"),
final_text=_("Thank you for your participation and contribution to science!"),
final_text=_(
"Thank you for your participation and contribution to science!"
),
feedback_info=self.feedback_info(),
social=social_info,
)
return view

Expand Down
Loading

0 comments on commit a8edec2

Please sign in to comment.