diff --git a/backend/experiment/actions/consent.py b/backend/experiment/actions/consent.py index e027d5a08..732913c45 100644 --- a/backend/experiment/actions/consent.py +++ b/backend/experiment/actions/consent.py @@ -38,7 +38,7 @@ def render_html_or_markdown(dry_text: str, render_format: str) -> str: class Consent(BaseAction): # pylint: disable=too-few-public-methods """ Provide data for a view that ask consent for using the experiment data - - text: Uploaded file via experiment.consent (fileField) + - text: Uploaded file via block.consent (fileField) - title: The title to be displayed - confirm: The text on the confirm button - deny: The text on the deny button @@ -49,7 +49,7 @@ class Consent(BaseAction): # pylint: disable=too-few-public-methods Relates to client component: Consent.js """ - # default consent text, that can be used for multiple experiments + # default consent text, that can be used for multiple blocks ID = 'CONSENT' default_text = "Lorem ipsum dolor sit amet, nec te atqui scribentur. Diam \ @@ -62,11 +62,11 @@ class Consent(BaseAction): # pylint: disable=too-few-public-methods contentiones, vix ex maiorum denique! Lorem ipsum dolor sit \ amet, nec te atqui scribentur. Diam molestie posidonium te sit, \ ea sea expetenda suscipiantur contentiones." - + def __init__(self, text, title='Informed consent', confirm='I agree', deny='Stop', url=''): # Determine which text to use if text!='': - # Uploaded consent via file field: experiment.consent (High priority) + # Uploaded consent via file field: block.consent (High priority) with text.open('r') as f: dry_text = f.read() render_format = get_render_format(text.url) diff --git a/backend/experiment/actions/score.py b/backend/experiment/actions/score.py index 79930bd1e..26baa64cc 100644 --- a/backend/experiment/actions/score.py +++ b/backend/experiment/actions/score.py @@ -29,7 +29,7 @@ def __init__(self, session, title: str = None, score=None, score_message=None, c self.session = session self.title = title or _('Round {rounds_passed} / {total_rounds}').format( rounds_passed=session.rounds_passed(), - total_rounds=self.session.experiment.rounds + total_rounds=self.session.block.rounds ) self.score = score or session.last_score() self.score_message = score_message or self.default_score_message diff --git a/backend/experiment/actions/tests/test_actions_consent.py b/backend/experiment/actions/tests/test_actions_consent.py index ff85828cf..c45aeb537 100644 --- a/backend/experiment/actions/tests/test_actions_consent.py +++ b/backend/experiment/actions/tests/test_actions_consent.py @@ -1,25 +1,25 @@ from django.test import TestCase from django.core.files.uploadedfile import SimpleUploadedFile -from experiment.models import Experiment +from experiment.models import Block from experiment.actions.consent import Consent class ConsentTest(TestCase): @classmethod - def setUpTestData(cls): - Experiment.objects.create( + def setUpTestData(cls): + Block.objects.create( name='test_md', slug='MARKDOWN', consent=SimpleUploadedFile('consent.md', b'#test', content_type='text/html') ) - Experiment.objects.create( + Block.objects.create( name='test_html', slug='HTML', consent=SimpleUploadedFile('consent.html', b'

test

', content_type='text/html') ) - Experiment.objects.create( + Block.objects.create( name='test_template', slug='TEMPLATE', consent=SimpleUploadedFile('template.html', b'{% load i18n %}{% blocktranslate %}

test

{% endblocktranslate %}', content_type='text/html') @@ -34,16 +34,16 @@ def test_html_rendering(self): self.assertEqual(consent.text, '

test

') def test_uploaded_markdown_rendering(self): - experiment = Experiment.objects.get(slug='MARKDOWN') - consent = Consent(experiment.consent) + block = Block.objects.get(slug='MARKDOWN') + consent = Consent(block.consent) self.assertEqual(consent.text, '

test

') def test_uploaded_html_rendering(self): - experiment = Experiment.objects.get(slug='HTML') - consent = Consent(experiment.consent) + block = Block.objects.get(slug='HTML') + consent = Consent(block.consent) self.assertEqual(consent.text, '

test

') def test_template_language_rendering(self): - experiment = Experiment.objects.get(slug='TEMPLATE') - consent = Consent(experiment.consent) + block = Block.objects.get(slug='TEMPLATE') + consent = Consent(block.consent) self.assertEqual(consent.text, '

test

') diff --git a/backend/experiment/actions/tests/test_actions_score.py b/backend/experiment/actions/tests/test_actions_score.py index f37d76d1c..7c5e3b400 100644 --- a/backend/experiment/actions/tests/test_actions_score.py +++ b/backend/experiment/actions/tests/test_actions_score.py @@ -12,7 +12,7 @@ def setUp(self): self.mock_session.last_song.return_value = "Test Song" self.mock_session.total_score.return_value = 50 self.mock_session.rounds_passed.return_value = 2 - self.mock_session.experiment.rounds = 5 + self.mock_session.block.rounds = 5 def test_initialization_full_parameters(self): score = Score( diff --git a/backend/experiment/actions/tests/test_wrappers.py b/backend/experiment/actions/tests/test_wrappers.py index ccc1ed382..d635e77d7 100644 --- a/backend/experiment/actions/tests/test_wrappers.py +++ b/backend/experiment/actions/tests/test_wrappers.py @@ -3,7 +3,7 @@ from experiment.actions import Trial from experiment.actions.wrappers import song_sync -from experiment.models import Experiment +from experiment.models import Block from participant.models import Participant from section.models import Playlist, Section from session.models import Session @@ -15,9 +15,9 @@ def setUp(self): self.participant = Participant.objects.create() self.section = Section.objects.create( filename='some/audio/file.mp3', playlist=self.playlist) - self.experiment = Experiment.objects.create(name='TestExperiment') + self.block = Block.objects.create(name='TestBlock') self.session = Session.objects.create( - experiment=self.experiment, participant=self.participant, playlist=self.playlist) + block=self.block, participant=self.participant, playlist=self.playlist) def test_song_sync(self): actions = song_sync(self.session, self.section, 'HookedTest') diff --git a/backend/experiment/actions/toontjehoger.py b/backend/experiment/actions/toontjehoger.py index f19f14327..9e733a0d9 100644 --- a/backend/experiment/actions/toontjehoger.py +++ b/backend/experiment/actions/toontjehoger.py @@ -10,7 +10,7 @@ class ToontjeHoger(BaseAction): # pylint: disable=too-few-public-methods ID = "TOONTJEHOGER" - def __init__(self, config, experiments=[]): + def __init__(self, config, blocks=[]): """ ToontjeHoger shows the ToontjeHoger homepage @@ -21,7 +21,7 @@ def __init__(self, config, experiments=[]): - main_button_url - score_label - supporters_intro - experiments: A list of ExperimentData objects + blocks: A list of ExperimentData objects """ self.config = config - self.experiments = [vars(i) for i in self.experiments] + self.blocks = [vars(i) for i in self.blocks] diff --git a/backend/experiment/actions/trial.py b/backend/experiment/actions/trial.py index f4108ae83..5e6b27627 100644 --- a/backend/experiment/actions/trial.py +++ b/backend/experiment/actions/trial.py @@ -66,7 +66,7 @@ def __init__( def action(self): """ - Serialize data for experiment action + Serialize data for a block action """ # Create action diff --git a/backend/experiment/actions/utils.py b/backend/experiment/actions/utils.py index b1994cc97..5a82c6921 100644 --- a/backend/experiment/actions/utils.py +++ b/backend/experiment/actions/utils.py @@ -11,8 +11,8 @@ def final_action_with_optional_button(session, final_text='', title=_('End'), button_text=_('Continue')): - """ given a session, a score message and an optional session dictionary from an experiment series, - return a Final.action, which has a button to continue to the next experiment if series is defined + """ given a session, a score message and an optional session dictionary from an experiment collection, + return a Final.action, which has a button to continue to the next block if series is defined """ collection_slug = session.load_json_data().get(COLLECTION_KEY) @@ -49,7 +49,7 @@ def render_feedback_trivia(feedback, trivia): def get_average_difference(session, num_turnpoints, initial_value): - """ + """ return the average difference in milliseconds participants could hear """ last_turnpoints = get_last_n_turnpoints(session, num_turnpoints) @@ -58,8 +58,8 @@ def get_average_difference(session, num_turnpoints, initial_value): if last_result: return float(last_result.section.song.name) else: - # this cannot happen in DurationDiscrimination style experiments - # for future compatibility, still catch the condition that there may be no results + # this cannot happen in DurationDiscrimination style blocks + # for future compatibility, still catch the condition that there may be no results return initial_value return (sum([int(result.section.song.name) for result in last_turnpoints]) / last_turnpoints.count()) @@ -78,7 +78,7 @@ def get_average_difference_level_based(session, num_turnpoints, initial_value): # no results right after the practice rounds return initial_value # Difference by level starts at initial value (which is level 1, so 20/(2^0)) and then halves for every next level - return sum([initial_value / (2 ** (int(result.section.song.name.split('_')[-1]) - 1)) for result in last_turnpoints]) / last_turnpoints.count() + return sum([initial_value / (2 ** (int(result.section.song.name.split('_')[-1]) - 1)) for result in last_turnpoints]) / last_turnpoints.count() def get_fallback_result(session): diff --git a/backend/experiment/admin.py b/backend/experiment/admin.py index 215b4090e..53ebe2277 100644 --- a/backend/experiment/admin.py +++ b/backend/experiment/admin.py @@ -26,7 +26,7 @@ from question.admin import QuestionSeriesInline from experiment.forms import ( ExperimentCollectionForm, - ExperimentForm, + BlockForm, ExportForm, TemplateForm, SocialMediaConfigForm, @@ -46,9 +46,9 @@ class FeedbackInline(admin.TabularInline): extra = 0 -class ExperimentAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin): - list_display = ('image_preview', 'experiment_name_link', - 'experiment_slug_link', 'rules', +class BlockAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin): + list_display = ('image_preview', 'block_name_link', + 'block_slug_link', 'rules', 'rounds', 'playlist_count', 'session_count', 'active') list_filter = ['active'] @@ -60,7 +60,7 @@ class ExperimentAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin): 'rounds', 'bonus_points', 'playlists', 'consent'] inlines = [QuestionSeriesInline, FeedbackInline] - form = ExperimentForm + form = BlockForm # make playlists fields a list of checkboxes formfield_overrides = { @@ -68,7 +68,7 @@ class ExperimentAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin): } def export(self, request, obj, parent_obj=None): - """Export experiment JSON data as zip archive, force download""" + """Export block JSON data as zip archive, force download""" # Init empty querysets all_results = Result.objects.none() @@ -112,7 +112,7 @@ def export(self, request, obj, parent_obj=None): export.short_description = "Export JSON" def export_csv(self, request, obj, parent_obj=None): - """Export experiment data in CSV, force download""" + """Export block data in CSV, force download""" # Handle export command from intermediate form if '_export' in request.POST: session_keys = [] @@ -130,16 +130,16 @@ def export_csv(self, request, obj, parent_obj=None): response['Content-Disposition'] = 'attachment; filename="{}.csv"'.format( obj.slug) # Get filtered data - experiment_table, fieldnames = obj.export_table( + block_table, fieldnames = obj.export_table( session_keys, result_keys, export_options) fieldnames.sort() writer = csv.DictWriter(response, fieldnames) writer.writeheader() - writer.writerows(experiment_table) + writer.writerows(block_table) return response - # Go back to admin experiment overview + # Go back to admin block overview if '_back' in request.POST: - return redirect('/admin/experiment/experiment') + return redirect('/admin/experiment/block') # Load a template in the export form if '_template' in request.POST: selected_template = request.POST.get('select_template') @@ -170,26 +170,26 @@ def image_preview(self, obj): return mark_safe(f'') return "" - def experiment_name_link(self, obj): - """Generate a link to the experiment's admin change page.""" - url = reverse("admin:experiment_experiment_change", args=[obj.pk]) + def block_name_link(self, obj): + """Generate a link to the block's admin change page.""" + url = reverse("admin:experiment_block_change", args=[obj.pk]) name = obj.name or obj.slug or "No name" return format_html('{}', url, name) - def experiment_slug_link(self, obj): + def block_slug_link(self, obj): dev_mode = settings.DEBUG is True url = f"http://localhost:3000/{obj.slug}" if dev_mode else f"/{obj.slug}" return format_html( - f'{obj.slug} ') + f'{obj.slug} ') # Name the columns image_preview.short_description = "Image" - experiment_name_link.short_description = "Name" - experiment_slug_link.short_description = "Slug" + block_name_link.short_description = "Name" + block_slug_link.short_description = "Slug" -admin.site.register(Block, ExperimentAdmin) +admin.site.register(Block, BlockAdmin) class GroupedBlockInline(admin.StackedInline): @@ -245,7 +245,7 @@ def phases(self, obj): def dashboard(self, request, obj, parent_obj=None): """Open researchers dashboard for a collection""" - all_experiments = obj.associated_experiments() + all_blocks = obj.associated_blocks() all_participants = obj.current_participants() all_sessions = obj.export_sessions() collect_data = { @@ -253,23 +253,23 @@ def dashboard(self, request, obj, parent_obj=None): 'session_count': len(all_sessions) } - experiments = [{ - 'id': exp.id, - 'name': exp.name, - 'started': len(all_sessions.filter(experiment=exp)), + blocks = [{ + 'id': block.id, + 'name': block.name, + 'started': len(all_sessions.filter(block=block)), 'finished': len(all_sessions.filter( - experiment=exp, + block=block, finished_at__isnull=False, )), - 'participant_count': len(exp.current_participants()), - 'participants': exp.current_participants() - } for exp in all_experiments] + 'participant_count': len(block.current_participants()), + 'participants': block.current_participants() + } for block in all_blocks] return render( request, 'collection-dashboard.html', context={'collection': obj, - 'experiments': experiments, + 'blocks': blocks, 'sessions': all_sessions, 'participants': all_participants, 'collect_data': collect_data} @@ -280,7 +280,7 @@ def dashboard(self, request, obj, parent_obj=None): class PhaseAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin): - list_display = ('name_link', 'related_series', 'index', 'dashboard', 'randomize', 'experiments') + list_display = ('name_link', 'related_series', 'index', 'dashboard', 'randomize', 'blocks') fields = ['name', 'series', 'index', 'dashboard', 'randomize'] inlines = [GroupedBlockInline] @@ -295,13 +295,13 @@ def related_series(self, obj): "admin:experiment_experimentcollection_change", args=[obj.series.pk]) return format_html('{}', url, obj.series.name) - def experiments(self, obj): - experiments = GroupedBlock.objects.filter(phase=obj) + def blocks(self, obj): + blocks = GroupedBlock.objects.filter(phase=obj) - if not experiments: - return "No experiments" + if not blocks: + return "No blocks" - return format_html(', '.join([f'{experiment.block.name}' for experiment in experiments])) + return format_html(', '.join([f'{block.block.name}' for block in blocks])) admin.site.register(Phase, PhaseAdmin) diff --git a/backend/experiment/fixtures/experiment.json b/backend/experiment/fixtures/experiment.json index aa1bff627..f80e841c7 100644 --- a/backend/experiment/fixtures/experiment.json +++ b/backend/experiment/fixtures/experiment.json @@ -1,283 +1,283 @@ [ -{ - "model": "experiment.ExperimentCollection", - "pk": 1, - "fields": { - "slug": "RhythmTestSeries", - "first_experiments": [ - "10" - ], - "random_experiments": [ - "7", - "3", - "1", - "2", - "5", - "4", - "6", - "8" - ], - "last_experiments": [ - "9" - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 1, - "fields": { - "name": "DurationDiscrimination", - "slug": "ddi", - "active": true, - "rounds": 100, - "bonus_points": 0, - "rules": "DURATION_DISCRIMINATION", - "language": "", - "playlists": [ - 5 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 2, - "fields": { - "name": "DurationDiscriminationTone", - "slug": "ddit", - "active": true, - "rounds": 100, - "bonus_points": 0, - "rules": "DURATION_DISCRIMINATION_TONE", - "language": "", - "playlists": [ - 4 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 3, - "fields": { - "name": "BeatAlignment", - "slug": "bat", - "active": true, - "rounds": 17, - "bonus_points": 0, - "rules": "BEAT_ALIGNMENT", - "language": "", - "playlists": [ - 7 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 4, - "fields": { - "name": "HBAT-BIT", - "slug": "hbat_bit", - "active": true, - "rounds": 100, - "bonus_points": 0, - "rules": "H_BAT", - "language": "", - "playlists": [ - 9 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 5, - "fields": { - "name": "HBAT-BFIT", - "slug": "hbat_bfit", - "active": true, - "rounds": 100, - "bonus_points": 0, - "rules": "H_BAT_BFIT", - "language": "", - "playlists": [ - 8 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 6, - "fields": { - "name": "HBAT-BST", - "slug": "hbat_bst", - "active": true, - "rounds": 100, - "bonus_points": 0, - "rules": "BST", - "language": "", - "playlists": [ - 10 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 7, - "fields": { - "name": "Anisochrony", - "slug": "anis", - "active": true, - "rounds": 100, - "bonus_points": 0, - "rules": "ANISOCHRONY", - "language": "", - "playlists": [ - 3 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 8, - "fields": { - "name": "RhythmDiscrimination", - "slug": "rhdis", - "active": true, - "rounds": 40, - "bonus_points": 0, - "rules": "RHYTHM_DISCRIMINATION", - "language": "", - "playlists": [ - 6 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 9, - "fields": { - "name": "Rhythm Battery Final", - "slug": "rhythm_outro", - "active": true, - "rounds": 10, - "bonus_points": 0, - "rules": "RHYTHM_BATTERY_FINAL", - "language": "", - "playlists": [ - 3 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 10, - "fields": { - "name": "Rhythm Battery Intro", - "slug": "rhythm_intro", - "active": true, - "rounds": 10, - "bonus_points": 0, - "rules": "RHYTHM_BATTERY_INTRO", - "language": "", - "playlists": [ - 11 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 14, - "fields": { - "name": "Hooked-China", - "slug": "huang_2022", - "active": true, - "rounds": 30, - "bonus_points": 0, - "rules": "HUANG_2022", - "language": "zh", - "playlists": [ - 13, - 2, - 1 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 16, - "fields": { - "name": "Categorization", - "slug": "cat", - "active": true, - "rounds": 10, - "bonus_points": 0, - "rules": "CATEGORIZATION", - "language": "en", - "playlists": [ - 12 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 17, - "fields": { - "name": "TuneTwins", - "slug": "tunetwins", - "active": true, - "rounds": 10, - "bonus_points": 0, - "rules": "MATCHING_PAIRS", - "language": "en", - "playlists": [ - 14 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 18, - "fields": { - "name": "Hooked-Eurovision", - "slug": "eurovision_2021", - "active": true, - "rounds": 30, - "bonus_points": 0, - "rules": "EUROVISION_2020", - "language": "en", - "playlists": [ - 16 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 19, - "fields": { - "name": "Hooked-Christmas", - "slug": "christmas_2020", - "active": true, - "rounds": 30, - "bonus_points": 0, - "rules": "KUIPER_2020", - "language": "en", - "playlists": [ - 17 - ] - } -}, -{ - "model": "experiment.experiment", - "pk": 20, - "fields": { - "name": "ThatsMySong", - "slug": "thats_my_song", - "active": false, - "rounds": 10, - "bonus_points": 0, - "rules": "THATS_MY_SONG", - "language": "", - "playlists": [ - 18 - ] - } -} + { + "model": "experiment.ExperimentCollection", + "pk": 1, + "fields": { + "slug": "RhythmTestSeries", + "first_experiments": [ + "10" + ], + "random_experiments": [ + "7", + "3", + "1", + "2", + "5", + "4", + "6", + "8" + ], + "last_experiments": [ + "9" + ] + } + }, + { + "model": "experiment.Block", + "pk": 1, + "fields": { + "name": "DurationDiscrimination", + "slug": "ddi", + "active": true, + "rounds": 100, + "bonus_points": 0, + "rules": "DURATION_DISCRIMINATION", + "language": "", + "playlists": [ + 5 + ] + } + }, + { + "model": "experiment.Block", + "pk": 2, + "fields": { + "name": "DurationDiscriminationTone", + "slug": "ddit", + "active": true, + "rounds": 100, + "bonus_points": 0, + "rules": "DURATION_DISCRIMINATION_TONE", + "language": "", + "playlists": [ + 4 + ] + } + }, + { + "model": "experiment.Block", + "pk": 3, + "fields": { + "name": "BeatAlignment", + "slug": "bat", + "active": true, + "rounds": 17, + "bonus_points": 0, + "rules": "BEAT_ALIGNMENT", + "language": "", + "playlists": [ + 7 + ] + } + }, + { + "model": "experiment.Block", + "pk": 4, + "fields": { + "name": "HBAT-BIT", + "slug": "hbat_bit", + "active": true, + "rounds": 100, + "bonus_points": 0, + "rules": "H_BAT", + "language": "", + "playlists": [ + 9 + ] + } + }, + { + "model": "experiment.Block", + "pk": 5, + "fields": { + "name": "HBAT-BFIT", + "slug": "hbat_bfit", + "active": true, + "rounds": 100, + "bonus_points": 0, + "rules": "H_BAT_BFIT", + "language": "", + "playlists": [ + 8 + ] + } + }, + { + "model": "experiment.Block", + "pk": 6, + "fields": { + "name": "HBAT-BST", + "slug": "hbat_bst", + "active": true, + "rounds": 100, + "bonus_points": 0, + "rules": "BST", + "language": "", + "playlists": [ + 10 + ] + } + }, + { + "model": "experiment.Block", + "pk": 7, + "fields": { + "name": "Anisochrony", + "slug": "anis", + "active": true, + "rounds": 100, + "bonus_points": 0, + "rules": "ANISOCHRONY", + "language": "", + "playlists": [ + 3 + ] + } + }, + { + "model": "experiment.Block", + "pk": 8, + "fields": { + "name": "RhythmDiscrimination", + "slug": "rhdis", + "active": true, + "rounds": 40, + "bonus_points": 0, + "rules": "RHYTHM_DISCRIMINATION", + "language": "", + "playlists": [ + 6 + ] + } + }, + { + "model": "experiment.Block", + "pk": 9, + "fields": { + "name": "Rhythm Battery Final", + "slug": "rhythm_outro", + "active": true, + "rounds": 10, + "bonus_points": 0, + "rules": "RHYTHM_BATTERY_FINAL", + "language": "", + "playlists": [ + 3 + ] + } + }, + { + "model": "experiment.Block", + "pk": 10, + "fields": { + "name": "Rhythm Battery Intro", + "slug": "rhythm_intro", + "active": true, + "rounds": 10, + "bonus_points": 0, + "rules": "RHYTHM_BATTERY_INTRO", + "language": "", + "playlists": [ + 11 + ] + } + }, + { + "model": "experiment.Block", + "pk": 14, + "fields": { + "name": "Hooked-China", + "slug": "huang_2022", + "active": true, + "rounds": 30, + "bonus_points": 0, + "rules": "HUANG_2022", + "language": "zh", + "playlists": [ + 13, + 2, + 1 + ] + } + }, + { + "model": "experiment.Block", + "pk": 16, + "fields": { + "name": "Categorization", + "slug": "cat", + "active": true, + "rounds": 10, + "bonus_points": 0, + "rules": "CATEGORIZATION", + "language": "en", + "playlists": [ + 12 + ] + } + }, + { + "model": "experiment.Block", + "pk": 17, + "fields": { + "name": "TuneTwins", + "slug": "tunetwins", + "active": true, + "rounds": 10, + "bonus_points": 0, + "rules": "MATCHING_PAIRS", + "language": "en", + "playlists": [ + 14 + ] + } + }, + { + "model": "experiment.Block", + "pk": 18, + "fields": { + "name": "Hooked-Eurovision", + "slug": "eurovision_2021", + "active": true, + "rounds": 30, + "bonus_points": 0, + "rules": "EUROVISION_2020", + "language": "en", + "playlists": [ + 16 + ] + } + }, + { + "model": "experiment.Block", + "pk": 19, + "fields": { + "name": "Hooked-Christmas", + "slug": "christmas_2020", + "active": true, + "rounds": 30, + "bonus_points": 0, + "rules": "KUIPER_2020", + "language": "en", + "playlists": [ + 17 + ] + } + }, + { + "model": "experiment.Block", + "pk": 20, + "fields": { + "name": "ThatsMySong", + "slug": "thats_my_song", + "active": false, + "rounds": 10, + "bonus_points": 0, + "rules": "THATS_MY_SONG", + "language": "", + "playlists": [ + 18 + ] + } + } ] diff --git a/backend/experiment/forms.py b/backend/experiment/forms.py index 0f9350abe..7c41f2599 100644 --- a/backend/experiment/forms.py +++ b/backend/experiment/forms.py @@ -4,8 +4,8 @@ # session_keys for Export CSV -SESSION_CHOICES = [('experiment_id', 'Experiment ID'), - ('experiment_name', 'Experiment name'), +SESSION_CHOICES = [('block_id', 'Block ID'), + ('block_name', 'Block name'), ('participant_id', 'Participant ID'), ('participant_country', 'Participant Country'), ('participant_access_info', 'Participant access info'), @@ -36,61 +36,61 @@ # Export templates for Export CSV EXPORT_TEMPLATES = {'wide': - [['experiment_id', 'experiment_name', 'participant_id', + [['block_id', 'block_name', 'participant_id', 'participant_country', 'participant_access_info', 'session_start', 'session_end', 'final_score'], ['section_name', 'result_created_at', 'result_score', 'result_comment', 'expected_response', 'given_response'], ['export_profile', 'session_data', 'convert_session_json', 'decision_time', 'result_config', 'convert_result_json', 'wide_format']], 'wide_json': - [['experiment_id', 'experiment_name', 'participant_id', + [['block_id', 'block_name', 'participant_id', 'participant_country', 'participant_access_info', 'session_start', 'session_end', 'final_score'], ['section_name', 'result_created_at', 'result_score', 'result_comment', 'expected_response', 'given_response'], ['export_profile', 'session_data', 'decision_time', 'result_config', 'wide_format']], 'wide_results': - [['experiment_name', 'participant_id', 'session_start', 'session_end', 'final_score'], + [['block_name', 'participant_id', 'session_start', 'session_end', 'final_score'], ['section_name', 'result_created_at', 'result_score', 'result_comment', 'expected_response', 'given_response'], ['session_data', 'convert_session_json', 'decision_time', 'wide_format']], 'wide_results_json': - [['experiment_name', 'participant_id', 'session_start', 'session_end', 'final_score'], + [['block_name', 'participant_id', 'session_start', 'session_end', 'final_score'], ['section_name', 'result_created_at', 'result_score', 'result_comment', 'expected_response', 'given_response'], ['session_data', 'decision_time', 'result_config', 'wide_format']], 'wide_profile': - [['experiment_name', 'participant_id', + [['block_name', 'participant_id', 'participant_country', 'participant_access_info'], [], ['export_profile', 'wide_format']], 'long': - [['experiment_id', 'experiment_name', 'participant_id', + [['block_id', 'block_name', 'participant_id', 'participant_country', 'participant_access_info', 'session_start', 'session_end', 'final_score'], ['section_name', 'result_created_at', 'result_score', 'result_comment', 'expected_response', 'given_response'], ['export_profile', 'session_data', 'convert_session_json', 'decision_time', 'result_config', 'convert_result_json']], 'long_json': - [['experiment_id', 'experiment_name', 'participant_id', + [['block_id', 'block_name', 'participant_id', 'participant_country', 'participant_access_info', 'session_start', 'session_end', 'final_score'], ['section_name', 'result_created_at', 'result_score', 'result_comment', 'expected_response', 'given_response'], ['export_profile', 'session_data', 'decision_time', 'result_config' ]], 'long_results': - [['experiment_name', 'participant_id', 'session_start', 'session_end', 'final_score'], + [['block_name', 'participant_id', 'session_start', 'session_end', 'final_score'], ['section_name', 'result_created_at', 'result_score', 'result_comment', 'expected_response', 'given_response'], ['session_data', 'convert_session_json', 'decision_time']], 'long_results_json': - [['experiment_name', 'participant_id', 'session_start', 'session_end', 'final_score'], + [['block_name', 'participant_id', 'session_start', 'session_end', 'final_score'], ['section_name', 'result_created_at', 'result_score', 'result_comment', 'expected_response', 'given_response'], ['session_data', 'decision_time', 'result_config']], 'long_profile': - [['experiment_name', 'participant_id', + [['block_name', 'participant_id', 'participant_country', 'participant_access_info'], [], ['export_profile']] @@ -134,7 +134,7 @@ def __init__(self, *args, **kwargs): 'This field will be deprecated in the nearby future. ' 'Please use experiment phases for dashboard configuration. (see bottom of form).

' 'Legacy behavior: If you check "dashboard", the experiment collection will have a ' - 'dashboard that shows all or a subgroup of related experiments along ' + 'dashboard that shows all or a subgroup of related blocks along ' 'with a description, footer, and about page. If you leave it unchecked, ' 'the experiment collection will redirect to the first experiment.') self.fields['about_content'].widget = MarkdownPreviewTextInput() @@ -149,7 +149,7 @@ class Media: css = {"all": ["experiment_series_admin.css"]} -class ExperimentForm(ModelForm): +class BlockForm(ModelForm): def __init__(self, *args, **kwargs): super(ModelForm, self).__init__(*args, **kwargs) @@ -198,19 +198,19 @@ class Meta: fields = ['name', 'slug', 'active', 'rules', 'rounds', 'bonus_points', 'playlists',] help_texts = { - 'description': 'A short description of the experiment that will be displayed on the experiment collection page and as a meta description in search engines.', + 'description': 'A short description of the block that will be displayed on the experiment collection page and as a meta description in search engines.', 'image': 'An image that will be displayed on the experiment collection page and as a meta image in search engines.', 'consent': 'Upload an HTML (.html) or MARKDOWN (.md) file with a text to ask a user its consent
\ - for using the experiment data for this instance of the experiment.
\ + for using the block data for this instance of the block.
\ This field will override any consent text loaded from the rules file.
\ HTML files also allow django template tags so that the text can be translated', - 'slug': 'The slug is used to identify the experiment in the URL so you can access it on the web as follows: app.amsterdammusiclab.nl/{slug}
\ - It must be unique, lowercase and contain only letters, numbers, and hyphens. Nor can it start with any of the following reserved words: admin, server, experiment, participant, result, section, session, static.', + 'slug': 'The slug is used to identify the block in the URL so you can access it on the web as follows: app.amsterdammusiclab.nl/{slug}
\ + It must be unique, lowercase and contain only letters, numbers, and hyphens. Nor can it start with any of the following reserved words: admin, server, block, participant, result, section, session, static.', } class Media: - js = ["experiment_admin.js"] - css = {"all": ["experiment_admin.css"]} + js = ["block_admin.js"] + css = {"all": ["block_admin.css"]} class ExportForm(Form): diff --git a/backend/experiment/management/commands/bootstrap.py b/backend/experiment/management/commands/bootstrap.py index e99839991..03ce018b8 100644 --- a/backend/experiment/management/commands/bootstrap.py +++ b/backend/experiment/management/commands/bootstrap.py @@ -2,13 +2,13 @@ from django.core.management.base import BaseCommand from django.contrib.auth.models import User -from experiment.models import Experiment +from experiment.models import Block from section.models import Playlist from question.questions import create_default_questions class Command(BaseCommand): - """ Command for creating a superuser and an experiment if they do not yet exist """ + """ Command for creating a superuser and an block if they do not yet exist """ def handle(self, *args, **options): @@ -17,16 +17,15 @@ def handle(self, *args, **options): if User.objects.count() == 0: management.call_command('createsuperuser', '--no-input') print('Created superuser') - if Experiment.objects.count() == 0: + if Block.objects.count() == 0: playlist = Playlist.objects.create( name='Empty Playlist' ) - experiment = Experiment.objects.create( + block = Block.objects.create( name='Goldsmiths Musical Sophistication Index', rules='RHYTHM_BATTERY_FINAL', slug='gold-msi', ) - experiment.playlists.add(playlist) - experiment.add_default_question_series() - print('Created default experiment') - + block.playlists.add(playlist) + block.add_default_question_series() + print('Created default block') diff --git a/backend/experiment/management/commands/compileplaylist.py b/backend/experiment/management/commands/compileplaylist.py index 7646f6c61..46e6c3f0e 100644 --- a/backend/experiment/management/commands/compileplaylist.py +++ b/backend/experiment/management/commands/compileplaylist.py @@ -49,7 +49,7 @@ def handle(self, *args, **options): if song_names_option: with open(join(playlist_dir, song_names_option)) as json_file: song_names = json.load(json_file) - experiment_option = options.get('experiment') + block_option = options.get('experiment') with open(join(playlist_dir, 'audiofiles.csv'), 'w+') as f: csv_writer = csv.writer(f) for i, audio_file in enumerate(search_critera): @@ -62,8 +62,8 @@ def handle(self, *args, **options): if song_names_option: artist_name = song_names[audio_file_clean] song_name = basename(audio_file)[:-4] - elif experiment_option: - rules = BLOCK_RULES.get(experiment_option) + elif block_option: + rules = BLOCK_RULES.get(block_option) info = rules.get_info_playlist(rules, audio_file_clean) artist_name = info.get('artist') song_name = info.get('song') @@ -84,34 +84,34 @@ def handle(self, *args, **options): csv_writer.writerow(row) -def calculate_group_tag(filename, experiment, index): +def calculate_group_tag(filename, block, index): identifier = splitext(pathsplit(filename)[-1])[0] - if experiment == 'huang2022': + if block == 'huang2022': parts = identifier.split('.') group = parts[-1] tag = None else: parts = identifier.split('_') - if experiment == 'hbat': - # H-BAT style experiments: + if block == 'hbat': + # H-BAT style blocks: # level gets encoded as group # slower (1) or faster (0) gets encoded as tag group = int(parts[-1]) tag = 1 if parts[-3] == 'S' else 0 - elif experiment == 'bst': - # BST experiments: + elif block == 'bst': + # BST blocks: # level gets encoded as group # duple (1) / triple (0) gets encoded as tag group = int(parts[-1]) tag = 1 if parts[-3] == 'D' else 0 - elif experiment == 'rhdi': - # rhythm discrimination experiment: + elif block == 'rhdi': + # rhythm discrimination block: # standard (1) / deviant (0) gets encoded as group # tempo (160 - 200) gets encoded as tag group = 1 if parts[-2] == 'Standard' else 0 tag = int(parts[-1]) - elif experiment == 'cat': - # categorization experiment + elif block == 'cat': + # categorization block # Pair1: 1A, 1B / Pair2: 2A, 2B gets encodes as tag # Same direction: SAME, Crossed direction: CROSSED gets encoded as group if identifier[-2:] == '1A': @@ -123,7 +123,7 @@ def calculate_group_tag(filename, experiment, index): elif identifier[-2:] == '2B': tag = '2B' group = 'SAME' if identifier[0] == 'S' else 'CROSSED' - elif experiment == 'matching_pairs': + elif block == 'matching_pairs': group = index tag = pathsplit(pathsplit(filename)[0])[1] return group, tag diff --git a/backend/experiment/management/commands/createruleset.py b/backend/experiment/management/commands/createruleset.py index 7342fbfa3..bc3e22948 100644 --- a/backend/experiment/management/commands/createruleset.py +++ b/backend/experiment/management/commands/createruleset.py @@ -4,47 +4,47 @@ class Command(BaseCommand): - help = 'Creates a new experiment rules class' + help = 'Creates a new block rules class' def handle(self, *args, **options): - # Ask for the experiment name - ruleset_name = input("What is the name of your experiment ruleset? (ex. Musical Preferences): ") + # Ask for the block name + ruleset_name = input("What is the name of your block ruleset? (ex. Musical Preferences): ") - # Create the experiment rule class - success = self.create_experiment_rule_class(ruleset_name) + # Create the block rule class + success = self.create_block_rule_class(ruleset_name) if not success: return - # Add the new experiment to ./experiment/rules/__init__.py + # Add the new block to ./experiment/rules/__init__.py self.register_experiment_rule(ruleset_name, './experiment/rules/__init__.py') - # Create a basic test file for the experiment + # Create a basic test file for the block self.create_test_file(ruleset_name) - def create_experiment_rule_class(self, ruleset_name): - # Get the experiment name in different cases + def create_block_rule_class(self, ruleset_name): + # Get the block name in different cases ruleset_name_snake_case, ruleset_name_snake_case_upper, ruleset_name_pascal_case = self.get_ruleset_name_cases(ruleset_name) - # Create a new file for the experiment rules class + # Create a new file for the block rules class filename = f"./experiment/rules/{ruleset_name_snake_case}.py" # Check if the file already exists if os.path.isfile(filename): - self.stdout.write(self.style.ERROR(f"Experiment ruleset \"{ruleset_name}\" already exists. Exiting without creating file(s).")) + self.stdout.write(self.style.ERROR(f"Experiment block ruleset \"{ruleset_name}\" already exists. Exiting without creating file(s).")) return # Create the file by copying ./experiment/management/commands/templates/experiment.py with open(filename, 'w') as f: with open('./experiment/management/commands/templates/experiment.py', 'r') as template: f.write(template.read() - .replace('NewExperimentRuleset', ruleset_name_pascal_case) + .replace('NewBlockRuleset', ruleset_name_pascal_case) .replace('new_block_ruleset', ruleset_name_snake_case) .replace('NEW_BLOCK_RULESET', ruleset_name_snake_case_upper) - .replace('New Experiment Ruleset', ruleset_name.title()) + .replace('New Block Ruleset', ruleset_name.title()) ) - self.stdout.write(self.style.SUCCESS(f"Created {filename} for experiment ruleset \"{ruleset_name}\"")) + self.stdout.write(self.style.SUCCESS(f"Created {filename} for block ruleset \"{ruleset_name}\"")) return True @@ -77,10 +77,10 @@ def register_experiment_rule(self, ruleset_name, file_path): self.stdout.write(self.style.SUCCESS(f"Registered ruleset \"{ruleset_name}\" in {file_path}")) def create_test_file(self, ruleset_name): - # Get the experiment name in different cases + # Get the block name in different cases ruleset_name_snake_case, ruleset_name_snake_case_upper, ruleset_name_pascal_case = self.get_ruleset_name_cases(ruleset_name) - # Create a new file for the experiment class + # Create a new file for the block class filename = f"./experiment/rules/tests/test_{ruleset_name_snake_case}.py" # Check if the file already exists @@ -92,20 +92,20 @@ def create_test_file(self, ruleset_name): with open(filename, 'w') as f: with open('./experiment/management/commands/templates/test_experiment.py', 'r') as template: f.write(template.read() - .replace('NewExperimentRuleset', ruleset_name_pascal_case) + .replace('NewBlockRuleset', ruleset_name_pascal_case) .replace('new_block_ruleset', ruleset_name_snake_case) .replace('NEW_BLOCK_RULESET', ruleset_name_snake_case_upper) - .replace('New Experiment Ruleset', ruleset_name.title()) + .replace('New Block Ruleset', ruleset_name.title()) ) - self.stdout.write(self.style.SUCCESS(f"Created {filename} for experiment {ruleset_name}")) + self.stdout.write(self.style.SUCCESS(f"Created {filename} for experiment block {ruleset_name}")) def get_ruleset_name_cases(self, ruleset_name): - # Convert experiment name to snake_case and lowercase every word and replace spaces with underscores + # Convert block name to snake_case and lowercase every word and replace spaces with underscores ruleset_name_snake_case = ruleset_name.lower().replace(' ', '_') ruleset_name_snake_case_upper = ruleset_name_snake_case.upper() - # Convert experiment name to PascalCase and capitalize every word and remove spaces + # Convert block name to PascalCase and capitalize every word and remove spaces ruleset_name_pascal_case = ruleset_name.title().replace(' ', '') return ruleset_name_snake_case, ruleset_name_snake_case_upper, ruleset_name_pascal_case diff --git a/backend/experiment/management/commands/exportexperiment.py b/backend/experiment/management/commands/exportexperiment.py index 3be6ee15a..43ba79029 100644 --- a/backend/experiment/management/commands/exportexperiment.py +++ b/backend/experiment/management/commands/exportexperiment.py @@ -1,19 +1,19 @@ import json from django.core.management.base import BaseCommand, CommandError -from experiment.models import Experiment +from experiment.models import Block class Command(BaseCommand): - """Command for exporting experiments using the manage.py script""" + """Command for exporting blocks using the manage.py script""" - help = 'Export experiment data' + help = 'Export block data' def add_arguments(self, parser): # Positional arguments - parser.add_argument('experiment_slug', + parser.add_argument('block_slug', type=str, - help="Experiment slug") + help="Block slug") # Named (optional) arguments parser.add_argument( @@ -24,17 +24,17 @@ def add_arguments(self, parser): ) def handle(self, *args, **options): - experiment_slug = options['experiment_slug'] + block_slug = options['block_slug'] indent = options['indent'] try: - experiment = Experiment.objects.get(slug=experiment_slug) - except Experiment.DoesNotExist: + block = Block.objects.get(slug=block_slug) + except Block.DoesNotExist: raise CommandError( - 'Experiment "%s" does not exist with slug' % experiment_slug) + 'Block "%s" does not exist with slug' % block_slug) # Optional indent options = {} if indent > 0: options = {'indent': indent} - self.stdout.write(json.dumps(experiment.export_admin(), **options)) + self.stdout.write(json.dumps(block.export_admin(), **options)) diff --git a/backend/experiment/management/commands/templates/experiment.py b/backend/experiment/management/commands/templates/experiment.py index 55e22d631..b26be140b 100644 --- a/backend/experiment/management/commands/templates/experiment.py +++ b/backend/experiment/management/commands/templates/experiment.py @@ -10,8 +10,8 @@ from result.utils import prepare_result -class NewExperimentRuleset(Base): - ''' An experiment type that could be used to test musical preferences ''' +class NewBlockRuleset(Base): + ''' An block type that could be used to test musical preferences ''' ID = 'NEW_BLOCK_RULESET' contact_email = 'info@example.com' @@ -32,20 +32,20 @@ def __init__(self): }, ] - def first_round(self, experiment): - ''' Provide the first rounds of the experiment, + def first_round(self, block): + ''' Provide the first rounds of the block, before session creation The first_round must return at least one Info or Explainer action Consent and Playlist are often desired, but optional ''' # 1. Informed consent (optional) - consent = Consent(experiment.consent, + consent = Consent(block.consent, title=_('Informed consent'), confirm=_('I agree'), deny=_('Stop')) # 2. Choose playlist (optional, only relevant if there are multiple playlists the participant can choose from) - playlist = Playlist(experiment.playlists.all()) + playlist = Playlist(block.playlists.all()) # 3. Explainer (optional) explainer = Explainer( @@ -71,7 +71,7 @@ def next_round(self, session): return actions elif session.rounds_complete(): - # we have as many results as rounds in this experiment + # we have as many results as rounds in this block # finish session and show Final view session.finish() session.save() @@ -102,7 +102,7 @@ def get_trial(self, session): view = Trial( playback=playback, feedback_form=form, - title=_('Test experiment'), + title=_('Test block'), config={ 'response_time': section.duration, 'listen_first': True diff --git a/backend/experiment/migrations/0036_add_question_model_data.py b/backend/experiment/migrations/0036_add_question_model_data.py index ebaba6b0d..b5cc214c3 100644 --- a/backend/experiment/migrations/0036_add_question_model_data.py +++ b/backend/experiment/migrations/0036_add_question_model_data.py @@ -1,11 +1,11 @@ - from django.db import migrations -from experiment.models import Block as Experiment from experiment.rules import BLOCK_RULES as EXPERIMENT_RULES def add_default_question_series(apps, schema_editor): + Experiment = apps.get_model("experiment", "Experiment") + for experiment in Experiment.objects.all(): if EXPERIMENT_RULES.get(experiment.rules) and not experiment.questionseries_set.all(): experiment.add_default_question_series() diff --git a/backend/experiment/migrations/0046_alter_socialmediaconfig_content.py b/backend/experiment/migrations/0046_alter_socialmediaconfig_content.py new file mode 100644 index 000000000..36e37c03c --- /dev/null +++ b/backend/experiment/migrations/0046_alter_socialmediaconfig_content.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.11 on 2024-07-03 10:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiment', '0042_rename_experiment_to_block_squashed_0045_rename_experiment_feedback_block'), + ] + + operations = [ + migrations.AlterField( + model_name='socialmediaconfig', + name='content', + field=models.TextField(blank=True, default='I scored {points} points in {block_name}!', help_text='Content for social media sharing. Use {points} and {block_name} as placeholders.'), + ), + ] diff --git a/backend/experiment/models.py b/backend/experiment/models.py index 2917d83b8..9274c9ad0 100644 --- a/backend/experiment/models.py +++ b/backend/experiment/models.py @@ -11,7 +11,7 @@ from session.models import Session from typing import Optional -from .validators import markdown_html_validator, experiment_slug_validator +from .validators import markdown_html_validator, block_slug_validator language_choices = [(key, ISO_LANGUAGES[key]) for key in ISO_LANGUAGES.keys()] language_choices[0] = ('', 'Unset') @@ -52,15 +52,15 @@ def __str__(self): class Meta: verbose_name_plural = "Experiment Collections" - def associated_experiments(self): + def associated_blocks(self): phases = self.phases.all() return [ - experiment.experiment for phase in phases for experiment in list(phase.experiments.all())] + experiment.block for phase in phases for experiment in list(phase.blocks.all())] def export_sessions(self): """export sessions for this collection""" all_sessions = Session.objects.none() - for exp in self.associated_experiments(): + for exp in self.associated_blocks(): all_sessions |= Session.objects.filter(block=exp).order_by('-started_at') return all_sessions @@ -122,7 +122,7 @@ class Block(models.Model): blank=True, null=True ) - slug = models.SlugField(db_index=True, max_length=64, unique=True, validators=[experiment_slug_validator]) + slug = models.SlugField(db_index=True, max_length=64, unique=True, validators=[block_slug_validator]) url = models.CharField(verbose_name='URL with more information about the block', max_length=100, blank=True, default='') hashtag = models.CharField(verbose_name='hashtag for social media', max_length=20, blank=True, default='') active = models.BooleanField(default=True) @@ -348,8 +348,8 @@ class SocialMediaConfig(models.Model): content = models.TextField( blank=True, - help_text=_("Content for social media sharing. Use {points} and {experiment_name} as placeholders."), - default="I scored {points} points in {experiment_name}!" + help_text=_("Content for social media sharing. Use {points} and {block_name} as placeholders."), + default="I scored {points} points in {block_name}!" ) SOCIAL_MEDIA_CHANNELS = [ @@ -368,17 +368,17 @@ class SocialMediaConfig(models.Model): ) def get_content( - self, score: int | None = None, experiment_name: str | None = None + self, score: int | None = None, block_name: str | None = None ) -> str: if self.content: return self.content - if not score or not experiment_name: - raise ValueError("score and experiment_name are required") + if not score or not block_name: + raise ValueError("score and block_name are required") - return _("I scored {points} points in {experiment_name}").format( + return _("I scored {points} points in {block_name}").format( score=score, - experiment_name=experiment_name + block_name=block_name ) def __str__(self): diff --git a/backend/experiment/rules/base.py b/backend/experiment/rules/base.py index acf2df4fe..180a1242f 100644 --- a/backend/experiment/rules/base.py +++ b/backend/experiment/rules/base.py @@ -56,10 +56,10 @@ def calculate_score(self, result, data): def get_play_again_url(self, session: Session): participant_id_url_param = f'?participant_id={session.participant.participant_id_url}' if session.participant.participant_id_url else "" - return f'/{session.experiment.slug}{participant_id_url_param}' + return f'/{session.block.slug}{participant_id_url_param}' def calculate_intermediate_score(self, session, result): - """ process result data during a trial (i.e., between next_round calls) + """ process result data during a trial (i.e., between next_round calls) return score """ return 0 @@ -128,7 +128,7 @@ def get_single_question(self, session, randomize=False): Participants will not continue to the next question set until they have completed their current one. """ - questionnaire = unanswered_questions(session.participant, get_questions_from_series(session.experiment.questionseries_set.all()), randomize) + questionnaire = unanswered_questions(session.participant, get_questions_from_series(session.block.questionseries_set.all()), randomize) try: question = next(questionnaire) return Trial( @@ -141,7 +141,7 @@ def get_questionnaire(self, session, randomize=False, cutoff_index=None): ''' Get a list of questions to be asked in succession ''' trials = [] - questions = list(unanswered_questions(session.participant, get_questions_from_series(session.experiment.questionseries_set.all()), randomize, cutoff_index)) + questions = list(unanswered_questions(session.participant, get_questions_from_series(session.block.questionseries_set.all()), randomize, cutoff_index)) open_questions = len(questions) if not open_questions: return None @@ -152,29 +152,29 @@ def get_questionnaire(self, session, randomize=False, cutoff_index=None): )) return trials - def social_media_info(self, experiment, score): - current_url = f"{settings.RELOAD_PARTICIPANT_TARGET}/{experiment.slug}" + def social_media_info(self, block, score): + current_url = f"{settings.RELOAD_PARTICIPANT_TARGET}/{block.slug}" return { 'apps': ['facebook', 'twitter'], 'message': _("I scored %(score)i points on %(url)s") % { 'score': score, 'url': current_url }, - 'url': experiment.url or current_url, - 'hashtags': [experiment.hashtag or experiment.slug, "amsterdammusiclab", "citizenscience"] + 'url': block.url or current_url, + 'hashtags': [block.hashtag or block.slug, "amsterdammusiclab", "citizenscience"] } def validate_playlist(self, playlist: None): errors = [] - # Common validations across experiments + # Common validations across blocks if not playlist: - errors.append('The experiment must have a playlist.') + errors.append('The block must have a playlist.') return errors sections = playlist.section_set.all() if not sections: - errors.append('The experiment must have at least one section.') + errors.append('The block must have at least one section.') try: playlist.clean_csv() diff --git a/backend/experiment/rules/beat_alignment.py b/backend/experiment/rules/beat_alignment.py index c4e7036bf..deb43c0e4 100644 --- a/backend/experiment/rules/beat_alignment.py +++ b/backend/experiment/rules/beat_alignment.py @@ -18,8 +18,8 @@ class BeatAlignment(Base): ID = 'BEAT_ALIGNMENT' - def first_round(self, experiment): - """Create data for the first experiment rounds""" + def first_round(self, block): + """Create data for the first block rounds""" # 1. General explainer explainer = Explainer( @@ -45,21 +45,21 @@ def first_round(self, experiment): def next_round(self, session): """Get action data for the next round""" - # If the number of results equals the number of experiment.rounds + # If the number of results equals the number of block.rounds # Close the session and return data for the final_score view if session.rounds_complete(): # Finish session session.finish() session.save() percentage = int( - (sum([r.score for r in session.result_set.all()]) / session.experiment.rounds) * 100) + (sum([r.score for r in session.result_set.all()]) / session.block.rounds) * 100) feedback = _('Well done! You’ve answered {} percent correctly!').format( percentage) trivia = _('In the UK, over 140.000 people did \ this test when it was first developed?') final_text = render_feedback_trivia(feedback, trivia) return final_action_with_optional_button(session, final_text) - + # Next round number, can be used to return different actions next_round_number = session.get_next_round() diff --git a/backend/experiment/rules/categorization.py b/backend/experiment/rules/categorization.py index dd1971fac..eacb195f7 100644 --- a/backend/experiment/rules/categorization.py +++ b/backend/experiment/rules/categorization.py @@ -29,7 +29,7 @@ def __init__(self): }, ] - def first_round(self, experiment): + def first_round(self, block): explainer = Explainer( instruction="This is a listening experiment in which you have to respond to short sound sequences.", steps=[], @@ -37,7 +37,7 @@ def first_round(self, experiment): ) # Add consent from file or admin (admin has priority) consent = Consent( - experiment.consent, + block.consent, title='Informed consent', confirm='I agree', deny='Stop', @@ -80,7 +80,7 @@ def next_round(self, session): # Calculate round number from passed training rounds rounds_passed = (session.rounds_passed() - - int(json_data['training_rounds'])) + int(json_data['training_rounds'])) # Change phase to enable collecting results of second half of training-1 if session.rounds_passed() == 10: json_data['phase'] = 'training-1B' @@ -185,8 +185,8 @@ def next_round(self, session): feedback = self.get_feedback(session) return [feedback, explainer] - elif json_data['phase'] == 'testing': - if rounds_passed < len(json_data['sequence']): + elif json_data['phase'] == 'testing': + if rounds_passed < len(json_data['sequence']): # Determine wether this round has feedback if rounds_passed in json_data['feedback_sequence']: return self.get_trial_with_feedback(session) @@ -258,7 +258,7 @@ def plan_experiment(self, session): """ # Check for unfinished sessions older then 24 hours caused by closed browser - all_sessions = session.experiment.session_set.filter( + all_sessions = session.block.session_set.filter( finished_at=None).filter( started_at__lte=timezone.now()-timezone.timedelta(hours=24)).exclude( json_data__contains='ABORTED').exclude( @@ -276,18 +276,18 @@ def plan_experiment(self, session): # Count sessions per assigned group used_groups = [ - session.experiment.session_set.filter( + session.block.session_set.filter( json_data__contains='S1').count(), - session.experiment.session_set.filter( + session.block.session_set.filter( json_data__contains='S2').count(), - session.experiment.session_set.filter( + session.block.session_set.filter( json_data__contains='C1').count(), - session.experiment.session_set.filter( + session.block.session_set.filter( json_data__contains='C2').count() ] # Get sessions for current participant - current_sessions = session.experiment.session_set.filter( + current_sessions = session.block.session_set.filter( participant=session.participant) # Check if this participant already has a previous session diff --git a/backend/experiment/rules/congosamediff.py b/backend/experiment/rules/congosamediff.py index f19128a76..c6f11c520 100644 --- a/backend/experiment/rules/congosamediff.py +++ b/backend/experiment/rules/congosamediff.py @@ -13,31 +13,31 @@ class CongoSameDiff(Base): - """ A micro-PROMS inspired experiment that tests the participant's ability to distinguish between different sounds. """ + """ A micro-PROMS inspired experiment block that tests the participant's ability to distinguish between different sounds. """ ID = 'CONGOSAMEDIFF' contact_email = 'aml.tunetwins@gmail.com' def __init__(self): pass - def first_round(self, experiment: Block): - """ Provide the first rounds of the experiment, + def first_round(self, block: Block): + """ Provide the first rounds of the block, before session creation The first_round must return at least one Info or Explainer action Consent and Playlist are often desired, but optional """ - # Do a validity check on the experiment - errors = self.validate_playlist(experiment.playlists.first()) + # Do a validity check on the block + errors = self.validate_playlist(block.playlists.first()) if errors: - raise ValueError('The experiment playlist is not valid: \n- ' + '\n- '.join(errors)) + raise ValueError('The block playlist is not valid: \n- ' + '\n- '.join(errors)) # 1. Playlist - playlist = Playlist(experiment.playlists.all()) + playlist = Playlist(block.playlists.all()) # 2. Explainer explainer = Explainer( - instruction='Welcome to this Musicality Battery experiment', + instruction='Welcome to this Musicality Battery block', steps=[], ) @@ -206,11 +206,11 @@ def get_next_trial( ) form = Form([question]) playback = PlayButton([section], play_once=False) - experiment_name = session.experiment.name if session.experiment else 'Musicality Battery Block' + block_name = session.block.name if session.block else 'Musicality Battery Block' view = Trial( playback=playback, feedback_form=form, - title=_(experiment_name), + title=_(block_name), config={ 'response_time': section.duration, 'listen_first': False, diff --git a/backend/experiment/rules/duration_discrimination.py b/backend/experiment/rules/duration_discrimination.py index ea1016486..a80800850 100644 --- a/backend/experiment/rules/duration_discrimination.py +++ b/backend/experiment/rules/duration_discrimination.py @@ -30,8 +30,8 @@ class DurationDiscrimination(Base): increase_difficulty_multiplier = .5 decrease_difficulty_multiplier = 1.5 - def first_round(self, experiment): - """Create data for the first experiment rounds""" + def first_round(self, block): + """Create data for the first block rounds""" explainer = self.intro_explanation() explainer2 = practice_explainer() @@ -131,7 +131,7 @@ def next_trial_action(self, session, trial_condition, difficulty): submits=True ) # create Result object and save expected result to database - + playback = Autoplay([section]) form = Form([question]) view = Trial( @@ -172,7 +172,7 @@ def get_task_explanation(self): def get_introduction(self): return _('In this test you will hear two time durations for each trial, which are marked by two tones.') - def finalize_experiment(self, session): + def finalize_block(self, session): ''' After 8 turnpoints, finalize experiment Give participant feedback ''' @@ -273,7 +273,7 @@ def staircasing_blocks(self, session, trial_action_callback): difficulty) if not action: # action is None if the audio file doesn't exist - return self.finalize_experiment(session) + return self.finalize_block(session) return action def get_difficulty(self, session, multiplier=1.0): @@ -291,7 +291,7 @@ def get_difficulty(self, session, multiplier=1.0): # return rounded difficulty # this uses the decimal module, since round() does not work entirely as expected return int(Decimal(str(current_difficulty)).quantize(Decimal('0'), rounding=ROUND_HALF_UP)) - + def last_non_catch_correct(self, previous_results): """ check if previous responses (before the current one, which is correct) have been catch or non-catch, and if non-catch, if they were correct diff --git a/backend/experiment/rules/eurovision_2020.py b/backend/experiment/rules/eurovision_2020.py index 3b832f9fd..80cb15661 100644 --- a/backend/experiment/rules/eurovision_2020.py +++ b/backend/experiment/rules/eurovision_2020.py @@ -29,9 +29,9 @@ def plan_sections(self, session): old_new_song_set = set(session.playlist.song_ids({'tag__gt': 0})) # How many sections do we need? - n_old = round(0.17 * session.experiment.rounds) - n_new = round(0.33 * session.experiment.rounds) - n_old - n_free = session.experiment.rounds - 2 * n_old - n_new + n_old = round(0.17 * session.block.rounds) + n_new = round(0.33 * session.block.rounds) - n_old + n_free = session.block.rounds - 2 * n_old - n_new # Assign songs. old_songs = random.sample(list(old_new_song_set), k=n_old) diff --git a/backend/experiment/rules/h_bat.py b/backend/experiment/rules/h_bat.py index d17f8daf5..1cd895a0b 100644 --- a/backend/experiment/rules/h_bat.py +++ b/backend/experiment/rules/h_bat.py @@ -48,19 +48,19 @@ def next_round(self, session): action = self.next_trial_action(session, trial_condition, 1) if not action: # participant answered first trial incorrectly (outlier) - action = self.finalize_experiment(session) + action = self.finalize_block(session) else: action = staircasing(session, self.next_trial_action) if not action: # action is None if the audio file doesn't exist - action = self.finalize_experiment(session) + action = self.finalize_block(session) if session.final_score == MAX_TURNPOINTS+1: # delete result created before this check session.result_set.order_by('-created_at').first().delete() - action = self.finalize_experiment(session) + action = self.finalize_block(session) return action - - def first_round(self, experiment): + + def first_round(self, block): explainer = self.intro_explainer() # Consent with admin text or default text explainer2 = practice_explainer() @@ -71,7 +71,7 @@ def first_round(self, experiment): def next_trial_action(self, session, trial_condition, level=1, *kwargs): """ - Get the next actions for the experiment + Get the next actions for the block trial_condition is either 1 or 0 level can be 1 (20 ms) or higher (10, 5, 2.5 ms...) """ @@ -111,7 +111,7 @@ def next_trial_action(self, session, trial_condition, level=1, *kwargs): } ) return view - + def intro_explainer(self): return Explainer( instruction=_( @@ -132,7 +132,7 @@ def intro_explainer(self): step_numbers=True, button_label='Ok' ) - + def response_explainer(self, correct, slower, button_label=_('Next fragment')): if correct: if slower: @@ -153,8 +153,8 @@ def response_explainer(self, correct, slower, button_label=_('Next fragment')): steps=[], button_label=button_label ) - - def finalize_experiment(self, session): + + def finalize_block(self, session): """ if either the max_turnpoints have been reached, or if the section couldn't be found (outlier), stop the experiment """ @@ -169,7 +169,7 @@ def finalize_experiment(self, session): session.finish() session.save() return final_action_with_optional_button(session, final_text) - + def get_trivia(self): return _("When people listen to music, they often perceive an underlying regular pulse, like the woodblock \ in this task. This allows us to clap along with the music at a concert and dance together in synchrony.") diff --git a/backend/experiment/rules/hbat_bst.py b/backend/experiment/rules/hbat_bst.py index 5e7ffcdc5..2d7f8d9f6 100644 --- a/backend/experiment/rules/hbat_bst.py +++ b/backend/experiment/rules/hbat_bst.py @@ -34,7 +34,7 @@ def intro_explainer(self): ], button_label='Ok' ) - + def next_trial_action(self, session, trial_condition, level=1): """ Get the next actions for the experiment @@ -73,7 +73,7 @@ def next_trial_action(self, session, trial_condition, level=1): } ) return view - + def response_explainer(self, correct, in2, button_label=_('Next fragment')): if correct: if in2: @@ -94,8 +94,8 @@ def response_explainer(self, correct, in2, button_label=_('Next fragment')): steps=[], button_label=button_label ) - - def finalize_experiment(self, session): + + def finalize_block(self, session): """ if either the max_turnpoints have been reached, or if the section couldn't be found (outlier), stop the experiment """ diff --git a/backend/experiment/rules/hooked.py b/backend/experiment/rules/hooked.py index 1b78b130f..186e7ffca 100644 --- a/backend/experiment/rules/hooked.py +++ b/backend/experiment/rules/hooked.py @@ -44,8 +44,8 @@ def __init__(self): {"name": "TIPI", "keys": QUESTION_GROUPS["TIPI"], "randomize": True}, # 5. TIPI (10 questions) ] - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" # 1. Explain game. explainer = Explainer( @@ -63,15 +63,15 @@ def first_round(self, experiment): # 2. Add consent from file or admin (admin has priority) consent = Consent( - experiment.consent, + block.consent, title=_('Informed consent'), confirm=_('I agree'), deny=_('Stop'), url=self.consent_file ) - + # 3. Choose playlist. - playlist = Playlist(experiment.playlists.all()) + playlist = Playlist(block.playlists.all()) return [ consent, @@ -84,9 +84,9 @@ def next_round(self, session): json_data = session.load_json_data() round_number = self.get_current_round(session) - # If the number of results equals the number of experiment.rounds, + # If the number of results equals the number of block.rounds, # close the session and return data for the final_score view. - if round_number == session.experiment.rounds: + if round_number == session.block.rounds: # Finish session. session.finish() @@ -101,7 +101,7 @@ def next_round(self, session): final_text=self.final_score_message(session), rank=self.rank(session), social=self.social_media_info( - session.experiment, total_score), + session.block, total_score), show_profile_link=True, button={ 'text': _('Play again'), @@ -202,7 +202,7 @@ def final_score_message(self, session): def get_trial_title(self, session, round_number): return _("Round %(number)d / %(total)d") %\ - {'number': round_number+1, 'total': session.experiment.rounds} + {'number': round_number+1, 'total': session.block.rounds} def plan_sections(self, session, filter_by={}): """Set the plan of tracks for a session. @@ -216,7 +216,7 @@ def plan_sections(self, session, filter_by={}): # 2/3 of the rounds are SongSync, of which 1/4 old songs, 3/4 "free" # 1/3 of the rounds are "heard before", of which 1/2 old songs # e.g. 30 rounds -> 20 SongSync with 5 songs to be repeated later - n_rounds = session.experiment.rounds + n_rounds = session.block.rounds n_old = round(0.17 * n_rounds) n_new = round(0.17 * n_rounds) n_free = n_rounds - 2 * n_old - n_new diff --git a/backend/experiment/rules/huang_2022.py b/backend/experiment/rules/huang_2022.py index 216855755..bf595a64d 100644 --- a/backend/experiment/rules/huang_2022.py +++ b/backend/experiment/rules/huang_2022.py @@ -48,17 +48,17 @@ def __init__(self): }, ] - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" # Add consent from file or admin (admin has priority) consent = Consent( - experiment.consent, + block.consent, title=_('Informed consent'), confirm=_('I agree'), deny=_('Stop'), url='consent/consent_huang2021.html' ) - playlist = Playlist(experiment.playlists.all()) + playlist = Playlist(block.playlists.all()) return [ consent, @@ -70,17 +70,17 @@ def feedback_info(self): info['header'] = _("Any remarks or questions (optional):") info['thank_you'] = _("Thank you for your feedback!") return info - + def next_round(self, session): """Get action data for the next round""" - # If the number of results equals the number of experiment.rounds, + # If the number of results equals the number of block.rounds, # close the session and return data for the final_score view. json_data = session.load_json_data() # Get next round number and initialise actions list. Two thirds of # rounds will be song_sync; the remainder heard_before. round_number = self.get_current_round(session) - total_rounds = session.experiment.rounds + total_rounds = session.block.rounds # Collect actions. actions = [] @@ -94,7 +94,7 @@ def next_round(self, session): form = Form(form=[BooleanQuestion( key='audio_check1', choices={'no': _('No'), 'yes': _('Yes')}, - result_id=prepare_result('audio_check1', session, + result_id=prepare_result('audio_check1', session, scoring_rule='BOOLEAN'), submits=True, style=STYLE_BOOLEAN_NEGATIVE_FIRST)]) @@ -123,7 +123,7 @@ def next_round(self, session): session.save() return Redirect(settings.HOMEPAGE) if last_result.score == 1: - # Start experiment: plan sections and show explainers + # Start block: plan sections and show explainers self.plan_sections(session) # Show explainers and go to SongSync explainer = Explainer( @@ -157,10 +157,10 @@ def next_round(self, session): heard_before_offset = len(plan['song_sync_sections']) - # show score + # show score score = self.get_score(session, round_number) actions.append(score) - + # SongSync rounds if round_number < heard_before_offset: actions.extend(self.next_song_sync_action(session)) @@ -172,7 +172,7 @@ def next_round(self, session): self.next_heard_before_action(session)) elif heard_before_offset < round_number < total_rounds: actions.append( - self.next_heard_before_action(session)) + self.next_heard_before_action(session)) else: questionnaire = self.get_questionnaire(session) if questionnaire: @@ -185,7 +185,7 @@ def next_round(self, session): else: return [self.finalize(session)] return actions - + def finalize(self, session): session.finish() session.save() @@ -196,7 +196,7 @@ def finalize(self, session): show_profile_link=True, feedback_info=self.feedback_info() ) - + def final_score_message(self, session): """Create final score message for given session""" @@ -248,4 +248,3 @@ def get_test_playback(): show_animation=True ) return playback - \ No newline at end of file diff --git a/backend/experiment/rules/kuiper_2020.py b/backend/experiment/rules/kuiper_2020.py index 95731f92c..12123493f 100644 --- a/backend/experiment/rules/kuiper_2020.py +++ b/backend/experiment/rules/kuiper_2020.py @@ -30,9 +30,9 @@ def plan_sections(self, session): old_new_song_set = set(session.playlist.song_ids({'tag__gt': 0})) # How many sections do we need? - n_old = round(0.17 * session.experiment.rounds) - n_new = round(0.33 * session.experiment.rounds) - n_old - n_free = session.experiment.rounds - 2 * n_old - n_new + n_old = round(0.17 * session.block.rounds) + n_new = round(0.33 * session.block.rounds) - n_old + n_free = session.block.rounds - 2 * n_old - n_new # Assign songs. old_songs = random.sample(old_new_song_set, k=n_old) diff --git a/backend/experiment/rules/matching_pairs.py b/backend/experiment/rules/matching_pairs.py index 0ef6e6426..93f399407 100644 --- a/backend/experiment/rules/matching_pairs.py +++ b/backend/experiment/rules/matching_pairs.py @@ -36,17 +36,17 @@ def __init__(self): }, ] - def first_round(self, experiment): + def first_round(self, block): # Add consent from file or admin (admin has priority) consent = Consent( - experiment.consent, + block.consent, title=_('Informed consent'), confirm=_('I agree'), deny=_('Stop'), url='consent/consent_matching_pairs.html' ) # 2. Choose playlist. - playlist = Playlist(experiment.playlists.all()) + playlist = Playlist(block.playlists.all()) explainer = Explainer( instruction='', @@ -64,7 +64,7 @@ def first_round(self, experiment): playlist, explainer ] - + def next_round(self, session): if session.rounds_passed() < 1: trials = self.get_questionnaire(session) @@ -79,7 +79,7 @@ def next_round(self, session): return [trial] else: # final score saves the result from the cleared board into account - social_info = self.social_media_info(session.experiment, session.final_score) + social_info = self.social_media_info(session.block, session.final_score) social_info['apps'].append('clipboard') score = Final( session, @@ -104,7 +104,7 @@ def select_sections(self, session): random.shuffle(pairs) selected_pairs = pairs[:self.num_pairs] session.save_json_data({'pairs': pairs[self.num_pairs:]}) - originals = session.playlist.section_set.filter(group__in=selected_pairs, tag='Original') + originals = session.playlist.section_set.filter(group__in=selected_pairs, tag='Original') degradations = json_data.get('degradations') if not degradations: degradations = ['Original', '1stDegradation', '2ndDegradation'] @@ -141,7 +141,7 @@ def get_matching_pairs_trial(self, session): def calculate_score(self, result, data): ''' not used in this experiment ''' pass - + def calculate_intermediate_score(self, session, result): ''' will be called every time two cards have been turned ''' result_data = json.loads(result) @@ -168,5 +168,3 @@ def calculate_intermediate_score(self, session, result): prepare_result('move', session, json_data=result_data, score=score, given_response=given_response) return score - - diff --git a/backend/experiment/rules/matching_pairs_lite.py b/backend/experiment/rules/matching_pairs_lite.py index 4f4a53a15..4542012ad 100644 --- a/backend/experiment/rules/matching_pairs_lite.py +++ b/backend/experiment/rules/matching_pairs_lite.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from .matching_pairs import MatchingPairsGame -from experiment.actions import Final, Playlist, Info +from experiment.actions import Playlist, Info from experiment.actions.utils import final_action_with_optional_button @@ -14,9 +14,9 @@ class MatchingPairsLite(MatchingPairsGame): score_feedback_display = 'small-bottom-right' contact_email = 'aml.tunetwins@gmail.com' - def first_round(self, experiment): + def first_round(self, block): # 2. Choose playlist. - playlist = Playlist(experiment.playlists.all()) + playlist = Playlist(block.playlists.all()) info = Info('', heading='Press start to enter the game', button_label='Start') diff --git a/backend/experiment/rules/musical_preferences.py b/backend/experiment/rules/musical_preferences.py index 6b39050ea..e07b0fa35 100644 --- a/backend/experiment/rules/musical_preferences.py +++ b/backend/experiment/rules/musical_preferences.py @@ -52,16 +52,16 @@ def __init__(self): }, ] - def first_round(self, experiment): - + def first_round(self, block): + consent = Consent( - experiment.consent, + block.consent, title=_('Informed consent'), confirm=_('I consent and continue.'), deny=_('I do not consent.'), url=self.consent_file ) - playlist = Playlist(experiment.playlists.all()) + playlist = Playlist(block.playlists.all()) explainer = Explainer( instruction=_('Welcome to the Musical Preferences experiment!'), steps=[ @@ -118,7 +118,7 @@ def next_round(self, session): else: session.decrement_round() if last_result.question_key == 'audio_check1': - playback = get_test_playback() + playback = get_test_playback() html = HTML(body=render_to_string('html/huang_2022/audio_check.html')) form = Form(form=[BooleanQuestion( key='audio_check2', @@ -175,7 +175,7 @@ def next_round(self, session): })) ) actions = [feedback] - elif n_songs == session.experiment.rounds + 1: + elif n_songs == session.block.rounds + 1: like_results = session.result_set.filter(question_key='like_song') known_songs = session.result_set.filter( question_key='know_song', score=2).count() @@ -231,7 +231,7 @@ def next_round(self, session): playback=playback, feedback_form=form, title=_('Song %(round)s/%(total)s') % { - 'round': n_songs, 'total': session.experiment.rounds}, + 'round': n_songs, 'total': session.block.rounds}, config={ 'response_time': section.duration + .1, } @@ -245,9 +245,9 @@ def calculate_score(self, result, data): else: return super().calculate_score(result, data) - def social_media_info(self, experiment, top_participant, known_songs, n_songs, top_all): + def social_media_info(self, block, top_participant, known_songs, n_songs, top_all): current_url = "{}/{}".format(settings.RELOAD_PARTICIPANT_TARGET, - experiment.slug + block.slug ) def format_songs(songs): return ', '.join( @@ -261,14 +261,14 @@ def format_songs(songs): return ', '.join( 'n_songs': n_songs, 'top_all': format_songs(top_all) }, - 'url': experiment.url or current_url, - 'hashtags': [experiment.hashtag or experiment.slug, "amsterdammusiclab", "citizenscience"] + 'url': block.url or current_url, + 'hashtags': [block.hashtag or block.slug, "amsterdammusiclab", "citizenscience"] } def get_final_view(self, session, top_participant, known_songs, n_songs, top_all): - # finalize experiment + # finalize block social_info = self.social_media_info( - session.experiment, + session.block, top_participant, known_songs, n_songs, diff --git a/backend/experiment/rules/rhythm_battery_final.py b/backend/experiment/rules/rhythm_battery_final.py index 1b9ce090b..87a61c90f 100644 --- a/backend/experiment/rules/rhythm_battery_final.py +++ b/backend/experiment/rules/rhythm_battery_final.py @@ -34,7 +34,7 @@ def __init__(self): }, ] - def first_round(self, experiment): + def first_round(self, block): explainer = Explainer( _('Finally, we would like to ask you to answer some questions about your musical and demographic background.'), steps=[ diff --git a/backend/experiment/rules/rhythm_battery_intro.py b/backend/experiment/rules/rhythm_battery_intro.py index 6ae5a09d5..df98f60ad 100644 --- a/backend/experiment/rules/rhythm_battery_intro.py +++ b/backend/experiment/rules/rhythm_battery_intro.py @@ -127,7 +127,7 @@ def intro_explainer(self): button_label=_("Continue") ) - def first_round(self, experiment): + def first_round(self, block): intro_explainer = self.intro_explainer() explainer = Explainer( instruction=_( diff --git a/backend/experiment/rules/rhythm_discrimination.py b/backend/experiment/rules/rhythm_discrimination.py index 9734cfcb4..64d8a4e7c 100644 --- a/backend/experiment/rules/rhythm_discrimination.py +++ b/backend/experiment/rules/rhythm_discrimination.py @@ -80,8 +80,8 @@ class RhythmDiscrimination(Base): ID = 'RHYTHM_DISCRIMINATION' - def first_round(self, experiment): - """Create data for the first experiment rounds""" + def first_round(self, block): + """Create data for the first block rounds""" explainer = intro_explainer() explainer2 = practice_explainer() @@ -112,7 +112,7 @@ def next_trial_actions(session, round_number): return actions if len(plan) == round_number-1: - return [finalize_experiment(session)] + return [finalize_block(session)] condition = plan[round_number-1] @@ -213,10 +213,10 @@ def plan_stimuli(session): {'rhythm': STIMULI['practice']['nonmetric']['deviant'], 'tag': random.choice(tempi), 'group': '0'}, ] - experiment = metric_deviants + metric_standard + \ + block = metric_deviants + metric_standard + \ nonmetric_deviants + nonmetric_standard - random.shuffle(experiment) - plan = practice + experiment + random.shuffle(block) + plan = practice + block session.save_json_data({'plan': plan}) session.save() @@ -259,7 +259,7 @@ def response_explainer(correct, same, button_label=_('Next fragment')): ) -def finalize_experiment(session): +def finalize_block(session): # we had 4 practice trials and 60 experiment trials percentage = (sum([res.score for res in session.result_set.all()] ) / session.result_set.count()) * 100 diff --git a/backend/experiment/rules/speech2song.py b/backend/experiment/rules/speech2song.py index 918b437e0..589007195 100644 --- a/backend/experiment/rules/speech2song.py +++ b/backend/experiment/rules/speech2song.py @@ -45,7 +45,7 @@ def __init__(self): }, ] - def first_round(self, experiment): + def first_round(self, block): explainer = Explainer( instruction=_("This is an experiment about an auditory illusion."), steps=[ @@ -57,14 +57,14 @@ def first_round(self, experiment): ) # Add consent from file or admin (admin has priority) consent = Consent( - experiment.consent, + block.consent, title=_('Informed consent'), confirm=_('I agree'), deny=_('Stop'), url='consent/consent_speech2song.html' ) - playlist = Playlist(experiment.playlists.all()) + playlist = Playlist(block.playlists.all()) return [ consent, diff --git a/backend/experiment/rules/tafc.py b/backend/experiment/rules/tafc.py index 4651d280e..665fe791b 100644 --- a/backend/experiment/rules/tafc.py +++ b/backend/experiment/rules/tafc.py @@ -1,7 +1,7 @@ """ -Setup experiment data in the admin panel +Setup block data in the admin panel -* Choose a slug for the experiment ('tafc') +* Choose a slug for the block ('tafc') * Upload sound files * Find the root directory name of the uploaded sound files. It is backend/upload on your local machine. On a server, ask the administrator. @@ -21,7 +21,7 @@ * Save * Create experiment - * Admin panel -> Experiments -> Add + * Admin panel -> Blocks -> Add * Choose name: TwoAlternativeForced * Slug: tafc * Rules: TwoAlternativeForced @@ -59,7 +59,7 @@ def __init__(self): "randomize": False }] - def first_round(self, experiment): + def first_round(self, block): """ Returns a list of actions. Actions used here: Explainer, Consent. """ @@ -72,7 +72,7 @@ def first_round(self, experiment): # Add consent, text in admin consent = Consent( - experiment.consent, + block.consent, title='Informed consent', confirm='I agree', deny='Stop', diff --git a/backend/experiment/rules/tests/test_base.py b/backend/experiment/rules/tests/test_base.py index bf00bce89..0bf1eb559 100644 --- a/backend/experiment/rules/tests/test_base.py +++ b/backend/experiment/rules/tests/test_base.py @@ -1,6 +1,6 @@ from django.test import TestCase from django.conf import settings -from experiment.models import Experiment +from experiment.models import Block from session.models import Session from participant.models import Participant from section.models import Playlist @@ -12,13 +12,13 @@ class BaseTest(TestCase): def test_social_media_info(self): reload_participant_target = settings.RELOAD_PARTICIPANT_TARGET slug = 'music-lab' - experiment = Experiment.objects.create( + block = Block.objects.create( name='Music Lab', slug=slug, ) base = Base() social_media_info = base.social_media_info( - experiment=experiment, + block=block, score=100, ) @@ -32,12 +32,12 @@ def test_social_media_info(self): self.assertEqual(social_media_info['hashtags'], ['music-lab', 'amsterdammusiclab', 'citizenscience']) def test_get_play_again_url(self): - experiment = Experiment.objects.create( + block = Block.objects.create( name='Music Lab', slug='music-lab', ) session = Session.objects.create( - experiment=experiment, + block=block, participant=Participant.objects.create(), ) base = Base() @@ -45,7 +45,7 @@ def test_get_play_again_url(self): self.assertEqual(play_again_url, '/music-lab') def test_get_play_again_url_with_participant_id(self): - experiment = Experiment.objects.create( + block = Block.objects.create( name='Music Lab', slug='music-lab', ) @@ -53,7 +53,7 @@ def test_get_play_again_url_with_participant_id(self): participant_id_url='42', ) session = Session.objects.create( - experiment=experiment, + block=block, participant=participant, ) base = Base() @@ -64,8 +64,8 @@ def test_validate_playlist(self): base = Base() playlist = None errors = base.validate_playlist(playlist) - self.assertEqual(errors, ['The experiment must have a playlist.']) + self.assertEqual(errors, ['The block must have a playlist.']) playlist = Playlist.objects.create() errors = base.validate_playlist(playlist) - self.assertEqual(errors, ['The experiment must have at least one section.']) + self.assertEqual(errors, ['The block must have at least one section.']) diff --git a/backend/experiment/rules/tests/test_beat_alignment.py b/backend/experiment/rules/tests/test_beat_alignment.py index 7a373134e..9c44d39e7 100644 --- a/backend/experiment/rules/tests/test_beat_alignment.py +++ b/backend/experiment/rules/tests/test_beat_alignment.py @@ -1,6 +1,6 @@ from django.test import TestCase -from experiment.models import Experiment +from experiment.models import Block from result.models import Result from section.models import Playlist from session.models import Session @@ -32,9 +32,9 @@ def setUpTestData(cls): playlist.csv = csv playlist.update_sections() # rules is BeatAlignment.ID in beat_alignment.py - cls.experiment = Experiment.objects.create( + cls.block = Block.objects.create( rules='BEAT_ALIGNMENT', slug='ba', rounds=13) - cls.experiment.playlists.add(playlist) + cls.block.playlists.add(playlist) def load_json(self, response): '''Asserts response status 200 OK, asserts content type json, loads and returns response.content json in a dictionary''' @@ -42,7 +42,7 @@ def load_json(self, response): self.assertEqual(response['content-type'], 'application/json') return json.loads(response.content) - def test_experiment(self): + def test_block(self): response = self.client.get('/experiment/ba/') response_json = self.load_json(response) self.assertTrue({'id', 'slug', 'name', 'class_name', 'rounds', @@ -70,7 +70,7 @@ def test_experiment(self): self.assertTrue(response_json['status'], 'ok') # Can throw an error if some of the tags in playlist not zero, cannot find a section to play - data = {"experiment_id": self.experiment.id, "playlist_id": "", + data = {"block_id": self.block.id, "playlist_id": "", "json_data": "", "csrfmiddlewaretoken": csrf_token} response = self.client.post('/session/create/', data) response_json = self.load_json(response) @@ -84,7 +84,7 @@ def test_experiment(self): rounds = response_json.get('next_round') assert len(rounds) == 4 assert rounds[0].get('title') == 'Example 1' - rounds_n = self.experiment.rounds # Default 10 + rounds_n = self.block.rounds # Default 10 views_exp = ['TRIAL_VIEW']*(rounds_n) for i in range(len(views_exp)): response = self.client.post( diff --git a/backend/experiment/rules/tests/test_congosamediff.py b/backend/experiment/rules/tests/test_congosamediff.py index a5957b571..7af5d1d68 100644 --- a/backend/experiment/rules/tests/test_congosamediff.py +++ b/backend/experiment/rules/tests/test_congosamediff.py @@ -2,7 +2,7 @@ from django.test import TestCase -from experiment.models import Experiment +from experiment.models import Block from participant.models import Participant from result.models import Result from section.models import Playlist as PlaylistModel, Section, Song @@ -43,13 +43,13 @@ def setUpTestData(self): self.playlist.csv = self.section_csv self.playlist.update_sections() self.participant = Participant.objects.create() - self.experiment = Experiment.objects.create( + self.block = Block.objects.create( name='CongoSameDiff', slug='congosamediff', rounds=4, ) self.session = Session.objects.create( - experiment=self.experiment, + block=self.block, participant=self.participant, playlist=self.playlist ) @@ -61,10 +61,10 @@ def test_initializes_correctly(self): def test_first_round(self): congo_same_diff = CongoSameDiff() - experiment = Experiment(id=1, name='CongoSameDiff', slug='congosamediff_first_round', rounds=4) - experiment.save() - experiment.playlists.set([self.playlist]) - actions = congo_same_diff.first_round(experiment) + block = Block(id=1, name='CongoSameDiff', slug='congosamediff_first_round', rounds=4) + block.save() + block.playlists.set([self.playlist]) + actions = congo_same_diff.first_round(block) assert len(actions) >= 1 assert isinstance(actions[0], PlaylistAction) assert isinstance(actions[1], Explainer) @@ -122,8 +122,8 @@ def test_get_final_round(self): def test_throw_exception_if_trial_without_group(self): congo_same_diff = CongoSameDiff() - experiment = Experiment(id=1, name='CongoSameDiff', slug='congosamediff_first_round', rounds=4) - experiment.save() + block = Block(id=1, name='CongoSameDiff', slug='congosamediff_first_round', rounds=4) + block.save() playlist = PlaylistModel.objects.create(name='CongoSameDiff') Section.objects.create( playlist=playlist, @@ -133,14 +133,14 @@ def test_throw_exception_if_trial_without_group(self): tag='practice_contour', group='' ) - experiment.playlists.set([playlist]) + block.playlists.set([playlist]) with self.assertRaisesRegex(ValueError, "Section no_group should have a group value"): - congo_same_diff.first_round(experiment) + congo_same_diff.first_round(block) def test_throw_exception_if_trial_group_not_int(self): congo_same_diff = CongoSameDiff() - experiment = Experiment(id=1, name='CongoSameDiff', slug='congosamediff_first_round', rounds=4) - experiment.save() + block = Block(id=1, name='CongoSameDiff', slug='congosamediff_first_round', rounds=4) + block.save() playlist = PlaylistModel.objects.create(name='CongoSameDiff') Section.objects.create( playlist=playlist, @@ -150,14 +150,14 @@ def test_throw_exception_if_trial_group_not_int(self): tag='practice_contour', group='not_int_42' ) - experiment.playlists.set([playlist]) + block.playlists.set([playlist]) with self.assertRaisesRegex(ValueError, "Section group_not_int should have a group value containing only digits"): - congo_same_diff.first_round(experiment) + congo_same_diff.first_round(block) def test_throw_exception_if_no_practice_rounds(self): congo_same_diff = CongoSameDiff() - experiment = Experiment(id=1, name='CongoSameDiff', slug='congosamediff_first_round', rounds=4) - experiment.save() + block = Block(id=1, name='CongoSameDiff', slug='congosamediff_first_round', rounds=4) + block.save() playlist = PlaylistModel.objects.create(name='CongoSameDiff') Section.objects.create( playlist=playlist, @@ -167,14 +167,14 @@ def test_throw_exception_if_no_practice_rounds(self): tag='', group='1' ) - experiment.playlists.set([playlist]) + block.playlists.set([playlist]) with self.assertRaisesRegex(ValueError, 'At least one section should have the tag "practice"'): - congo_same_diff.first_round(experiment) + congo_same_diff.first_round(block) def test_throw_exception_if_no_normal_rounds(self): congo_same_diff = CongoSameDiff() - experiment = Experiment(id=1, name='CongoSameDiff', slug='congosamediff_first_round', rounds=4) - experiment.save() + block = Block(id=1, name='CongoSameDiff', slug='congosamediff_first_round', rounds=4) + block.save() playlist = PlaylistModel.objects.create(name='CongoSameDiff') Section.objects.create( playlist=playlist, @@ -184,14 +184,14 @@ def test_throw_exception_if_no_normal_rounds(self): tag='practice_contour', group='42' ) - experiment.playlists.set([playlist]) + block.playlists.set([playlist]) with self.assertRaisesRegex(ValueError, 'At least one section should not have the tag "practice"'): - congo_same_diff.first_round(experiment) + congo_same_diff.first_round(block) def test_throw_combined_exceptions_if_multiple_errors(self): congo_same_diff = CongoSameDiff() - experiment = Experiment(id=1, name='CongoSameDiff', slug='congosamediff_first_round', rounds=4) - experiment.save() + block = Block(id=1, name='CongoSameDiff', slug='congosamediff_first_round', rounds=4) + block.save() playlist = PlaylistModel.objects.create(name='CongoSameDiff') Section.objects.create( playlist=playlist, @@ -217,9 +217,9 @@ def test_throw_combined_exceptions_if_multiple_errors(self): tag='practice_contour', group='42' ) - experiment.playlists.set([playlist]) - with self.assertRaisesRegex(ValueError, "The experiment playlist is not valid: \n- Section group_not_int should have a group value containing only digits\n- Section no_group should have a group value containing only digits\n- At least one section should not have the tag \"practice\""): - congo_same_diff.first_round(experiment) + block.playlists.set([playlist]) + with self.assertRaisesRegex(ValueError, "The block playlist is not valid: \n- Section group_not_int should have a group value containing only digits\n- Section no_group should have a group value containing only digits\n- At least one section should not have the tag \"practice\""): + congo_same_diff.first_round(block) def test_get_total_trials_count(self): congo_same_diff = CongoSameDiff() @@ -332,4 +332,4 @@ def test_invalid_parameters(self): congo_same_diff.get_participant_group_variant(-1, 1, 3, 3) # Negative participant ID congo_same_diff.get_participant_group_variant(1, -1, 3, 3) # Negative group number congo_same_diff.get_participant_group_variant(1, 1, -1, 3) # Negative groups amount - congo_same_diff.get_participant_group_variant(1, 1, 3, -1) # Negative variants amount \ No newline at end of file + congo_same_diff.get_participant_group_variant(1, 1, 3, -1) # Negative variants amount diff --git a/backend/experiment/rules/tests/test_duration_discrimination.py b/backend/experiment/rules/tests/test_duration_discrimination.py index d509faf67..dd42ed713 100644 --- a/backend/experiment/rules/tests/test_duration_discrimination.py +++ b/backend/experiment/rules/tests/test_duration_discrimination.py @@ -1,6 +1,6 @@ from django.test import TestCase -from experiment.models import Experiment +from experiment.models import Block from experiment.rules import Anisochrony, DurationDiscrimination from participant.models import Participant from section.models import Playlist, Section @@ -15,9 +15,9 @@ def setUpTestData(cls): cls.participant = Participant.objects.create() cls.playlist = Playlist.objects.get(name='DurationDiscrimination') cls.playlist.update_sections() - cls.experiment = Experiment.objects.get(name='DurationDiscrimination') + cls.block = Block.objects.get(name='DurationDiscrimination') cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, playlist=cls.playlist ) @@ -47,9 +47,9 @@ def setUpTestData(cls): cls.participant = Participant.objects.create() cls.playlist = Playlist.objects.get(name='Anisochrony') cls.playlist.update_sections() - cls.experiment = Experiment.objects.get(name='Anisochrony') + cls.block = Block.objects.get(name='Anisochrony') cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, playlist=cls.playlist ) diff --git a/backend/experiment/rules/tests/test_hbat.py b/backend/experiment/rules/tests/test_hbat.py index e73725700..a13231a41 100644 --- a/backend/experiment/rules/tests/test_hbat.py +++ b/backend/experiment/rules/tests/test_hbat.py @@ -1,6 +1,6 @@ from django.test import TestCase -from experiment.models import Experiment +from experiment.models import Block from experiment.rules import HBat, BST from participant.models import Participant from result.models import Result @@ -16,9 +16,9 @@ def setUpTestData(cls): cls.participant = Participant.objects.create() cls.playlist = Playlist.objects.get(name='HBAT-BIT') cls.playlist.update_sections() - cls.experiment = Experiment.objects.get(name='HBAT-BIT') + cls.block = Block.objects.get(name='HBAT-BIT') cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, playlist=cls.playlist ) @@ -48,9 +48,9 @@ def setUpTestData(cls): cls.participant = Participant.objects.create() cls.playlist = Playlist.objects.get(name='HBAT-BST') cls.playlist.update_sections() - cls.experiment = Experiment.objects.get(name='HBAT-BST') + cls.block = Block.objects.get(name='HBAT-BST') cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, playlist=cls.playlist ) diff --git a/backend/experiment/rules/tests/test_hooked.py b/backend/experiment/rules/tests/test_hooked.py index 0430ee0f3..e13a1d7b2 100644 --- a/backend/experiment/rules/tests/test_hooked.py +++ b/backend/experiment/rules/tests/test_hooked.py @@ -1,6 +1,6 @@ from django.test import TestCase -from experiment.models import Experiment +from experiment.models import Block from question.musicgens import MUSICGENS_17_W_VARIANTS from participant.models import Participant from result.models import Result @@ -17,11 +17,11 @@ def setUpTestData(cls): cls.participant = Participant.objects.create() def test_hooked(self): - experiment = Experiment.objects.create(name='Hooked', rules='HOOKED', rounds=3) + block = Block.objects.create(name='Hooked', rules='HOOKED', rounds=3) playlist = Playlist.objects.get(name='Eurovision 2021') playlist.update_sections() session = Session.objects.create( - experiment=experiment, + block=block, participant=self.participant, playlist=playlist ) @@ -37,35 +37,35 @@ def test_hooked(self): assert action is not None def test_eurovision(self): - experiment = Experiment.objects.get(name='Hooked-Eurovision') + block = Block.objects.get(name='Hooked-Eurovision') playlist = Playlist.objects.get(name='Eurovision 2021') playlist.update_sections() session = Session.objects.create( - experiment=experiment, + block=block, participant=self.participant, playlist=playlist ) rules = session.block_rules() - for i in range(0, experiment.rounds): + for i in range(0, block.rounds): actions = rules.next_round(session) assert actions def test_thats_my_song(self): musicgen_keys = [q.key for q in MUSICGENS_17_W_VARIANTS] - experiment = Experiment.objects.get(name='ThatsMySong') + block = Block.objects.get(name='ThatsMySong') playlist = Playlist.objects.get(name='ThatsMySong') playlist.update_sections() session = Session.objects.create( - experiment=experiment, + block=block, participant=self.participant, playlist=playlist ) rules = session.block_rules() assert rules.feedback_info() is None - for i in range(0, experiment.rounds): + for i in range(0, block.rounds): actions = rules.next_round(session) - if i == experiment.rounds + 1: + if i == block.rounds + 1: assert len(actions) == 2 assert actions[1].ID == 'FINAL' elif i == 0: @@ -117,11 +117,11 @@ def test_thats_my_song(self): assert actions[2].feedback_form.form[0].key == 'heard_before' def test_hooked_china(self): - experiment = Experiment.objects.get(name='Hooked-China') + block = Block.objects.get(name='Hooked-China') playlist = Playlist.objects.get(name='普通话') playlist.update_sections() session = Session.objects.create( - experiment=experiment, + block=block, participant=self.participant, playlist=playlist ) diff --git a/backend/experiment/rules/tests/test_matching_pairs.py b/backend/experiment/rules/tests/test_matching_pairs.py index f5576a476..1c3a3745d 100644 --- a/backend/experiment/rules/tests/test_matching_pairs.py +++ b/backend/experiment/rules/tests/test_matching_pairs.py @@ -2,7 +2,7 @@ from django.test import TestCase -from experiment.models import Experiment +from experiment.models import Block from participant.models import Participant from result.models import Result from section.models import Playlist @@ -34,9 +34,9 @@ def setUpTestData(cls): cls.playlist.csv = section_csv cls.playlist.update_sections() cls.participant = Participant.objects.create() - cls.experiment = Experiment.objects.create(rules='MATCHING_PAIRS', slug='mpairs', rounds=42) + cls.block = Block.objects.create(rules='MATCHING_PAIRS', slug='mpairs', rounds=42) cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, playlist=cls.playlist ) diff --git a/backend/experiment/rules/tests/test_matching_pairs_fixed.py b/backend/experiment/rules/tests/test_matching_pairs_fixed.py index aff39b98b..e5c77a4c6 100644 --- a/backend/experiment/rules/tests/test_matching_pairs_fixed.py +++ b/backend/experiment/rules/tests/test_matching_pairs_fixed.py @@ -1,6 +1,6 @@ from django.test import TestCase -from experiment.models import Experiment +from experiment.models import Block from participant.models import Participant from section.models import Playlist, Section from session.models import Session @@ -10,7 +10,7 @@ class MatchingPairsFixedTest(TestCase): @classmethod def setUpTestData(self): - self.experiment = Experiment.objects.create( + self.block = Block.objects.create( name='Test Experiment Matching Pairs Fixed', slug='test-experiment-matching-pairs-fixed', rules='matching_pairs_lite' @@ -73,7 +73,7 @@ def test_select_sections_original_degraded(self): unique_hash='testhash' ) self.session = Session.objects.create( - experiment=self.experiment, + block=self.block, participant=self.participant, playlist=self.playlist, ) @@ -158,7 +158,7 @@ def test_select_sections_original_original(self): unique_hash='testhash' ) self.session = Session.objects.create( - experiment=self.experiment, + block=self.block, participant=self.participant, playlist=self.playlist, ) diff --git a/backend/experiment/rules/tests/test_matching_pairs_variants.py b/backend/experiment/rules/tests/test_matching_pairs_variants.py index e4091702d..e731d5875 100644 --- a/backend/experiment/rules/tests/test_matching_pairs_variants.py +++ b/backend/experiment/rules/tests/test_matching_pairs_variants.py @@ -1,7 +1,7 @@ from django.test import TestCase from experiment.actions import Trial -from experiment.models import Experiment +from experiment.models import Block from participant.models import Participant from section.models import Playlist from session.models import Session @@ -28,17 +28,17 @@ def setUp(self): self.participant = Participant.objects.create() def test_lite_version(self): - experiment = Experiment.objects.create( + block = Block.objects.create( rules='MATCHING_PAIRS_LITE', slug='mpairs_lite' ) session = Session.objects.create( - experiment=experiment, + block=block, participant=self.participant, playlist=self.playlist ) first_trial = session.block_rules().get_matching_pairs_trial(session) another_session = Session.objects.create( - experiment=experiment, + block=block, participant=self.participant, playlist=self.playlist ) @@ -49,16 +49,16 @@ def test_lite_version(self): assert first_trial.playback.sections != second_trial.playback.sections def test_fixed_order_sections(self): - experiment = Experiment.objects.create( + block = Block.objects.create( rules='MATCHING_PAIRS_FIXED', slug='mpairs_fixed') session = Session.objects.create( - experiment=experiment, + block=block, participant=self.participant, playlist=self.playlist ) first_trial = session.block_rules().get_matching_pairs_trial(session) another_session = Session.objects.create( - experiment=experiment, + block=block, participant=self.participant, playlist=self.playlist ) diff --git a/backend/experiment/rules/tests/test_musical_preferences.py b/backend/experiment/rules/tests/test_musical_preferences.py index ee70f1ae5..2c618e3cd 100644 --- a/backend/experiment/rules/tests/test_musical_preferences.py +++ b/backend/experiment/rules/tests/test_musical_preferences.py @@ -3,7 +3,7 @@ from experiment.rules.musical_preferences import MusicalPreferences -from experiment.models import Experiment +from experiment.models import Block from participant.models import Participant from result.models import Result from section.models import Playlist @@ -12,7 +12,7 @@ class MusicalPreferencesTest(TestCase): fixtures = ['playlist', 'experiment'] - + @classmethod def setUpTestData(cls): cls.participant = Participant.objects.create() @@ -24,9 +24,9 @@ def setUpTestData(cls): "AwfulArtist,AwfulSong,0.0,10.0,bat/artist5.mp3,0,0,0\n") cls.playlist.csv = csv cls.playlist.update_sections() - cls.experiment = Experiment.objects.create(name='MusicalPreferences', rounds=5) + cls.block = Block.objects.create(name='MusicalPreferences', rounds=5) cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, playlist=cls.playlist ) @@ -59,7 +59,7 @@ def test_preferred_songs_results_without_section(self): ) other_session = Session.objects.create( - experiment=self.experiment, + block=self.block, participant=self.participant, playlist=self.playlist ) @@ -74,8 +74,8 @@ def test_preferred_songs_results_without_section(self): mp = MusicalPreferences() # Go to the last round (top_all = ... caused the error) - for i in range(self.session.experiment.rounds + 1): + for i in range(self.session.block.rounds + 1): self.session.increment_round() - + # get_preferred_songs() called by top_all = ... in the final round should not raise an error - mp.next_round(self.session) \ No newline at end of file + mp.next_round(self.session) diff --git a/backend/experiment/rules/tests/test_rhythm_battery_final.py b/backend/experiment/rules/tests/test_rhythm_battery_final.py index 1f8577ae9..6c09a321a 100644 --- a/backend/experiment/rules/tests/test_rhythm_battery_final.py +++ b/backend/experiment/rules/tests/test_rhythm_battery_final.py @@ -1,14 +1,14 @@ from django.test import TestCase from django.core.files.uploadedfile import SimpleUploadedFile from experiment.actions import Explainer -from experiment.models import Experiment +from experiment.models import Block from experiment.rules.rhythm_battery_final import RhythmBatteryFinal class TestRhythmBatteryFinal(TestCase): @classmethod def setUpTestData(cls): - Experiment.objects.create( + Block.objects.create( name='test_md', slug='MARKDOWN', consent=SimpleUploadedFile( @@ -20,8 +20,8 @@ def test_init(self): self.assertEqual(rhythm_final.ID, 'RHYTHM_BATTERY_FINAL') def test_first_round(self): - experiment = Experiment.objects.first() + block = Block.objects.first() goldMSI = RhythmBatteryFinal() - actions = goldMSI.first_round(experiment) + actions = goldMSI.first_round(block) self.assertIsInstance(actions[0], Explainer) diff --git a/backend/experiment/rules/tests/test_rhythm_battery_intro.py b/backend/experiment/rules/tests/test_rhythm_battery_intro.py index 8a7152953..ec5312e77 100644 --- a/backend/experiment/rules/tests/test_rhythm_battery_intro.py +++ b/backend/experiment/rules/tests/test_rhythm_battery_intro.py @@ -2,7 +2,7 @@ from section.models import Section, Song, Playlist as PlaylistModel from participant.models import Participant from session.models import Session -from experiment.models import Experiment +from experiment.models import Block from experiment.rules.rhythm_battery_intro import RhythmBatteryIntro from experiment.actions import Explainer, Final, Playback, Trial, Form from experiment.actions.form import Form @@ -25,25 +25,25 @@ def setUp(self): filename="not/to_be_found.mp3", tag=0 ) - self.experiment = Experiment.objects.create( + self.block = Block.objects.create( name='test', slug='TEST', ) participant = Participant.objects.create() self.session = Session.objects.create( - experiment=Experiment.objects.first(), + block=Block.objects.first(), participant=participant, playlist=playlist ) def test_first_round(self): listening_conditions = RhythmBatteryIntro() - actions = listening_conditions.first_round(self.experiment) + actions = listening_conditions.first_round(self.block) self.assertIsInstance(actions[0], Explainer) def test_next_round_first_round(self): listening_conditions = RhythmBatteryIntro() - listening_conditions.first_round(self.experiment) + listening_conditions.first_round(self.block) actions = listening_conditions.next_round(self.session) self.assertIsInstance(actions[0], Trial) @@ -54,7 +54,7 @@ def test_next_round_first_round(self): def test_next_round_final_round(self): listening_conditions = RhythmBatteryIntro() - listening_conditions.first_round(self.experiment) + listening_conditions.first_round(self.block) listening_conditions.next_round(self.session) listening_conditions.next_round(self.session) listening_conditions.next_round(self.session) diff --git a/backend/experiment/rules/tests/test_rhythm_discrimination.py b/backend/experiment/rules/tests/test_rhythm_discrimination.py index bc4b935ab..c1461e1b6 100644 --- a/backend/experiment/rules/tests/test_rhythm_discrimination.py +++ b/backend/experiment/rules/tests/test_rhythm_discrimination.py @@ -1,6 +1,6 @@ from django.test import TestCase -from experiment.models import Experiment +from experiment.models import Block from experiment.rules.rhythm_discrimination import next_trial_actions, plan_stimuli from participant.models import Participant from result.models import Result @@ -16,16 +16,16 @@ def setUpTestData(cls): cls.participant = Participant.objects.create() cls.playlist = Playlist.objects.get(name='RhythmDiscrimination') cls.playlist.update_sections() - cls.experiment = Experiment.objects.get(name='RhythmDiscrimination') + cls.block = Block.objects.get(name='RhythmDiscrimination') cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, playlist=cls.playlist ) - + def test_next_trial_actions(self): plan_stimuli(self.session) self.session.final_score = 1 self.session.save() trial = next_trial_actions(self.session, 6) - assert trial \ No newline at end of file + assert trial diff --git a/backend/experiment/rules/tests/test_speech2song.py b/backend/experiment/rules/tests/test_speech2song.py index 26118c58e..397ef86a2 100644 --- a/backend/experiment/rules/tests/test_speech2song.py +++ b/backend/experiment/rules/tests/test_speech2song.py @@ -1,6 +1,6 @@ from django.test import TestCase -from experiment.models import Experiment +from experiment.models import Block from participant.models import Participant from result.models import Result from section.models import Playlist @@ -29,10 +29,10 @@ def setUpTestData(cls): cls.playlist.csv = section_csv cls.playlist.update_sections() cls.participant = Participant.objects.create() - cls.experiment = Experiment.objects.create( + cls.block = Block.objects.create( rules='SPEECH_TO_SONG', slug='s2s', rounds=42) cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, playlist=cls.playlist ) diff --git a/backend/experiment/rules/tests/test_visual_matching_pairs.py b/backend/experiment/rules/tests/test_visual_matching_pairs.py index b417a9241..858eac9dd 100644 --- a/backend/experiment/rules/tests/test_visual_matching_pairs.py +++ b/backend/experiment/rules/tests/test_visual_matching_pairs.py @@ -1,6 +1,6 @@ from django.test import TestCase -from experiment.models import Experiment +from experiment.models import Block from experiment.rules import VisualMatchingPairsGame from participant.models import Participant from section.models import Playlist @@ -28,10 +28,10 @@ def setUpTestData(self): self.sections = list(self.playlist.section_set.filter(tag__contains='vmp')) self.participant = Participant.objects.create() - self.experiment = Experiment.objects.create(rules='VISUAL_MATCHING_PAIRS', slug='vmpairs', rounds=3) + self.block = Block.objects.create(rules='VISUAL_MATCHING_PAIRS', slug='vmpairs', rounds=3) self.session = Session.objects.create( - experiment=self.experiment, + block=self.block, participant=self.participant, playlist=self.playlist ) @@ -75,4 +75,4 @@ def test_next_round_logic(self): self.session.increment_round() next_round = self.rules.next_round(self.session) self.assertEqual(len(next_round), 1) - self.assertEqual(next_round[0].title, 'Visual Tune twins') \ No newline at end of file + self.assertEqual(next_round[0].title, 'Visual Tune twins') diff --git a/backend/experiment/rules/thats_my_song.py b/backend/experiment/rules/thats_my_song.py index eec629657..aa67bb41e 100644 --- a/backend/experiment/rules/thats_my_song.py +++ b/backend/experiment/rules/thats_my_song.py @@ -50,26 +50,26 @@ def get_info_playlist(self, filename): 'group': decade } - def first_round(self, experiment): - actions = super().first_round(experiment) + def first_round(self, block): + actions = super().first_round(block) # skip Consent and Playlist action return [actions[2]] - def next_round(self, session): + def next_round(self, session): """Get action data for the next round""" json_data = session.load_json_data() round_number = self.get_current_round(session) - # If the number of results equals the number of experiment.rounds, + # If the number of results equals the number of block.rounds, # close the session and return data for the final_score view. - if round_number == session.experiment.rounds + self.round_modifier: + if round_number == session.block.rounds + self.round_modifier: # Finish session. session.finish() session.save() # Return a score and final score action. - social_info = self.social_media_info(session.experiment, session.final_score) + social_info = self.social_media_info(session.block, session.final_score) return [ self.get_score(session, round_number - self.round_modifier), Final( diff --git a/backend/experiment/rules/toontjehoger_1_mozart.py b/backend/experiment/rules/toontjehoger_1_mozart.py index d525cd429..e7117f113 100644 --- a/backend/experiment/rules/toontjehoger_1_mozart.py +++ b/backend/experiment/rules/toontjehoger_1_mozart.py @@ -37,8 +37,8 @@ class ToontjeHoger1Mozart(Base): ANSWER_URL1 = "/images/experiments/toontjehoger/mozart-effect1-answer.webp" ANSWER_URL2 = "/images/experiments/toontjehoger/mozart-effect2-answer.webp" - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" # 1. Explain game. explainer = Explainer( diff --git a/backend/experiment/rules/toontjehoger_2_preverbal.py b/backend/experiment/rules/toontjehoger_2_preverbal.py index c71c912eb..82cb4d18a 100644 --- a/backend/experiment/rules/toontjehoger_2_preverbal.py +++ b/backend/experiment/rules/toontjehoger_2_preverbal.py @@ -58,8 +58,8 @@ def validate_playlist(self, playlist: Playlist): return errors - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" # 1. Explain game. explainer = Explainer( @@ -97,7 +97,7 @@ def get_spectrogram_info(self): button_label="Volgende", ) return info - + def next_round(self, session): """Get action data for the next round""" @@ -114,7 +114,7 @@ def next_round(self, session): # Final return self.get_final_round(session) - + def get_score(self, session, rounds_passed): # Feedback last_result = session.last_result() @@ -139,7 +139,7 @@ def get_score(self, session, rounds_passed): config = {'show_total_score': True} score = Score(session, config=config, feedback=feedback) return [score] - + def get_round1(self, session): # Question key = 'expected_spectrogram' @@ -212,7 +212,7 @@ def get_round1_playback(self, session): title=self.TITLE ) return [trial] - + def get_round2(self, round, session): # Get sections @@ -260,10 +260,10 @@ def get_round2(self, round, session): title=self.TITLE, ) return [trial] - + def calculate_score(self, result, data): return self.SCORE_CORRECT if result.expected_response == result.given_response else self.SCORE_WRONG - + def get_final_round(self, session): # Finish session. diff --git a/backend/experiment/rules/toontjehoger_3_plink.py b/backend/experiment/rules/toontjehoger_3_plink.py index 6295bc483..0373c5880 100644 --- a/backend/experiment/rules/toontjehoger_3_plink.py +++ b/backend/experiment/rules/toontjehoger_3_plink.py @@ -61,15 +61,15 @@ def validate_era_and_mood(self, sections): ) return errors - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" # 1. Explain game. explainer = Explainer( instruction="Muziekherkenning", steps=[ Step("Je krijgt {} zeer korte muziekfragmenten te horen.".format( - experiment.rounds)), + block.rounds)), Step("Ken je het nummer? Noem de juiste artiest en titel!"), Step( "Weet je het niet? Beantwoord dan extra vragen over de tijdsperiode en emotie van het nummer.") @@ -91,8 +91,8 @@ def next_round(self, session): if rounds_passed == 0: return self.get_plink_round(session) - # Round 2-experiments.rounds - if rounds_passed < session.experiment.rounds: + # Round 2-blocks.rounds + if rounds_passed < session.block.rounds: return self.get_plink_round(session, present_score=True) # Final @@ -158,7 +158,7 @@ def get_score_view(self, session): config = {'show_total_score': True} round_number = session.get_relevant_results(['plink']).count() - 1 score_title = "Ronde %(number)d / %(total)d" %\ - {'number': round_number+1, 'total': session.experiment.rounds} + {'number': round_number+1, 'total': session.block.rounds} return Score(session, config=config, feedback=feedback, score=score, title=score_title) def get_plink_round(self, session, present_score=False): diff --git a/backend/experiment/rules/toontjehoger_4_absolute.py b/backend/experiment/rules/toontjehoger_4_absolute.py index c0cffea11..fc9689341 100644 --- a/backend/experiment/rules/toontjehoger_4_absolute.py +++ b/backend/experiment/rules/toontjehoger_4_absolute.py @@ -26,8 +26,8 @@ class ToontjeHoger4Absolute(Base): # number of songs (each with a,b,c version) in the playlist PLAYLIST_ITEMS = 13 - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" # 1. Explain game. explainer = Explainer( @@ -57,7 +57,7 @@ def next_round(self, session): return self.get_round(session) # Round 2 - 4 - if rounds_passed < session.experiment.rounds: + if rounds_passed < session.block.rounds: return [*self.get_score(session), *self.get_round(session)] # Final @@ -163,9 +163,9 @@ def get_final_round(self, session): # Final final_text = "Dat bleek toch even lastig!" - if session.final_score >= session.experiment.rounds * 0.8 * self.SCORE_CORRECT: + if session.final_score >= session.block.rounds * 0.8 * self.SCORE_CORRECT: final_text = "Goed gedaan! Jouw absolute gehoor is uitstekend!" - elif session.final_score >= session.experiment.rounds * 0.5 * self.SCORE_CORRECT: + elif session.final_score >= session.block.rounds * 0.5 * self.SCORE_CORRECT: final_text = "Goed gedaan! Jouw absolute gehoor is best OK!" final = Final( diff --git a/backend/experiment/rules/toontjehoger_5_tempo.py b/backend/experiment/rules/toontjehoger_5_tempo.py index 3f0d965ac..89bbe7438 100644 --- a/backend/experiment/rules/toontjehoger_5_tempo.py +++ b/backend/experiment/rules/toontjehoger_5_tempo.py @@ -24,8 +24,8 @@ class ToontjeHoger5Tempo(Base): SCORE_CORRECT = 20 SCORE_WRONG = 0 - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" # 1. Explain game. explainer = Explainer( @@ -57,7 +57,7 @@ def next_round(self, session): return self.get_round(session, rounds_passed) # Round 2 - if rounds_passed < session.experiment.rounds: + if rounds_passed < session.block.rounds: return [*self.get_score(session), *self.get_round(session, rounds_passed)] # Final @@ -232,9 +232,9 @@ def get_final_round(self, session): # Final final_text = "Dat bleek toch even lastig!" - if session.final_score >= session.experiment.rounds * 0.8 * self.SCORE_CORRECT: + if session.final_score >= session.block.rounds * 0.8 * self.SCORE_CORRECT: final_text = "Goed gedaan! Jouw timing is uitstekend!" - elif session.final_score >= session.experiment.rounds * 0.5 * self.SCORE_CORRECT: + elif session.final_score >= session.block.rounds * 0.5 * self.SCORE_CORRECT: final_text = "Goed gedaan! Jouw timing is best OK!" final = Final( diff --git a/backend/experiment/rules/toontjehoger_6_relative.py b/backend/experiment/rules/toontjehoger_6_relative.py index 4b8f96a38..f4b734e94 100644 --- a/backend/experiment/rules/toontjehoger_6_relative.py +++ b/backend/experiment/rules/toontjehoger_6_relative.py @@ -38,8 +38,8 @@ def validate_playlist(self, playlist: Playlist): errors.append('The sections should have the tags a, b, c') return errors - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" # 1. Explain game. explainer = Explainer( diff --git a/backend/experiment/rules/toontjehoger_home.py b/backend/experiment/rules/toontjehoger_home.py index 3189ddf2b..c655b8d40 100644 --- a/backend/experiment/rules/toontjehoger_home.py +++ b/backend/experiment/rules/toontjehoger_home.py @@ -70,12 +70,12 @@ class ToontjeHogerHome(Base): ), ] - def first_round(self, experiment): - """Create data for the first experiment round""" + def first_round(self, block): + """Create data for the first block round""" # Session history sessions = self.get_sessions(participant) # To be fixed in the future - next_experiment_slug = self.get_next_experiment_slug(sessions) + next_block_slug = self.get_next_block_slug(sessions) # Score score = self.get_score(sessions) @@ -95,10 +95,10 @@ def first_round(self, experiment): score_class = "diamond" # Main button shows - # - 'next experiment' when user does not have completed all experiments yet - # - 'random experiment' when user has completed all experiments - main_button_label = "Volgende experiment" if next_experiment_slug else "Willekeurig experiment" - main_button_url = "/{}".format(next_experiment_slug) if next_experiment_slug else random.choice([ + # - 'next experiment' when user does not have completed all blocks yet + # - 'random experiment' when user has completed all blocks + main_button_label = "Volgende experiment" if next_block_slug else "Willekeurig experiment" + main_button_url = "/{}".format(next_block_slug) if next_block_slug else random.choice([ experiment.slug for experiment in self.EXPERIMENT_DATA]) # Home @@ -143,25 +143,25 @@ def get_score(self, sessions): def get_sessions(self, participant): from session.models import Session - from experiment.models import Experiment + from experiment.models import Block - experiment_slugs = [ - experiment.slug for experiment in self.EXPERIMENT_DATA] + block_slugs = [ + block.slug for block in self.EXPERIMENT_DATA] - experiment_ids = Experiment.objects.filter(slug__in=experiment_slugs) + block_ids = Block.objects.filter(slug__in=block_slugs) sessions = Session.objects.filter(participant=participant, - experiment_id__in=experiment_ids) + block_id__in=block_ids) return sessions - def get_next_experiment_slug(self, sessions): - experiment_slugs = [ - experiment.slug for experiment in self.EXPERIMENT_DATA] + def get_next_block_slug(self, sessions): + block_slugs = [ + block.slug for block in self.EXPERIMENT_DATA] for session in sessions: - if session.experiment.slug in experiment_slugs: - experiment_slugs.remove(session.experiment.slug) + if session.block.slug in block_slugs: + block_slugs.remove(session.block.slug) - if len(experiment_slugs) > 0: - return experiment_slugs[0] + if len(block_slugs) > 0: + return block_slugs[0] return '' diff --git a/backend/experiment/rules/toontjehogerkids_1_mozart.py b/backend/experiment/rules/toontjehogerkids_1_mozart.py index 4ae595e83..0131bbd62 100644 --- a/backend/experiment/rules/toontjehogerkids_1_mozart.py +++ b/backend/experiment/rules/toontjehogerkids_1_mozart.py @@ -15,8 +15,8 @@ class ToontjeHogerKids1Mozart(ToontjeHoger1Mozart): ANSWER_URL1 = "/images/experiments/toontjehogerkids/mozart-effect1-answer.webp" ANSWER_URL2 = "/images/experiments/toontjehogerkids/mozart-effect2-answer.webp" - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" # 1. Explain game. explainer = Explainer( diff --git a/backend/experiment/rules/toontjehogerkids_2_preverbal.py b/backend/experiment/rules/toontjehogerkids_2_preverbal.py index ad5c03fd9..044fe0b5a 100644 --- a/backend/experiment/rules/toontjehogerkids_2_preverbal.py +++ b/backend/experiment/rules/toontjehogerkids_2_preverbal.py @@ -12,8 +12,8 @@ class ToontjeHogerKids2Preverbal(ToontjeHoger2Preverbal): ID = 'TOONTJE_HOGER_KIDS_2_PREVERBAL' - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" # 1. Explain game. explainer = Explainer( diff --git a/backend/experiment/rules/toontjehogerkids_3_plink.py b/backend/experiment/rules/toontjehogerkids_3_plink.py index 047bea70f..655ada16b 100644 --- a/backend/experiment/rules/toontjehogerkids_3_plink.py +++ b/backend/experiment/rules/toontjehogerkids_3_plink.py @@ -27,14 +27,14 @@ class ToontjeHogerKids3Plink(ToontjeHoger3Plink): def validate_era_and_mood(self, sections): return [] - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" explainer = Explainer( instruction="Muziekherkenning", steps=[ Step("Je hoort zo een heel kort stukje van {} liedjes.".format( - experiment.rounds)), + block.rounds)), Step("Herken je de liedjes? Kies dan steeds de juiste artiest en titel!"), Step( "Weet je het niet zeker? Doe dan maar een gok.") @@ -73,7 +73,7 @@ def get_score_view(self, session): config = {'show_total_score': True} round_number = session.get_relevant_results(['plink']).count() - 1 score_title = "Ronde %(number)d / %(total)d" %\ - {'number': round_number+1, 'total': session.experiment.rounds} + {'number': round_number+1, 'total': session.block.rounds} return Score(session, config=config, feedback=feedback, score=score, title=score_title) def get_plink_round(self, session, present_score=False): diff --git a/backend/experiment/rules/toontjehogerkids_4_absolute.py b/backend/experiment/rules/toontjehogerkids_4_absolute.py index 97090ce7f..ae1df8d5f 100644 --- a/backend/experiment/rules/toontjehogerkids_4_absolute.py +++ b/backend/experiment/rules/toontjehogerkids_4_absolute.py @@ -9,8 +9,8 @@ class ToontjeHogerKids4Absolute(ToontjeHoger4Absolute): ID = 'TOONTJE_HOGER_KIDS_4_ABSOLUTE' PLAYLIST_ITEMS = 12 - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" # 1. Explain game. explainer = Explainer( @@ -44,7 +44,7 @@ def get_final_round(self, session): # Final final_text = "Best lastig!" - if session.final_score >= session.experiment.rounds * 0.5 * self.SCORE_CORRECT: + if session.final_score >= session.block.rounds * 0.5 * self.SCORE_CORRECT: final_text = "Goed gedaan!" final = Final( diff --git a/backend/experiment/rules/toontjehogerkids_5_tempo.py b/backend/experiment/rules/toontjehogerkids_5_tempo.py index b96c5fbdf..090ad71a5 100644 --- a/backend/experiment/rules/toontjehogerkids_5_tempo.py +++ b/backend/experiment/rules/toontjehogerkids_5_tempo.py @@ -13,8 +13,8 @@ class ToontjeHogerKids5Tempo(ToontjeHoger5Tempo): ID = 'TOONTJE_HOGER_KIDS_5_TEMPO' - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" # 1. Explain game. explainer = Explainer( @@ -105,7 +105,7 @@ def get_final_round(self, session): # Final final_text = "Best lastig!" - if session.final_score >= session.experiment.rounds * 0.5 * self.SCORE_CORRECT: + if session.final_score >= session.block.rounds * 0.5 * self.SCORE_CORRECT: final_text = "Goed gedaan!" final = Final( diff --git a/backend/experiment/rules/toontjehogerkids_6_relative.py b/backend/experiment/rules/toontjehogerkids_6_relative.py index b637e748c..35a1e0d8a 100644 --- a/backend/experiment/rules/toontjehogerkids_6_relative.py +++ b/backend/experiment/rules/toontjehogerkids_6_relative.py @@ -11,8 +11,8 @@ class ToontjeHogerKids6Relative(ToontjeHoger6Relative): ID = 'TOONTJE_HOGER_KIDS_6_RELATIVE' - def first_round(self, experiment): - """Create data for the first experiment rounds.""" + def first_round(self, block): + """Create data for the first block rounds.""" # 1. Explain game. explainer = Explainer( diff --git a/backend/experiment/rules/visual_matching_pairs.py b/backend/experiment/rules/visual_matching_pairs.py index 493f5c596..1775ea681 100644 --- a/backend/experiment/rules/visual_matching_pairs.py +++ b/backend/experiment/rules/visual_matching_pairs.py @@ -33,17 +33,17 @@ def __init__(self): }, ] - def first_round(self, experiment): + def first_round(self, block): # Add consent from file or admin (admin has priority) consent = Consent( - experiment.consent, + block.consent, title=_('Informed consent'), confirm=_('I agree'), deny=_('Stop'), url='consent/consent_matching_pairs.html' ) - playlist = Playlist(experiment.playlists.all()) + playlist = Playlist(block.playlists.all()) explainer = Explainer( instruction='', @@ -63,7 +63,7 @@ def first_round(self, experiment): ] def next_round(self, session): - if session.rounds_passed() < 1: + if session.rounds_passed() < 1: trials = self.get_questionnaire(session) if trials: intro_questions = Explainer( @@ -78,7 +78,7 @@ def next_round(self, session): session.final_score += session.result_set.filter( question_key='visual_matching_pairs').last().score session.save() - social_info = self.social_media_info(session.experiment, session.final_score) + social_info = self.social_media_info(session.block, session.final_score) social_info['apps'].append('clipboard') score = Final( session, diff --git a/backend/experiment/serializers.py b/backend/experiment/serializers.py index 58b49a531..d79247c65 100644 --- a/backend/experiment/serializers.py +++ b/backend/experiment/serializers.py @@ -67,66 +67,66 @@ def serialize_phase( phase: Phase, participant: Participant ) -> dict: - grouped_experiments = list(GroupedBlock.objects.filter( - phase_id=phase.id).order_by('order')) + grouped_blocks = list(GroupedBlock.objects.filter( + phase_id=phase.id).order_by('index')) if phase.randomize: - shuffle(grouped_experiments) + shuffle(grouped_blocks) - next_experiment = get_upcoming_experiment( - grouped_experiments, participant, phase.dashboard) + next_block = get_upcoming_block( + grouped_blocks, participant, phase.dashboard) - total_score = get_total_score(grouped_experiments, participant) + total_score = get_total_score(grouped_blocks, participant) - if not next_experiment: + if not next_block: return None return { - 'dashboard': [serialize_experiment(experiment.block, participant) for experiment in grouped_experiments] if phase.dashboard else [], - 'nextExperiment': next_experiment, + 'dashboard': [serialize_block(block.block, participant) for block in grouped_blocks] if phase.dashboard else [], + 'nextBlock': next_block, 'totalScore': total_score } -def serialize_experiment(experiment_object: Block, participant: Participant): +def serialize_block(block_object: Block, participant: Participant): return { - 'slug': experiment_object.slug, - 'name': experiment_object.name, - 'description': experiment_object.description, - 'image': serialize_image(experiment_object.image) if experiment_object.image else None, + 'slug': block_object.slug, + 'name': block_object.name, + 'description': block_object.description, + 'image': serialize_image(block_object.image) if block_object.image else None, } -def get_upcoming_experiment(experiment_list, participant, repeat_allowed=True): - ''' return next experiment with minimum finished sessions for this participant - if repeated experiments are not allowed (dashboard=False) and there are only finished sessions, return None ''' +def get_upcoming_block(block_list, participant, repeat_allowed=True): + ''' return next block with minimum finished sessions for this participant + if repeated blocks are not allowed (dashboard=False) and there are only finished sessions, return None ''' finished_session_counts = [get_finished_session_count( - experiment.experiment, participant) for experiment in experiment_list] + block.block, participant) for block in block_list] minimum_session_count = min(finished_session_counts) if not repeat_allowed and minimum_session_count != 0: return None - return serialize_experiment(experiment_list[finished_session_counts.index(minimum_session_count)].experiment, participant) + return serialize_block(block_list[finished_session_counts.index(minimum_session_count)].block, participant) -def get_started_session_count(experiment, participant): - ''' Get the number of started sessions for this experiment and participant ''' +def get_started_session_count(block, participant): + ''' Get the number of started sessions for this block and participant ''' count = Session.objects.filter( - experiment=experiment, participant=participant).count() + block=block, participant=participant).count() return count -def get_finished_session_count(experiment, participant): - ''' Get the number of finished sessions for this experiment and participant ''' +def get_finished_session_count(block, participant): + ''' Get the number of finished sessions for this block and participant ''' count = Session.objects.filter( - experiment=experiment, participant=participant, finished_at__isnull=False).count() + block=block, participant=participant, finished_at__isnull=False).count() return count -def get_total_score(grouped_experiments, participant): - '''Calculate total score of all experiments on the dashboard''' +def get_total_score(grouped_blocks, participant): + '''Calculate total score of all blocks on the dashboard''' total_score = 0 - for grouped_experiment in grouped_experiments: - sessions = Session.objects.filter(experiment=grouped_experiment.experiment, participant=participant) + for grouped_block in grouped_blocks: + sessions = Session.objects.filter(block=grouped_block.block, participant=participant) for session in sessions: total_score += session.final_score return total_score diff --git a/backend/experiment/static/experiment_admin.css b/backend/experiment/static/block_admin.css similarity index 100% rename from backend/experiment/static/experiment_admin.css rename to backend/experiment/static/block_admin.css diff --git a/backend/experiment/static/experiment_admin.js b/backend/experiment/static/block_admin.js similarity index 100% rename from backend/experiment/static/experiment_admin.js rename to backend/experiment/static/block_admin.js diff --git a/backend/experiment/templates/collection-dashboard-sessions.html b/backend/experiment/templates/collection-dashboard-sessions.html index d99092397..6edf80b47 100644 --- a/backend/experiment/templates/collection-dashboard-sessions.html +++ b/backend/experiment/templates/collection-dashboard-sessions.html @@ -12,14 +12,14 @@ Finished Final score Results - Preview + Preview - - {% for session in sessions %} - + + {% for session in sessions %} +

@@ -30,9 +30,9 @@

- - {{ session.experiment.name }} -

+ + {{ session.block.name }} +

@@ -45,15 +45,15 @@ {{ session.participant.id }} - {% endif %} -

+ {% endif %} +

{{ session.started_at }}

- + {% if session.finished_at %}

{{ session.finished_at }}

{% else %} @@ -73,9 +73,9 @@ - + - +
@@ -83,11 +83,11 @@ {% for result in session.export_results %}

Result : {{ result.id }}

- Session: {{ session.id }}
- Participant: {{ session.participant.participant_id_url }}
- Experiment: {{ session.experiment.name }} + Session: {{ session.id }}
+ Participant: {{ session.participant.participant_id_url }}
+ Experiment: {{ session.block.name }}

- +
 Created:            {{ result.created_at }}
 Section:            {{ result.section.filename }}
@@ -103,11 +103,11 @@ 


{{ result.json_data|json_script:result.id }} - - + + {% endfor %}

diff --git a/backend/experiment/templates/collection-dashboard.html b/backend/experiment/templates/collection-dashboard.html index 55193aa43..583bd987b 100644 --- a/backend/experiment/templates/collection-dashboard.html +++ b/backend/experiment/templates/collection-dashboard.html @@ -16,8 +16,8 @@ background-color: var(--body-bg) } td, th { - vertical-align: middle; - width: auto; + vertical-align: middle; + width: auto; } td { padding-left: 1rem; @@ -30,7 +30,7 @@ width: 8rem; } td.participants-overview { - max-width: 25vw; + max-width: 25vw; } pre { color: black; @@ -43,23 +43,23 @@ font-weight: 600; margin: 2rem 0px 2rem 0px; } - .pop-up { + .pop-up { background-color: var(--body-bg); box-shadow: 0px 0px 10px var(--primary); position: fixed; - - padding: 2rem; + + padding: 2rem; border-style: solid; border-color: var(--primary); - border-width: 1px; + border-width: 1px; border-radius: 1px; overflow-y: scroll; } .pop-up-big { top: 4.2rem; - left: 10vw; + left: 10vw; width: calc(80vw - 34px); - height: 80vh; + height: 80vh; } .pop-up-small { @@ -73,10 +73,10 @@

Experiments in collection: {{ collection.name }}

- + -
+

@@ -89,19 +89,19 @@

Experiments in collection: {{ collection.name }}

Times finished Participant count Participants * - Sessions + Sessions - + - {% for exp in experiments %} + {% for exp in experiments %}

- + {{ exp.name }} -

+

@@ -120,7 +120,7 @@

Experiments in collection: {{ collection.name }}

{% for participant in exp.participants %} - + {% if participant.participant_id_url %} {{ participant.participant_id_url }} @@ -132,20 +132,20 @@

Experiments in collection: {{ collection.name }}

{% endif %} , {% endfor %} -

+

- + - + {% endfor %} - +

* Click to edit in a new window

diff --git a/backend/experiment/tests/test_admin_experiment.py b/backend/experiment/tests/test_admin_experiment.py index 7c7ffdad3..4e75717fa 100644 --- a/backend/experiment/tests/test_admin_experiment.py +++ b/backend/experiment/tests/test_admin_experiment.py @@ -6,15 +6,15 @@ from django.contrib.admin.sites import AdminSite from django.urls import reverse from django.utils.html import format_html -from experiment.admin import ExperimentAdmin, ExperimentCollectionAdmin, PhaseAdmin -from experiment.models import Experiment, ExperimentCollection, Phase, GroupedBlock +from experiment.admin import BlockAdmin, ExperimentCollectionAdmin, PhaseAdmin +from experiment.models import Block, ExperimentCollection, Phase, GroupedBlock from participant.models import Participant from result.models import Result from session.models import Session # Expected field count per model -EXPECTED_EXPERIMENT_FIELDS = 15 +EXPECTED_BLOCK_FIELDS = 15 EXPECTED_SESSION_FIELDS = 9 EXPECTED_RESULT_FIELDS = 12 EXPECTED_PARTICIPANT_FIELDS = 5 @@ -31,13 +31,13 @@ class TestAdminExperiment(TestCase): @classmethod def setUpTestData(cls): - Experiment.objects.create( + Block.objects.create( name='test', slug='TEST' ) Participant.objects.create() Session.objects.create( - experiment=Experiment.objects.first(), + block=Block.objects.first(), participant=Participant.objects.first() ) Result.objects.create( @@ -45,14 +45,15 @@ def setUpTestData(cls): ) def setUp(self): - self.admin = ExperimentAdmin(model=Experiment, - admin_site=AdminSite - ) + self.admin = BlockAdmin( + model=Block, + admin_site=AdminSite, + ) - def test_experiment_model_fields(self): - experiment = model_to_dict(Experiment.objects.first()) - experiment_fields = [key for key in experiment] - self.assertEqual(len(experiment_fields), EXPECTED_EXPERIMENT_FIELDS) + def test_block_model_fields(self): + block = model_to_dict(Block.objects.first()) + block_fields = [key for key in block] + self.assertEqual(len(block_fields), EXPECTED_BLOCK_FIELDS) def test_session_model_fields(self): session = model_to_dict(Session.objects.first()) @@ -69,31 +70,31 @@ def test_participant_model(self): participant_fields = [key for key in participant] self.assertEqual(len(participant_fields), EXPECTED_PARTICIPANT_FIELDS) - def test_experiment_link(self): - experiment = Experiment.objects.create(name="Test Experiment") + def test_block_link(self): + block = Block.objects.create(name="Test Block") site = AdminSite() - admin = ExperimentAdmin(experiment, site) - link = admin.experiment_name_link(experiment) + admin = BlockAdmin(block, site) + link = admin.block_name_link(block) expected_url = reverse( - "admin:experiment_experiment_change", args=[experiment.pk]) - expected_name = "Test Experiment" + "admin:experiment_block_change", args=[block.pk]) + expected_name = "Test Block" expected_link = format_html( '
{}', expected_url, expected_name) self.assertEqual(link, expected_link) -class TestAdminExperimentExport(TestCase): +class TestAdminBlockExport(TestCase): fixtures = ['playlist', 'experiment'] @classmethod def setUpTestData(cls): cls.participant = Participant.objects.create(unique_hash=42) - cls.experiment = Experiment.objects.get(name='Hooked-China') - for playlist in cls.experiment.playlists.all(): + cls.block = Block.objects.get(name='Hooked-China') + for playlist in cls.block.playlists.all(): playlist.update_sections() cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, ) for i in range(5): @@ -111,12 +112,13 @@ def setUpTestData(cls): def setUp(self): self.client = Client() - self.admin = ExperimentAdmin(model=Experiment, - admin_site=AdminSite - ) + self.admin = BlockAdmin( + model=Block, + admin_site=AdminSite + ) def test_admin_export(self): - response = self.admin.export(request, self.experiment) + response = self.admin.export(request, self.block) zip_buffer = BytesIO(response.content) with ZipFile(zip_buffer, 'r') as test_zip: # Test files inside zip @@ -145,7 +147,7 @@ def test_admin_export(self): these_sessions = json.loads(test_zip.read('sessions.json').decode("utf-8")) self.assertEqual(len(these_sessions), 1) - self.assertEqual(these_sessions[0]['fields']['experiment'], 14) + self.assertEqual(these_sessions[0]['fields']['block'], 14) these_songs = json.loads(test_zip.read('songs.json').decode("utf-8")) self.assertEqual(len(these_songs), 100) @@ -160,7 +162,7 @@ def test_export_table_includes_question_key(self): export_options = ['convert_result_json'] # Adjust based on your needs # Call the method under test - rows, fieldnames = self.experiment.export_table(session_keys, result_keys, export_options) + rows, fieldnames = self.block.export_table(session_keys, result_keys, export_options) # Assert that 'question_key' is in the fieldnames and check its value in rows self.assertIn('question_key', fieldnames) @@ -232,27 +234,27 @@ def test_related_series_with_series(self): expected_related_series = format_html('{}', expected_url, series.name) self.assertEqual(related_series, expected_related_series) - def test_experiments_with_no_experiments(self): + def test_blocks_with_no_blocks(self): series = ExperimentCollection.objects.create(name='Test Series') phase = Phase.objects.create( name='Test Group', index=1, randomize=False, dashboard=True, series=series) - experiments = self.admin.experiments(phase) - self.assertEqual(experiments, "No experiments") + blocks = self.admin.blocks(phase) + self.assertEqual(blocks, "No blocks") - def test_experiments_with_experiments(self): + def test_blocks_with_blocks(self): series = ExperimentCollection.objects.create(name='Test Series') phase = Phase.objects.create( name='Test Group', index=1, randomize=False, dashboard=True, series=series) - experiment1 = Experiment.objects.create(name='Experiment 1', slug='experiment-1') - experiment2 = Experiment.objects.create(name='Experiment 2', slug='experiment-2') - grouped_experiment1 = GroupedBlock.objects.create(phase=phase, experiment=experiment1) - grouped_experiment2 = GroupedBlock.objects.create(phase=phase, experiment=experiment2) + block1 = Block.objects.create(name='Block 1', slug='block-1') + block2 = Block.objects.create(name='Block 2', slug='block-2') + grouped_block1 = GroupedBlock.objects.create(phase=phase, block=block1) + grouped_block2 = GroupedBlock.objects.create(phase=phase, block=block2) - experiments = self.admin.experiments(phase) - expected_experiments = format_html( + blocks = self.admin.blocks(phase) + expected_blocks = format_html( ', '.join([ - f'{experiment.experiment.name}' - for experiment in [grouped_experiment1, grouped_experiment2] + f'{block.block.name}' + for block in [grouped_block1, grouped_block2] ]) ) - self.assertEqual(experiments, expected_experiments) + self.assertEqual(blocks, expected_blocks) diff --git a/backend/experiment/tests/test_forms.py b/backend/experiment/tests/test_forms.py index babe045f6..b306c4fac 100644 --- a/backend/experiment/tests/test_forms.py +++ b/backend/experiment/tests/test_forms.py @@ -1,11 +1,11 @@ from django.test import TestCase -from experiment.forms import ExperimentForm, ExportForm, TemplateForm, BLOCK_RULES, SESSION_CHOICES, RESULT_CHOICES, EXPORT_OPTIONS, TEMPLATE_CHOICES +from experiment.forms import BlockForm, ExportForm, TemplateForm, BLOCK_RULES, SESSION_CHOICES, RESULT_CHOICES, EXPORT_OPTIONS, TEMPLATE_CHOICES -class ExperimentFormTest(TestCase): +class BlockFormTest(TestCase): def test_form_fields(self): - form = ExperimentForm() + form = BlockForm() self.assertIn('name', form.fields) self.assertIn('slug', form.fields) self.assertIn('active', form.fields) @@ -15,7 +15,7 @@ def test_form_fields(self): self.assertIn('playlists', form.fields) def test_rules_field_choices(self): - form = ExperimentForm() + form = BlockForm() expected_choices = [(i, BLOCK_RULES[i].__name__) for i in BLOCK_RULES] expected_choices.append(("", "---------")) self.assertEqual(form.fields['rules'].choices, sorted(expected_choices)) diff --git a/backend/experiment/tests/test_model.py b/backend/experiment/tests/test_model.py index 609105119..d4f67a9c6 100644 --- a/backend/experiment/tests/test_model.py +++ b/backend/experiment/tests/test_model.py @@ -2,13 +2,13 @@ from image.models import Image from theme.models import ThemeConfig -from experiment.models import Experiment, ExperimentCollection +from experiment.models import Block, ExperimentCollection from participant.models import Participant from session.models import Session from result.models import Result -class ExperimentModelTest(TestCase): +class BlockModelTest(TestCase): @classmethod def setUpTestData(cls): logo_image = Image.objects.create( @@ -25,11 +25,11 @@ def setUpTestData(cls): logo_image=logo_image, background_image=background_image, ) - Experiment.objects.create( - name='Test Experiment', - description='Test experiment description', - slug='test-experiment', - url='https://example.com/experiment', + Block.objects.create( + name='Test Block', + description='Test block description', + slug='test-block', + url='https://example.com/block', hashtag='test', rounds=5, bonus_points=10, @@ -38,40 +38,40 @@ def setUpTestData(cls): theme_config=ThemeConfig.objects.get(name='Default'), ) - def test_experiment_str(self): - experiment = Experiment.objects.get(name='Test Experiment') - self.assertEqual(str(experiment), 'Test Experiment') + def test_block_str(self): + block = Block.objects.get(name='Test Block') + self.assertEqual(str(block), 'Test Block') - def test_experiment_session_count(self): - experiment = Experiment.objects.get(name='Test Experiment') - self.assertEqual(experiment.session_count(), 0) + def test_block_session_count(self): + block = Block.objects.get(name='Test Block') + self.assertEqual(block.session_count(), 0) - def test_experiment_playlist_count(self): - experiment = Experiment.objects.get(name='Test Experiment') - self.assertEqual(experiment.playlist_count(), 0) + def test_block_playlist_count(self): + block = Block.objects.get(name='Test Block') + self.assertEqual(block.playlist_count(), 0) - def test_experiment_current_participants(self): - experiment = Experiment.objects.get(name='Test Experiment') - participants = experiment.current_participants() + def test_block_current_participants(self): + block = Block.objects.get(name='Test Block') + participants = block.current_participants() self.assertEqual(len(participants), 0) - def test_experiment_export_admin(self): - experiment = Experiment.objects.get(name='Test Experiment') - exported_data = experiment.export_admin() - self.assertEqual(exported_data['experiment']['name'], 'Test Experiment') + def test_block_export_admin(self): + block = Block.objects.get(name='Test Block') + exported_data = block.export_admin() + self.assertEqual(exported_data['block']['name'], 'Test Block') - def test_experiment_export_sessions(self): - experiment = Experiment.objects.get(name='Test Experiment') - sessions = experiment.export_sessions() + def test_block_export_sessions(self): + block = Block.objects.get(name='Test Block') + sessions = block.export_sessions() self.assertEqual(len(sessions), 0) - def test_experiment_export_table(self): - experiment = Experiment.objects.get(name='Test Experiment') + def test_block_export_table(self): + block = Block.objects.get(name='Test Block') amount_of_sessions = 3 for i in range(amount_of_sessions): session = Session.objects.create( - experiment=experiment, + block=block, participant=Participant.objects.create() ) Result.objects.create( @@ -81,10 +81,10 @@ def test_experiment_export_table(self): question_key='test_question_1', ) - session_keys = ['experiment_id', 'experiment_name'] + session_keys = ['block_id', 'block_name'] result_keys = ['section_name', 'result_created_at'] export_options = {'wide_format': True} - rows, fieldnames = experiment.export_table( + rows, fieldnames = block.export_table( session_keys, result_keys, export_options @@ -93,19 +93,19 @@ def test_experiment_export_table(self): self.assertEqual(len(rows), amount_of_sessions) self.assertEqual(len(fieldnames), len(session_keys) + len(result_keys)) - def test_experiment_get_rules(self): - experiment = Experiment.objects.get(name='Test Experiment') - rules = experiment.get_rules() + def test_block_get_rules(self): + block = Block.objects.get(name='Test Block') + rules = block.get_rules() self.assertIsNotNone(rules) - def test_experiment_max_score(self): - experiment = Experiment.objects.get(name='Test Experiment') + def test_block_max_score(self): + block = Block.objects.get(name='Test Block') amount_of_results = 3 question_score = 1 session = Session.objects.create( - experiment=experiment, + block=block, participant=Participant.objects.create() ) for j in range(amount_of_results): @@ -120,8 +120,8 @@ def test_experiment_max_score(self): session.save() question_scores = amount_of_results * question_score - bonus_points = experiment.bonus_points - max_score = experiment.max_score() + bonus_points = block.bonus_points + max_score = block.max_score() self.assertEqual(max_score, question_scores + bonus_points) self.assertEqual(max_score, 13.0) diff --git a/backend/experiment/tests/test_model_functions.py b/backend/experiment/tests/test_model_functions.py index 961b38410..e42ae6a59 100644 --- a/backend/experiment/tests/test_model_functions.py +++ b/backend/experiment/tests/test_model_functions.py @@ -1,17 +1,17 @@ from django.test import TestCase from session.models import Session from participant.models import Participant -from experiment.models import Experiment, ExperimentCollection, Phase, GroupedBlock +from experiment.models import Block, ExperimentCollection, Phase, GroupedBlock class TestModelExperiment(TestCase): @classmethod def setUpTestData(cls): - cls.experiment = Experiment.objects.create(rules='THATS_MY_SONG', slug='hooked', rounds=42) + cls.block = Block.objects.create(rules='THATS_MY_SONG', slug='hooked', rounds=42) def test_separate_rules_instance(self): - rules1 = self.experiment.get_rules() - rules2 = self.experiment.get_rules() + rules1 = self.block.get_rules() + rules2 = self.block.get_rules() keys1 = rules1.question_series[0]['keys'] + rules1.question_series[1]['keys'] keys2 = rules2.question_series[0]['keys'] + rules2.question_series[1]['keys'] assert keys1 == keys2 @@ -31,39 +31,39 @@ def test_verbose_name_plural(self): # Check if verbose_name_plural is correctly set self.assertEqual(meta.verbose_name_plural, "Experiment Collections") - def test_associated_experiments(self): + def test_associated_blocks(self): collection = self.collection phase1 = Phase.objects.create( name='first_phase', series=collection) phase2 = Phase.objects.create( name='second_phase', series=collection) - experiment = Experiment.objects.create( + block = Block.objects.create( rules='THATS_MY_SONG', slug='hooked', rounds=42) - experiment2 = Experiment.objects.create( + block2 = Block.objects.create( rules='THATS_MY_SONG', slug='unhinged', rounds=42) - experiment3 = Experiment.objects.create( + block3 = Block.objects.create( rules='THATS_MY_SONG', slug='derailed', rounds=42) GroupedBlock.objects.create( - experiment=experiment, phase=phase1) + block=block, phase=phase1) GroupedBlock.objects.create( - experiment=experiment2, phase=phase2) + block=block2, phase=phase2) GroupedBlock.objects.create( - experiment=experiment3, phase=phase2) - self.assertEqual(collection.associated_experiments(), [ - experiment, experiment2, experiment3]) + block=block3, phase=phase2) + self.assertEqual(collection.associated_blocks(), [ + block, block2, block3]) def test_export_sessions(self): collection = self.collection phase = Phase.objects.create( name='test', series=collection) - experiment = Experiment.objects.create( + block = Block.objects.create( rules='THATS_MY_SONG', slug='hooked', rounds=42) GroupedBlock.objects.create( - experiment=experiment, phase=phase) + block=block, phase=phase) Session.objects.bulk_create( - [Session(experiment=experiment, participant=self.participant1), - Session(experiment=experiment, participant=self.participant2), - Session(experiment=experiment, participant=self.participant3)] + [Session(block=block, participant=self.participant1), + Session(block=block, participant=self.participant2), + Session(block=block, participant=self.participant3)] ) sessions = collection.export_sessions() self.assertEqual(len(sessions), 3) @@ -72,14 +72,14 @@ def test_current_participants(self): collection = self.collection phase = Phase.objects.create( name='test', series=collection) - experiment = Experiment.objects.create( + block = Block.objects.create( rules='THATS_MY_SONG', slug='hooked', rounds=42) GroupedBlock.objects.create( - experiment=experiment, phase=phase) + block=block, phase=phase) Session.objects.bulk_create( - [Session(experiment=experiment, participant=self.participant1), - Session(experiment=experiment, participant=self.participant2), - Session(experiment=experiment, participant=self.participant3)] + [Session(block=block, participant=self.participant1), + Session(block=block, participant=self.participant2), + Session(block=block, participant=self.participant3)] ) participants = collection.current_participants() self.assertEqual(len(participants), 3) diff --git a/backend/experiment/tests/test_validators.py b/backend/experiment/tests/test_validators.py index b532d34d7..8189bb984 100644 --- a/backend/experiment/tests/test_validators.py +++ b/backend/experiment/tests/test_validators.py @@ -1,7 +1,7 @@ from django.core.exceptions import ValidationError from django.test import TestCase -from experiment.validators import experiment_slug_validator +from experiment.validators import block_slug_validator class ExperimentValidatorsTest(TestCase): @@ -9,7 +9,7 @@ def test_valid_slug(self): # Test a valid lowercase slug slug = 'testslug' try: - experiment_slug_validator(slug) + block_slug_validator(slug) except ValidationError: self.fail(f"Unexpected ValidationError raised for slug: {slug}") @@ -17,27 +17,27 @@ def test_disallowed_slug(self): # Test a disallowed slug slug = 'admin' with self.assertRaises(ValidationError) as cm: - experiment_slug_validator(slug) + block_slug_validator(slug) self.assertEqual(str(cm.exception.messages[0]), 'The slug cannot start with "admin".') def test_uppercase_slug(self): # Test an uppercase slug slug = 'TestSlug' with self.assertRaises(ValidationError) as cm: - experiment_slug_validator(slug) + block_slug_validator(slug) self.assertEqual(str(cm.exception.messages[0]), 'Slugs must be lowercase.') def test_disallowed_prefix(self): # Test a disallowed prefix slug = 'admin-test' with self.assertRaises(ValidationError) as cm: - experiment_slug_validator(slug) + block_slug_validator(slug) self.assertEqual(str(cm.exception.messages[0]), 'The slug cannot start with "admin".') def test_valid_prefix(self): # Test a valid prefix slug = 'test-admin' try: - experiment_slug_validator(slug) + block_slug_validator(slug) except ValidationError: self.fail(f"Unexpected ValidationError raised for slug: {slug}") diff --git a/backend/experiment/tests/test_views.py b/backend/experiment/tests/test_views.py index 95f65279d..606a464b3 100644 --- a/backend/experiment/tests/test_views.py +++ b/backend/experiment/tests/test_views.py @@ -4,11 +4,11 @@ from image.models import Image from experiment.serializers import ( - serialize_experiment, + serialize_block, serialize_phase ) from experiment.models import ( - Experiment, + Block, ExperimentCollection, Phase, GroupedBlock, @@ -37,10 +37,10 @@ def setUpTestData(cls): series=collection, index=1 ) - cls.experiment1 = Experiment.objects.create( - name='experiment1', slug='experiment1') + cls.block1 = Block.objects.create( + name='block1', slug='block1') GroupedBlock.objects.create( - experiment=cls.experiment1, + block=cls.block1, phase=introductory_phase ) intermediate_phase = Phase.objects.create( @@ -48,16 +48,16 @@ def setUpTestData(cls): series=collection, index=2 ) - cls.experiment2 = Experiment.objects.create( - name='experiment2', slug='experiment2', theme_config=theme_config) - cls.experiment3 = Experiment.objects.create( - name='experiment3', slug='experiment3') + cls.block2 = Block.objects.create( + name='block2', slug='block2', theme_config=theme_config) + cls.block3 = Block.objects.create( + name='block3', slug='block3') GroupedBlock.objects.create( - experiment=cls.experiment2, + block=cls.block2, phase=intermediate_phase ) GroupedBlock.objects.create( - experiment=cls.experiment3, + block=cls.block3, phase=intermediate_phase ) final_phase = Phase.objects.create( @@ -65,10 +65,10 @@ def setUpTestData(cls): series=collection, index=3 ) - cls.experiment4 = Experiment.objects.create( - name='experiment4', slug='experiment4') + cls.block4 = Block.objects.create( + name='block4', slug='block4') GroupedBlock.objects.create( - experiment=cls.experiment4, + block=cls.block4, phase=final_phase ) @@ -80,31 +80,31 @@ def test_get_experiment_collection(self): # check that first_experiments is returned correctly response = self.client.get('/experiment/collection/test_series/') self.assertEqual(response.json().get( - 'nextExperiment').get('slug'), 'experiment1') + 'nextBlock').get('slug'), 'block1') # create session Session.objects.create( - experiment=self.experiment1, + block=self.block1, participant=self.participant, finished_at=timezone.now() ) response = self.client.get('/experiment/collection/test_series/') - self.assertIn(response.json().get('nextExperiment').get( - 'slug'), ('experiment2', 'experiment3')) + self.assertIn(response.json().get('nextBlock').get( + 'slug'), ('block2', 'block3')) self.assertEqual(response.json().get('dashboard'), []) Session.objects.create( - experiment=self.experiment2, + block=self.block2, participant=self.participant, finished_at=timezone.now() ) Session.objects.create( - experiment=self.experiment3, + block=self.block3, participant=self.participant, finished_at=timezone.now() ) response = self.client.get('/experiment/collection/test_series/') response_json = response.json() self.assertEqual(response_json.get( - 'nextExperiment').get('slug'), 'experiment4') + 'nextBlock').get('slug'), 'block4') self.assertEqual(response_json.get('dashboard'), []) self.assertEqual(response_json.get('theme').get('name'), 'test_theme') self.assertEqual(len(response_json['theme']['header']['score']), 3) @@ -129,12 +129,12 @@ def test_get_experiment_collection_inactive(self): self.assertEqual(response.status_code, 404) def test_experiment_collection_with_dashboard(self): - # if ExperimentCollection has dashboard set True, return list of random experiments + # if ExperimentCollection has dashboard set True, return list of random blocks session = self.client.session session['participant_id'] = self.participant.id session.save() Session.objects.create( - experiment=self.experiment1, + block=self.block1, participant=self.participant, finished_at=timezone.now() ) @@ -148,12 +148,12 @@ def test_experiment_collection_with_dashboard(self): self.assertEqual(type(response.json().get('dashboard')), list) def test_experiment_collection_total_score(self): - """ Test calculation of total score for grouped experiment on dashboard """ + """ Test calculation of total score for grouped block on dashboard """ session = self.client.session session['participant_id'] = self.participant.id session.save() Session.objects.create( - experiment=self.experiment2, + block=self.block2, participant=self.participant, finished_at=timezone.now(), final_score=8 @@ -167,7 +167,7 @@ def test_experiment_collection_total_score(self): total_score_1 = serialized_coll_1['totalScore'] self.assertEqual(total_score_1, 8) Session.objects.create( - experiment=self.experiment3, + block=self.block3, participant=self.participant, finished_at=timezone.now(), final_score=8 @@ -179,12 +179,12 @@ def test_experiment_collection_total_score(self): class ExperimentViewsTest(TestCase): - def test_serialize_experiment(self): - # Create an experiment - experiment = Experiment.objects.create( - slug='test-experiment', - name='Test Experiment', - description='This is a test experiment', + def test_serialize_block(self): + # Create an block + block = Block.objects.create( + slug='test-block', + name='Test Block', + description='This is a test block', image=Image.objects.create( title='Test', description='', @@ -198,24 +198,24 @@ def test_serialize_experiment(self): ) participant = Participant.objects.create() Session.objects.bulk_create([ - Session(experiment=experiment, participant=participant, finished_at=timezone.now()) for index in range(3) + Session(block=block, participant=participant, finished_at=timezone.now()) for index in range(3) ]) - # Call the serialize_experiment function - serialized_experiment = serialize_experiment(experiment, participant) + # Call the serialize_block function + serialized_block = serialize_block(block, participant) # Assert the serialized data self.assertEqual( - serialized_experiment['slug'], 'test-experiment' + serialized_block['slug'], 'test-block' ) self.assertEqual( - serialized_experiment['name'], 'Test Experiment' + serialized_block['name'], 'Test Block' ) self.assertEqual( - serialized_experiment['description'], 'This is a test experiment' + serialized_block['description'], 'This is a test block' ) self.assertEqual( - serialized_experiment['image'], { + serialized_block['image'], { 'title': 'Test', 'description': '', 'file': f'{settings.BASE_URL}/upload/test-image.jpg', @@ -227,12 +227,12 @@ def test_serialize_experiment(self): } ) - def test_get_experiment(self): - # Create an experiment - experiment = Experiment.objects.create( - slug='test-experiment', - name='Test Experiment', - description='This is a test experiment', + def test_get_block(self): + # Create an block + block = Block.objects.create( + slug='test-block', + name='Test Block', + description='This is a test block', image=Image.objects.create( file='test-image.jpg' ), @@ -241,16 +241,16 @@ def test_get_experiment(self): ) participant = Participant.objects.create() Session.objects.bulk_create([ - Session(experiment=experiment, participant=participant, finished_at=timezone.now()) for index in range(3) + Session(block=block, participant=participant, finished_at=timezone.now()) for index in range(3) ]) - response = self.client.get('/experiment/test-experiment/') + response = self.client.get('/experiment/test-block/') self.assertEqual( - response.json()['slug'], 'test-experiment' + response.json()['slug'], 'test-block' ) self.assertEqual( - response.json()['name'], 'Test Experiment' + response.json()['name'], 'Test Block' ) self.assertEqual( response.json()['theme']['name'], 'test_theme' diff --git a/backend/experiment/urls.py b/backend/experiment/urls.py index d6153b18e..0892c56fc 100644 --- a/backend/experiment/urls.py +++ b/backend/experiment/urls.py @@ -1,6 +1,6 @@ from django.urls import path from django.views.generic.base import TemplateView -from .views import get_experiment, get_experiment_collection, post_feedback, render_markdown, add_default_question_series, validate_experiment_playlist +from .views import get_block, get_experiment_collection, post_feedback, render_markdown, add_default_question_series, validate_block_playlist app_name = 'experiment' @@ -8,8 +8,8 @@ path('add_default_question_series//', add_default_question_series, name='add_default_question_series'), # Experiment path('render_markdown/', render_markdown, name='render_markdown'), - path('validate_playlist/', validate_experiment_playlist, name='validate_experiment_playlist'), - path('/', get_experiment, name='experiment'), + path('validate_playlist/', validate_block_playlist, name='validate_block_playlist'), + path('/', get_block, name='experiment'), path('/feedback/', post_feedback, name='feedback'), path('collection//', get_experiment_collection, name='experiment_collection'), diff --git a/backend/experiment/validators.py b/backend/experiment/validators.py index 95cd3295e..25bace138 100644 --- a/backend/experiment/validators.py +++ b/backend/experiment/validators.py @@ -8,8 +8,8 @@ def markdown_html_validator(): return FileExtensionValidator(allowed_extensions=valid_extensions) -def experiment_slug_validator(value): - +def block_slug_validator(value): + disallowed_slugs = [ 'admin', 'server', @@ -19,6 +19,7 @@ def experiment_slug_validator(value): 'section', 'session', 'static', + 'block', ] # Slug cannot start with a disallowed slug @@ -26,6 +27,10 @@ def experiment_slug_validator(value): if value.lower().startswith(slug): raise ValidationError(f'The slug cannot start with "{slug}".') - # Slugs must be lowercase + # Slugs must be lowercase if value.lower() != value: raise ValidationError('Slugs must be lowercase.') + + +# This is the validator that is used in the migration file +experiment_slug_validator = block_slug_validator diff --git a/backend/experiment/views.py b/backend/experiment/views.py index c00a0e557..ade3da7a1 100644 --- a/backend/experiment/views.py +++ b/backend/experiment/views.py @@ -18,56 +18,56 @@ logger = logging.getLogger(__name__) -def get_experiment(request, slug): - """Get experiment data from active experiment with given :slug +def get_block(request, slug): + """Get block data from active block with given :slug DO NOT modify session data here, it will break participant_id system - (/participant and /experiment/ are called at the same time by the frontend)""" - experiment = experiment_or_404(slug) + (/participant and /block/ are called at the same time by the frontend)""" + block = block_or_404(slug) class_name = '' if request.LANGUAGE_CODE.startswith('zh'): class_name = 'chinese' - if experiment.language: - activate(experiment.language) + if block.language: + activate(block.language) # create data - experiment_data = { - 'id': experiment.id, - 'slug': experiment.slug, - 'name': experiment.name, - 'theme': serialize_theme(experiment.theme_config) if experiment.theme_config else None, - 'description': experiment.description, - 'image': serialize_image(experiment.image) if experiment.image else None, + block_data = { + 'id': block.id, + 'slug': block.slug, + 'name': block.name, + 'theme': serialize_theme(block.theme_config) if block.theme_config else None, + 'description': block.description, + 'image': serialize_image(block.image) if block.image else None, 'class_name': class_name, # can be used to override style - 'rounds': experiment.rounds, + 'rounds': block.rounds, 'playlists': [ {'id': playlist.id, 'name': playlist.name} - for playlist in experiment.playlists.all() + for playlist in block.playlists.all() ], - 'feedback_info': experiment.get_rules().feedback_info(), - 'next_round': serialize_actions(experiment.get_rules().first_round(experiment)), + 'feedback_info': block.get_rules().feedback_info(), + 'next_round': serialize_actions(block.get_rules().first_round(block)), 'loading_text': _('Loading') } - response = JsonResponse(experiment_data, json_dumps_params={'indent': 4}) - if experiment.language: - response.set_cookie(settings.LANGUAGE_COOKIE_NAME, experiment.language) + response = JsonResponse(block_data, json_dumps_params={'indent': 4}) + if block.language: + response.set_cookie(settings.LANGUAGE_COOKIE_NAME, block.language) else: - # avoid carrying over language cookie from other experiments + # avoid carrying over language cookie from other blocks response.set_cookie(settings.LANGUAGE_COOKIE_NAME, None) return response def post_feedback(request, slug): text = request.POST.get('feedback') - experiment = experiment_or_404(slug) - feedback = Feedback(text=text, experiment=experiment) + block = block_or_404(slug) + feedback = Feedback(text=text, block=block) feedback.save() return JsonResponse({'status': 'ok'}) -def experiment_or_404(slug): - # get experiment +def block_or_404(slug): + # get block try: return Block.objects.get(slug=slug, active=True) except Block.DoesNotExist: @@ -88,9 +88,9 @@ def get_experiment_collection( ''' check which `Phase` objects are related to the `ExperimentCollection` with the given slug retrieve the phase with the lowest order (= current_phase) - return the next experiment from the current_phase without a finished session + return the next block from the current_phase without a finished session except if Phase.dashboard = True, - then all experiments of the current_phase will be returned as an array (also those with finished session) + then all blocks of the current_phase will be returned as an array (also those with finished session) ''' try: collection = ExperimentCollection.objects.get(slug=slug, active=True) @@ -112,14 +112,14 @@ def get_experiment_collection( serialized_phase = serialize_phase( current_phase, participant) if not serialized_phase: - # if the current phase is not a dashboard and has no unfinished experiments, it will return None + # if the current phase is not a dashboard and has no unfinished blocks, it will return None # set it to finished and continue to next phase phase_index += 1 return get_experiment_collection(request, slug, phase_index=phase_index) except IndexError: serialized_phase = { 'dashboard': [], - 'next_experiment': None + 'next_block': None } return JsonResponse({ **serialize_experiment_collection(collection), @@ -127,7 +127,7 @@ def get_experiment_collection( }) -def get_associated_experiments(pk_list): +def get_associated_blocks(pk_list): ''' get all the experiment objects registered in an ExperimentCollection field''' return [Block.objects.get(pk=pk) for pk in pk_list] @@ -152,7 +152,7 @@ def render_markdown(request): return JsonResponse({'html': ''}) -def validate_experiment_playlist( +def validate_block_playlist( request: HttpRequest, rules_id: str ) -> JsonResponse: @@ -174,7 +174,7 @@ def validate_experiment_playlist( playlists = Playlist.objects.filter(id__in=playlist_ids) if not playlists: - return JsonResponse({'status': 'error', 'message': 'The experiment must have a playlist.'}) + return JsonResponse({'status': 'error', 'message': 'The block must have a playlist.'}) rules = BLOCK_RULES[rules_id]() diff --git a/backend/participant/models.py b/backend/participant/models.py index 3015e92f6..aa3750db8 100644 --- a/backend/participant/models.py +++ b/backend/participant/models.py @@ -78,15 +78,15 @@ def scores_per_experiment(self): # Create best rank/score data per experiment session for session in sessions: - if session.experiment.slug in hits: + if session.block.slug in hits: continue - hits[session.experiment.slug] = True + hits[session.block.slug] = True scores.append({ - 'experiment_slug': session.experiment.slug, - 'experiment_name': session.experiment.name, - 'rank': session.experiment.get_rules().rank(session), + 'block_slug': session.block.slug, + 'block_name': session.block.name, + 'rank': session.block.get_rules().rank(session), 'score': session.final_score, 'date': naturalday(session.finished_at), }) diff --git a/backend/participant/tests.py b/backend/participant/tests.py index f679e0ad1..d3f94c563 100644 --- a/backend/participant/tests.py +++ b/backend/participant/tests.py @@ -3,7 +3,7 @@ from django.test import Client, TestCase from .models import Participant -from experiment.models import Experiment +from experiment.models import Block from session.models import Session from result.models import Result @@ -13,10 +13,10 @@ class ParticipantTest(TestCase): @classmethod def setUpTestData(cls): cls.participant = Participant.objects.create(unique_hash=42) - cls.experiment = Experiment.objects.create( + cls.block = Block.objects.create( rules='RHYTHM_BATTERY_INTRO', slug='test') cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, ) cls.result1 = Result.objects.create( @@ -67,5 +67,3 @@ def test_country_code(self): self.client.get('/participant/') participant = Participant.objects.last() assert participant.country_code == 'BLA' - - diff --git a/backend/question/fixtures/questionseries.json b/backend/question/fixtures/questionseries.json index 32dc4c7c2..729b087f4 100644 --- a/backend/question/fixtures/questionseries.json +++ b/backend/question/fixtures/questionseries.json @@ -1,20 +1,20 @@ [ - { - "model": "question.questionseries", - "pk": 1, - "fields": { - "experiment": 14, - "index": 1, - "randomize": false + { + "model": "question.questionseries", + "pk": 1, + "fields": { + "block": 14, + "index": 1, + "randomize": false + } + }, + { + "model": "question.questionseries", + "pk": 2, + "fields": { + "block": 20, + "index": 1, + "randomize": false + } } - }, - { - "model": "question.questionseries", - "pk": 2, - "fields": { - "experiment": 20, - "index": 1, - "randomize": false - } - } ] diff --git a/backend/question/tests.py b/backend/question/tests.py index 46722785c..bde6a7b45 100644 --- a/backend/question/tests.py +++ b/backend/question/tests.py @@ -1,6 +1,6 @@ from django.test import TestCase -from experiment.models import Experiment +from experiment.models import Block from participant.models import Participant from result.models import Result from session.models import Session @@ -13,10 +13,10 @@ class UtilsTestCase(TestCase): @classmethod def setUpTestData(cls): cls.participant = Participant.objects.create(unique_hash=42) - cls.experiment = Experiment.objects.create( + cls.block = Block.objects.create( rules='RHYTHM_BATTERY_INTRO', slug='test') cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, ) cls.result = Result.objects.create( @@ -25,14 +25,14 @@ def setUpTestData(cls): given_response='non_answer' ) cls.questions = DEMOGRAPHICS[:3] - + def test_unanswered_questions(self): questions = unanswered_questions(self.participant, self.questions) question = next(questions) assert question.key == 'dgf_generation' question = next(questions) assert question.key == 'dgf_country_of_origin' - + def test_total_unanswered_questions(self): number = total_unanswered_questions(self.participant, self.questions) assert number == 2 diff --git a/backend/result/tests/test_result.py b/backend/result/tests/test_result.py index bd6784e7c..a5e0be93b 100644 --- a/backend/result/tests/test_result.py +++ b/backend/result/tests/test_result.py @@ -1,6 +1,6 @@ from django.test import Client, TestCase -from experiment.models import Experiment +from experiment.models import Block from participant.models import Participant from result.models import Result from session.models import Session @@ -11,10 +11,10 @@ class ResultTest(TestCase): @classmethod def setUpTestData(cls): cls.participant = Participant.objects.create(unique_hash=42) - cls.experiment = Experiment.objects.create( + cls.block = Block.objects.create( rules='MUSICAL_PREFERENCES', slug='test') cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, ) @@ -29,16 +29,16 @@ def setUp(self): ) def test_json_data(self): - + self.result.save_json_data({'test': 'tested'}) self.assertEqual(self.result.load_json_data(), {'test': 'tested'}) self.result.save_json_data({'test_len': 'tested_len'}) self.assertEqual(len(self.result.json_data), 2) - def test_json_data_direct(self): + def test_json_data_direct(self): self.result.json_data.update({'test_direct': 'tested_direct'}) self.result.save() - self.assertEqual(self.result.json_data['test_direct'], 'tested_direct') + self.assertEqual(self.result.json_data['test_direct'], 'tested_direct') self.result.save_json_data({'test_direct_len': 'tested_direct_len'}) self.result.save() self.assertEqual(len(self.result.json_data), 2) diff --git a/backend/result/tests/test_views.py b/backend/result/tests/test_views.py index 587f44aa6..cb6b37bd8 100644 --- a/backend/result/tests/test_views.py +++ b/backend/result/tests/test_views.py @@ -2,7 +2,7 @@ from django.test import Client, TestCase -from experiment.models import Experiment +from experiment.models import Block from participant.models import Participant from result.models import Result from section.models import Playlist, Section, Song @@ -16,10 +16,10 @@ class ResultTest(TestCase): @classmethod def setUpTestData(cls): cls.participant = Participant.objects.create(unique_hash=42) - cls.experiment = Experiment.objects.create( + cls.block = Block.objects.create( rules='MUSICAL_PREFERENCES', slug='test') cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, ) @@ -95,11 +95,11 @@ def setUpTestData(cls): filename="not/to_be_found.mp3", tag=0 ) - cls.experiment = Experiment.objects.create( + cls.block = Block.objects.create( rules='RHYTHM_BATTERY_INTRO', slug='test') cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, playlist=playlist ) @@ -269,4 +269,3 @@ def test_song_sync(self): response = self.client.post('/result/score/', client_request) assert response.status_code == 200 assert self.session.get_previous_result(['recognize']).score == -5 - diff --git a/backend/section/admin.py b/backend/section/admin.py index 18a4815ce..0d3ffa5f6 100644 --- a/backend/section/admin.py +++ b/backend/section/admin.py @@ -49,7 +49,7 @@ class SongAdmin(admin.ModelAdmin): class PlaylistAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin): form = PlaylistAdminForm change_form_template = 'change_form.html' - list_display = ('name', 'section_count', 'experiment_count') + list_display = ('name', 'section_count', 'block_count') search_fields = ['name', 'section__song__artist', 'section__song__name'] inline_actions = ['add_sections', 'edit_sections', 'export_csv'] @@ -147,7 +147,7 @@ def edit_sections(self, request, obj, parent_obj=None): # Retrieve or create Song object song = None if this_artist or this_name: - song = get_or_create_song(this_artist, this_name) + song = get_or_create_song(this_artist, this_name) section.song = song section.start_time = request.POST.get(pre_fix + '_start_time') diff --git a/backend/section/models.py b/backend/section/models.py index 3c4de9fb1..c2b43d24e 100644 --- a/backend/section/models.py +++ b/backend/section/models.py @@ -72,11 +72,11 @@ def section_count(self): section_count.short_description = "Sections" - def experiment_count(self): - """Number of Experiments""" - return self.experiment_set.count() + def block_count(self): + """Number of Blocks""" + return self.block_set.count() - experiment_count.short_description = "Experiments" + block_count.short_description = "Blocks" def update_sections(self): """Update the sections from the csv file""" @@ -148,7 +148,7 @@ def is_number(string): # if same section already exists, update it with new info for ex_section in existing_sections: if ex_section.filename == section.filename: - if song: + if song: ex_section.song = song ex_section.save() ex_section.start_time = section.start_time @@ -219,7 +219,7 @@ def export_sections(self): def update_admin_csv(self): """Update csv data for admin""" csvfile = CsvStringBuilder() - writer = csv.writer(csvfile) + writer = csv.writer(csvfile) for section in self.section_set.all(): if section.song: this_artist = section.song.artist diff --git a/backend/session/tests/test_session.py b/backend/session/tests/test_session.py index 60d0071d2..08965c220 100644 --- a/backend/session/tests/test_session.py +++ b/backend/session/tests/test_session.py @@ -3,7 +3,7 @@ from django.test import TestCase from django.utils import timezone -from experiment.models import Experiment +from experiment.models import Block from participant.models import Participant from section.models import Playlist, Section, Song from result.models import Result @@ -14,25 +14,25 @@ class SessionTest(TestCase): @classmethod def setUpTestData(cls): - + cls.participant = Participant.objects.create(unique_hash=42) - cls.experiment = Experiment.objects.create( + cls.block = Block.objects.create( rules='RHYTHM_BATTERY_INTRO', slug='test') cls.playlist = Playlist.objects.create( name='Test playlist' ) cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, playlist=cls.playlist ) - + def test_create(self): data = { - 'experiment_id': self.experiment.pk, + 'block_id': self.block.pk, 'playlist_id': self.playlist.pk, 'participant_id': self.participant.pk - } + } response = self.client.post('/session/create', data) assert response.status_code != 500 @@ -42,7 +42,7 @@ def test_finalize(self): csrf_token = response_data.get('csrf_token') participant = Participant.objects.get(pk=response_data.get('id')) session = Session.objects.create( - experiment=self.experiment, + block=self.block, participant=participant) data = { 'csrfmiddlewaretoken:': csrf_token @@ -51,13 +51,13 @@ def test_finalize(self): assert response.status_code == 200 assert Session.objects.filter(finished_at__isnull=False).count() == 1 - def test_total_questions(self): + def test_total_questions(self): assert self.session.total_questions() == 0 Result.objects.create( session=self.session ) assert self.session.total_questions() == 1 - + def test_skipped_answered_questions(self): Result.objects.create( session=self.session, @@ -75,23 +75,23 @@ def test_skipped_answered_questions(self): given_response='' ) assert self.session.skipped_questions() == 2 - + def test_percentile_rank(self): # create one session with relatively low score Session.objects.create( - experiment=self.experiment, + block=self.block, participant=self.participant, final_score=24, finished_at=timezone.now() ) # create one unfinished session with relatively high score Session.objects.create( - experiment=self.experiment, + block=self.block, participant=self.participant, final_score=180 ) finished_session = Session.objects.create( - experiment=self.experiment, + block=self.block, participant=self.participant, final_score=42, finished_at=timezone.now() @@ -140,10 +140,10 @@ def test_json_data(self): self.session.save_json_data({'test_len': 'tested_len'}) self.assertEqual(len(self.session.json_data), 2) - def test_json_data_direct(self): + def test_json_data_direct(self): self.session.json_data.update({'test_direct': 'tested_direct'}) self.session.save() - self.assertEqual(self.session.json_data['test_direct'], 'tested_direct') + self.assertEqual(self.session.json_data['test_direct'], 'tested_direct') self.session.save_json_data({'test_direct_len': 'tested_direct_len'}) self.session.save() self.assertEqual(len(self.session.json_data), 2) diff --git a/backend/session/tests/test_utils.py b/backend/session/tests/test_utils.py index 72d8340ac..64dca5407 100644 --- a/backend/session/tests/test_utils.py +++ b/backend/session/tests/test_utils.py @@ -1,7 +1,7 @@ from django.test import TestCase -from experiment.models import Experiment +from experiment.models import Block from participant.models import Participant from result.models import Result from session.models import Session @@ -14,10 +14,10 @@ class SessionUtilsTest(TestCase): @classmethod def setUpTestData(cls): cls.participant = Participant.objects.create(unique_hash=42) - cls.experiment = Experiment.objects.create( + cls.block = Block.objects.create( rules='MUSICAL_PREFERENCES', slug='test') cls.session = Session.objects.create( - experiment=cls.experiment, + block=cls.block, participant=cls.participant, ) # create results with various question_keys, and scores from 0 to 9 @@ -28,17 +28,16 @@ def setUpTestData(cls): question_key=keys[i], score=i ) - + def test_relevant_results_without_filter(self): results = self.session.get_relevant_results() assert results.count() == n_results - + def test_relevant_results_with_filter(self): results = self.session.get_relevant_results(['a', 'b']) assert results.count() == 6 assert 'd' not in results.values_list('question_key') - + def test_previous_score(self): result = self.session.get_previous_result(['c', 'd']) assert result.score == 9 - diff --git a/backend/session/tests/test_views.py b/backend/session/tests/test_views.py index 25b6f63ec..26eb53122 100644 --- a/backend/session/tests/test_views.py +++ b/backend/session/tests/test_views.py @@ -1,6 +1,6 @@ from django.test import TestCase -from experiment.models import Experiment, ExperimentCollection, Phase, GroupedBlock +from experiment.models import Block, ExperimentCollection, Phase, GroupedBlock from experiment.actions.utils import COLLECTION_KEY from participant.models import Participant from section.models import Playlist @@ -13,12 +13,12 @@ def setUpTestData(cls): cls.participant = Participant.objects.create(unique_hash=42) cls.playlist1 = Playlist.objects.create(name='First Playlist') cls.playlist2 = Playlist.objects.create(name='Second Playlist') - cls.experiment = Experiment.objects.create( + cls.block = Block.objects.create( name='TestViews', slug='testviews', rules='RHYTHM_BATTERY_INTRO' ) - cls.experiment.playlists.add( + cls.block.playlists.add( cls.playlist1, cls.playlist2 ) @@ -29,28 +29,28 @@ def setUp(self): def test_create_with_playlist(self): request = { - "experiment_id": self.experiment.id, + "block_id": self.block.id, "playlist_id": self.playlist2.id } self.client.post('/session/create/', request) new_session = Session.objects.get( - experiment=self.experiment, participant=self.participant) + block=self.block, participant=self.participant) assert new_session assert new_session.playlist == self.playlist2 def test_create_without_playlist(self): request = { - "experiment_id": self.experiment.id + "block_id": self.block.id } self.client.post('/session/create/', request) new_session = Session.objects.get( - experiment=self.experiment, participant=self.participant) + block=self.block, participant=self.participant) assert new_session assert new_session.playlist == self.playlist1 def test_next_round(self): session = Session.objects.create( - experiment=self.experiment, participant=self.participant) + block=self.block, participant=self.participant) response = self.client.get( f'/session/{session.id}/next_round/') assert response @@ -62,7 +62,7 @@ def test_next_round_with_collection(self): request_session[COLLECTION_KEY] = slug request_session.save() session = Session.objects.create( - experiment=self.experiment, participant=self.participant) + block=self.block, participant=self.participant) response = self.client.get( f'/session/{session.id}/next_round/') assert response @@ -70,7 +70,7 @@ def test_next_round_with_collection(self): assert changed_session.load_json_data().get(COLLECTION_KEY) is None phase = Phase.objects.create(series=collection) GroupedBlock.objects.create( - phase=phase, experiment=self.experiment) + phase=phase, block=self.block) response = self.client.get( f'/session/{session.id}/next_round/') changed_session = Session.objects.get(pk=session.pk) diff --git a/backend/session/views.py b/backend/session/views.py index 1864b3dda..06e0a18f2 100644 --- a/backend/session/views.py +++ b/backend/session/views.py @@ -17,28 +17,28 @@ def create_session(request): # Current participant participant = get_participant(request) - # Get experiment - experiment_id = request.POST.get("experiment_id") - if not experiment_id: - return HttpResponseBadRequest("experiment_id not defined") + # Get block + block_id = request.POST.get("block_id") + if not block_id: + return HttpResponseBadRequest("block_id not defined") try: - experiment = Block.objects.get(pk=experiment_id, active=True) + block = Block.objects.get(pk=block_id, active=True) except Block.DoesNotExist: raise Http404("Block does not exist") # Create new session - session = Session(experiment=experiment, participant=participant) + session = Session(block=block, participant=participant) if request.POST.get("playlist_id"): try: playlist = Playlist.objects.get( - pk=request.POST.get("playlist_id"), experiment__id=session.experiment.id) + pk=request.POST.get("playlist_id"), block__id=session.block.id) session.playlist = playlist except: raise Http404("Playlist does not exist") - elif experiment.playlists.count() >= 1: + elif block.playlists.count() >= 1: # register first playlist - session.playlist = experiment.playlists.first() + session.playlist = block.playlists.first() # Save session session.save() @@ -58,7 +58,7 @@ def continue_session(request, session_id): def next_round(request, session_id): """ - Fall back to continue an experiment is case next_round data is missing + Fall back to continue an block is case next_round data is missing This data is normally provided in: result() """ # Current participant @@ -67,14 +67,14 @@ def next_round(request, session_id): session = get_object_or_404(Session, pk=session_id, participant__id=participant.id) - # check if this experiment is part of an ExperimentCollection + # check if this block is part of an ExperimentCollection collection_slug = request.session.get(COLLECTION_KEY) if collection_slug: # check that current session does not have the collection information saved yet if not session.load_json_data().get(COLLECTION_KEY): # set information of the ExperimentCollection to the session collection = ExperimentCollection.objects.get(slug=collection_slug) - if collection and session.experiment in collection.associated_experiments(): + if collection and session.block in collection.associated_blocks(): session.save_json_data({COLLECTION_KEY: collection_slug}) # Get next round for given session diff --git a/backend/theme/serializers.py b/backend/theme/serializers.py index db1bba179..9228f425f 100644 --- a/backend/theme/serializers.py +++ b/backend/theme/serializers.py @@ -23,7 +23,7 @@ def serialize_footer(footer: FooterConfig) -> dict: def serialize_header(header: HeaderConfig) -> dict: return { - 'nextExperimentButtonText': _('Next experiment'), + 'nextBlockButtonText': _('Next experiment'), 'aboutButtonText': _('About us'), 'score': { 'scoreClass': 'gold', diff --git a/backend/theme/tests/test_serializers.py b/backend/theme/tests/test_serializers.py index c9d8bb920..d28a4bbd5 100644 --- a/backend/theme/tests/test_serializers.py +++ b/backend/theme/tests/test_serializers.py @@ -76,8 +76,8 @@ def test_footer_serializer(self): self.assertEqual(serialize_footer(self.footer), expected_json) def test_header_serializer(self): - expected_json = { - 'nextExperimentButtonText': 'Next experiment', + expected_json = { + 'nextBlockButtonText': 'Next experiment', 'aboutButtonText': 'About us', 'score': { 'scoreClass': 'gold', diff --git a/e2e/tests/base_test.py b/e2e/tests/base_test.py index 7c2ba1633..3d17e9365 100644 --- a/e2e/tests/base_test.py +++ b/e2e/tests/base_test.py @@ -79,16 +79,16 @@ def agree_to_consent(self, h4_text='informed consent', button_text='I agree'): print("I agree button clicked") - def check_for_error(self, experiment_name, experiment_slug='[no slug provided]'): + def check_for_error(self, block_name, block_slug='[no slug provided]'): if "Error" in self.driver.find_element(By.TAG_NAME, "body").text: - raise Exception(f"Could not load {experiment_name} experiment, please check the server logs and make sure the slug ({experiment_slug}) is correct.") + raise Exception(f"Could not load {block_name} experiment, please check the server logs and make sure the slug ({block_slug}) is correct.") - def take_screenshot(self, experiment_name, notes=""): + def take_screenshot(self, block_name, notes=""): current_time = time.strftime("%Y-%m-%d-%H-%M-%S") - screen_shot_path = f"screenshots/{experiment_name}-{current_time}.png" + screen_shot_path = f"screenshots/{block_name}-{current_time}.png" print('Capturing screenshot to', screen_shot_path, notes) self.driver.get_screenshot_as_file(screen_shot_path) - def handle_error(self, e, experiment_name): - self.take_screenshot(experiment_name, str(e)) + def handle_error(self, e, block_name): + self.take_screenshot(block_name, str(e)) self.fail(e) diff --git a/e2e/tests/e2e/test_beatalignment.py b/e2e/tests/e2e/test_beatalignment.py index e1e0e116c..1674df486 100644 --- a/e2e/tests/e2e/test_beatalignment.py +++ b/e2e/tests/e2e/test_beatalignment.py @@ -9,11 +9,11 @@ class TestBeatAlignment(BaseTest): def test_beatalignment(self): - experiment_name = "beat_alignment" - experiment_slug = self.config['experiment_slugs'][experiment_name] - self.driver.get(f"{self.base_url}/{experiment_slug}") + block_name = "beat_alignment" + block_slug = self.config['block_slugs'][block_name] + self.driver.get(f"{self.base_url}/{block_slug}") - self.check_for_error(experiment_name, experiment_slug) + self.check_for_error(block_name, block_slug) WebDriverWait(self.driver, 5).until(expected_conditions.element_to_be_clickable((By.XPATH, '//button[text()="Ok"]'))).click() diff --git a/e2e/tests/e2e/test_categorization.py b/e2e/tests/e2e/test_categorization.py index 580cb58c1..41c62a059 100644 --- a/e2e/tests/e2e/test_categorization.py +++ b/e2e/tests/e2e/test_categorization.py @@ -12,16 +12,16 @@ class TestCategorization(BaseTest): def test_categorization(self): - experiment_name = "categorization" + block_name = "categorization" try: self.driver.delete_all_cookies() - experiment_slug = self.config['experiment_slugs'][experiment_name] - self.driver.get(f"{self.base_url}/{experiment_slug}") + block_slug = self.config['block_slugs'][block_name] + self.driver.get(f"{self.base_url}/{block_slug}") # if page body contains the word "Error", raise an exception - self.check_for_error(experiment_name, experiment_slug) + self.check_for_error(block_name, block_slug) # wait until h4 element is present and contains "INFORMED CONSENT" text (case-insensitive) WebDriverWait(self.driver, 5) \ @@ -129,4 +129,4 @@ def test_categorization(self): training = False except Exception as e: - self.handle_error(e, experiment_name) + self.handle_error(e, block_name) diff --git a/e2e/tests/e2e/test_eurovision.py b/e2e/tests/e2e/test_eurovision.py index bd9544424..d1549f392 100644 --- a/e2e/tests/e2e/test_eurovision.py +++ b/e2e/tests/e2e/test_eurovision.py @@ -12,15 +12,15 @@ class TestEurovision(BaseTest): def test_eurovision(self): - experiment_name = "eurovision" + block_name = "eurovision" try: - experiment_slug = self.config['experiment_slugs'][experiment_name] - self.driver.get(f"{self.base_url}/{experiment_slug}") + block_slug = self.config['block_slugs'][block_name] + self.driver.get(f"{self.base_url}/{block_slug}") # if page body contains the word "Error", raise an exception - self.check_for_error(experiment_name, experiment_slug) + self.check_for_error(block_name, block_slug) # Check & Agree to Informed Consent self.agree_to_consent() @@ -130,4 +130,4 @@ def test_eurovision(self): print("Eurovision Test completed!") except Exception as e: - self.handle_error(e, experiment_name) + self.handle_error(e, block_name) diff --git a/frontend/src/API.ts b/frontend/src/API.ts index 4ca97f79a..7132eea52 100644 --- a/frontend/src/API.ts +++ b/frontend/src/API.ts @@ -99,7 +99,7 @@ export const createSession = async ({ block, participant, playlist }: CreateSess const response = await axios.post( API_BASE_URL + URLS.session.create, qs.stringify({ - experiment_id: block.id, + block_id: block.id, playlist_id: playlist.current, csrfmiddlewaretoken: participant.csrf_token, }) diff --git a/frontend/src/components/ExperimentCollection/ExperimentCollection.test.tsx b/frontend/src/components/ExperimentCollection/ExperimentCollection.test.tsx index 78df22019..8d6a5fc82 100644 --- a/frontend/src/components/ExperimentCollection/ExperimentCollection.test.tsx +++ b/frontend/src/components/ExperimentCollection/ExperimentCollection.test.tsx @@ -53,7 +53,7 @@ const blockWithAllProps = getBlock({ image: 'some_image.jpg', description: 'Some describe('ExperimentCollection', () => { it('forwards to a single block if it receives an empty dashboard array', async () => { - mock.onGet().replyOnce(200, { dashboard: [], nextExperiment: block1 }); + mock.onGet().replyOnce(200, { dashboard: [], nextBlock: block1 }); render( @@ -76,7 +76,7 @@ describe('ExperimentCollection', () => { }); it('shows a placeholder if no image is available', () => { - mock.onGet().replyOnce(200, { dashboard: [block1], nextExperiment: block1 }); + mock.onGet().replyOnce(200, { dashboard: [block1], nextBlock: block1 }); render( @@ -88,7 +88,7 @@ describe('ExperimentCollection', () => { }); it('shows the image if it is available', () => { - mock.onGet().replyOnce(200, { dashboard: [blockWithAllProps], nextExperiment: block1 }); + mock.onGet().replyOnce(200, { dashboard: [blockWithAllProps], nextBlock: block1 }); render( @@ -100,7 +100,7 @@ describe('ExperimentCollection', () => { }); it('shows the description if it is available', () => { - mock.onGet().replyOnce(200, { dashboard: [blockWithAllProps], nextExperiment: block1 }); + mock.onGet().replyOnce(200, { dashboard: [blockWithAllProps], nextBlock: block1 }); render( @@ -112,7 +112,7 @@ describe('ExperimentCollection', () => { }); it('shows consent first if available', async () => { - mock.onGet().replyOnce(200, { consent: '

This is our consent form!

', dashboard: [blockWithAllProps], nextExperiment: block1 }); + mock.onGet().replyOnce(200, { consent: '

This is our consent form!

', dashboard: [blockWithAllProps], nextBlock: block1 }); render( @@ -124,7 +124,7 @@ describe('ExperimentCollection', () => { }); it('shows a footer if a theme with footer is available', async () => { - mock.onGet().replyOnce(200, { dashboard: [blockWithAllProps], nextExperiment: block1, theme }); + mock.onGet().replyOnce(200, { dashboard: [blockWithAllProps], nextBlock: block1, theme }); render( diff --git a/frontend/src/components/ExperimentCollection/ExperimentCollection.tsx b/frontend/src/components/ExperimentCollection/ExperimentCollection.tsx index de3340690..eac1ab1fe 100644 --- a/frontend/src/components/ExperimentCollection/ExperimentCollection.tsx +++ b/frontend/src/components/ExperimentCollection/ExperimentCollection.tsx @@ -32,7 +32,7 @@ const ExperimentCollection = ({ match }: ExperimentCollectionProps) => { const participant = useBoundStore((state) => state.participant); const setTheme = useBoundStore((state) => state.setTheme); const participantIdUrl = participant?.participant_id_url; - const nextExperiment = experimentCollection?.nextExperiment; + const nextBlock = experimentCollection?.nextBlock; const displayDashboard = experimentCollection?.dashboard.length; const showConsent = experimentCollection?.consent; const totalScore = experimentCollection?.totalScore; @@ -73,8 +73,8 @@ const ExperimentCollection = ({ match }: ExperimentCollectionProps) => { ) } - if (!displayDashboard && nextExperiment) { - return + if (!displayDashboard && nextBlock) { + return } return ( diff --git a/frontend/src/components/ExperimentCollection/ExperimentCollectionDashboard/ExperimentCollectionDashboard.tsx b/frontend/src/components/ExperimentCollection/ExperimentCollectionDashboard/ExperimentCollectionDashboard.tsx index 8b547803a..dfb572d93 100644 --- a/frontend/src/components/ExperimentCollection/ExperimentCollectionDashboard/ExperimentCollectionDashboard.tsx +++ b/frontend/src/components/ExperimentCollection/ExperimentCollectionDashboard/ExperimentCollectionDashboard.tsx @@ -16,10 +16,10 @@ interface ExperimentCollectionDashboardProps { export const ExperimentCollectionDashboard: React.FC = ({ experimentCollection, participantIdUrl, totalScore }) => { const { dashboard, description } = experimentCollection; - const { nextExperimentButtonText, aboutButtonText } = experimentCollection.theme?.header || { nextExperimentButtonText: "", aboutButtonText: "" }; + const { nextBlockButtonText, aboutButtonText } = experimentCollection.theme?.header || { nextBlockButtonText: "", aboutButtonText: "" }; const scoreDisplayConfig = experimentCollection.theme?.header?.score; - const nextBlockSlug = experimentCollection.nextExperiment?.slug; + const nextBlockSlug = experimentCollection.nextBlock?.slug; const showHeader = experimentCollection.theme?.header; const socialMediaConfig = experimentCollection.socialMediaConfig; @@ -35,7 +35,7 @@ export const ExperimentCollectionDashboard: React.FC diff --git a/frontend/src/components/Final/Final.test.tsx b/frontend/src/components/Final/Final.test.tsx index 3eff6a18c..c6b08cf35 100644 --- a/frontend/src/components/Final/Final.test.tsx +++ b/frontend/src/components/Final/Final.test.tsx @@ -43,7 +43,7 @@ describe('Final Component', () => { render( { render( { const history = createMemoryHistory(); const mockActionTexts = { - all_experiments: 'All Experiments', + all_blocks: 'All Blocks', profile: 'Profile', }; diff --git a/frontend/src/components/Profile/Profile.jsx b/frontend/src/components/Profile/Profile.jsx index c8edaa312..8c61f437f 100644 --- a/frontend/src/components/Profile/Profile.jsx +++ b/frontend/src/components/Profile/Profile.jsx @@ -73,7 +73,7 @@ export const ProfileView = (data) => { score.experiment_slug )} > - {score.experiment_name} + {score.block_name}
{score.score} {data.points}
diff --git a/frontend/src/stories/Header.stories.tsx b/frontend/src/stories/Header.stories.tsx index d9a7b2e7d..37b54b517 100644 --- a/frontend/src/stories/Header.stories.tsx +++ b/frontend/src/stories/Header.stories.tsx @@ -13,7 +13,7 @@ export default { function getHeaderData(overrides = {}) { return { description: "

Experiment ABC

This is the experiment description

", - nextExperimentSlug: '/th1-mozart', + nextBlockSlug: '/th1-mozart', nextBlockButtonText: 'Volgende experiment', collectionSlug: '/collection/thkids', aboutButtonText: 'Over ons', diff --git a/frontend/src/types/ExperimentCollection.ts b/frontend/src/types/ExperimentCollection.ts index 177320991..8eaf69a5e 100644 --- a/frontend/src/types/ExperimentCollection.ts +++ b/frontend/src/types/ExperimentCollection.ts @@ -21,7 +21,7 @@ export default interface ExperimentCollection { name: string; description: string; dashboard: Block[]; - nextExperiment: Block | null; + nextBlock: Block | null; aboutContent: string; consent?: Consent; theme?: Theme; diff --git a/frontend/src/types/Theme.ts b/frontend/src/types/Theme.ts index b5ae06413..aaedf662f 100644 --- a/frontend/src/types/Theme.ts +++ b/frontend/src/types/Theme.ts @@ -7,7 +7,7 @@ export interface ScoreDisplayConfig { } export interface Header { - nextExperimentButtonText: string; + nextBlockButtonText: string; aboutButtonText: string; score: ScoreDisplayConfig; };