Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Questions in database #946

Merged
merged 22 commits into from
Jun 8, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1240cc9
Add Question model
albertas-jn Apr 11, 2024
3363c21
Update tests for Question model
albertas-jn Apr 12, 2024
28bcfad
Fix linter errors
albertas-jn Apr 12, 2024
fd8a6c9
Fix linter errors 2
albertas-jn Apr 12, 2024
07f0199
Fix linter errors 3
albertas-jn Apr 12, 2024
ce73f21
Merge develop
albertas-jn Apr 16, 2024
9805400
Fix migration conflicts
albertas-jn Apr 16, 2024
0455b1e
Modify button text, Add rules' default and save
albertas-jn Apr 17, 2024
8074536
Do not add default questions to experiment if question_series does no…
albertas-jn Apr 22, 2024
0b96862
Move question to own app
albertas-jn May 10, 2024
ce1ba2d
Merge develop
albertas-jn May 10, 2024
dc264c8
First createquestions then boostrap, add default question series to b…
albertas-jn May 10, 2024
8ff247a
Convert indentation to spaces in experiment_admin.js and questionseri…
albertas-jn May 10, 2024
65375bf
Randomize MSI_F3 questions in rhythm_battery_final.py (former gold_ms…
albertas-jn May 10, 2024
a52f9f2
Add tests for createquestions command
albertas-jn May 10, 2024
0fc8474
Cleanup: remove get_default_question_keys()
albertas-jn May 10, 2024
64d1692
Add docstrings to question app
albertas-jn May 10, 2024
a323cfe
Move createquestions command tests to question app
albertas-jn May 12, 2024
3dc7bda
Merge develop
albertas-jn Jun 8, 2024
64f08fc
Move create_default_questions() to bootstrap.py
albertas-jn Jun 8, 2024
529810b
Data migration: add default questions to experiment only if rules exist
albertas-jn Jun 8, 2024
89b0b33
Remove call_command() from test_createquestions()
albertas-jn Jun 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 45 additions & 4 deletions backend/experiment/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
from inline_actions.admin import InlineActionsModelAdminMixin
from django.urls import reverse
from django.utils.html import format_html
from experiment.models import Experiment, ExperimentCollection, ExperimentCollectionGroup, Feedback, GroupedExperiment
from experiment.forms import ExperimentCollectionForm, ExperimentForm, ExportForm, TemplateForm, EXPORT_TEMPLATES
from experiment.models import Experiment, ExperimentCollection, ExperimentCollectionGroup, Feedback, GroupedExperiment, Question, QuestionGroup, QuestionSeries, QuestionInSeries
from experiment.forms import ExperimentCollectionForm, ExperimentForm, ExportForm, TemplateForm, EXPORT_TEMPLATES, QuestionSeriesAdminForm
from section.models import Section, Song
from result.models import Result
from participant.models import Participant
Expand All @@ -31,6 +31,47 @@ class FeedbackInline(admin.TabularInline):
extra = 0


class QuestionInSeriesInline(admin.TabularInline):
model = QuestionInSeries
extra = 0


class QuestionSeriesInline(admin.TabularInline):
model = QuestionSeries
extra = 0
show_change_link = True


class QuestionAdmin(admin.ModelAdmin):
def has_change_permission(self, request, obj=None):
return obj.editable if obj else False


class QuestionGroupAdmin(admin.ModelAdmin):
formfield_overrides = {
models.ManyToManyField: {'widget': CheckboxSelectMultiple},
}

def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)

if obj and not obj.editable:
for field_name in form.base_fields:
form.base_fields[field_name].disabled = True

return form


class QuestionSeriesAdmin(admin.ModelAdmin):
inlines = [QuestionInSeriesInline]
form = QuestionSeriesAdminForm


admin.site.register(Question, QuestionAdmin)
admin.site.register(QuestionGroup, QuestionGroupAdmin)
admin.site.register(QuestionSeries, QuestionSeriesAdmin)


class ExperimentAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin):
list_display = ('image_preview', 'experiment_name_link',
'experiment_slug_link', 'rules',
Expand All @@ -43,8 +84,8 @@ class ExperimentAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin):
'slug', 'url', 'hashtag', 'theme_config',
'language', 'active', 'rules',
'rounds', 'bonus_points', 'playlists',
'consent', 'questions']
inlines = [FeedbackInline]
'consent']
inlines = [QuestionSeriesInline, FeedbackInline]
form = ExperimentForm

# make playlists fields a list of checkboxes
Expand Down
6 changes: 0 additions & 6 deletions backend/experiment/fixtures/experiment.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,6 @@
13,
2,
1
],
"questions": [
"msi_01_music_activities", "msi_03_writing", "msi_08_intrigued_styles", "msi_15_internet_search_music", "msi_21_spend_income", "msi_24_music_addiction", "msi_28_track_new", "msi_34_attended_events", "msi_38_listen_music", "msi_05_good_singer", "msi_06_song_first_time", "msi_11_spot_mistakes", "msi_12_performance_diff", "msi_13_trouble_recognising", "msi_18_out_of_beat", "msi_22_out_of_tune", "msi_23_no_idea_in_tune", "msi_26_genre", "msi_14_never_complimented", "msi_27_consider_musician", "msi_32_practice_years", "msi_33_practice_daily", "msi_35_theory_training", "msi_36_instrumental_training", "msi_37_play_instruments", "msi_04_sing_along", "msi_07_from_memory", "msi_10_sing_with_recording", "msi_17_not_sing_harmony", "msi_25_sing_public", "msi_29_sing_after_hearing", "msi_30_sing_back", "msi_02_shivers", "msi_09_rarely_emotions", "msi_16_motivate", "msi_19_identify_special", "msi_20_talk_emotions", "msi_31_memories", "msi_39_best_instrument", "dgf_genre_preference_zh", "dgf_generation", "dgf_education", "dgf_highest_qualification_expectation", "dgf_occupational_status", "dgf_region_of_origin", "dgf_region_of_residence", "dgf_gender_identity_zh", "contact"
]
}
},
Expand Down Expand Up @@ -328,9 +325,6 @@
"language": "",
"playlists": [
18
],
"questions": [
"dgf_generation", "dgf_gender_identity", "P01_1", "P01_2", "P01_3", "P02_1", "P02_2", "P02_3", "P03_1", "P03_2", "P03_3", "P04_1", "P04_2", "P04_3", "P04_4", "P05_1", "P05_2", "P05_3", "P05_4", "P05_5", "P06_1", "P06_2", "P06_3", "P06_4", "P07_1", "P07_2", "P07_3", "P08_1", "P08_2", "P08_3", "P09_1", "P09_2", "P09_3", "P10_1", "P10_2", "P10_3", "P11_1", "P11_2", "P11_3", "P12_1", "P12_2", "P12_3", "P13_1", "P13_2", "P13_3", "P14_1", "P14_2", "P14_3", "P14_4", "P15_1", "P15_2", "P15_3", "P15_4", "P16_1", "P16_2", "P16_3", "P17_1", "P17_2", "P17_3"
]
}
}
Expand Down
66 changes: 66 additions & 0 deletions backend/experiment/fixtures/question.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
[
{
"model": "experiment.question",
"pk": "dgf_generation",
"fields": {
"question": "When were you born?",
"editable": false
}
},
{
"model": "experiment.question",
"pk": "dgf_gender_identity",
"fields": {
"question": "With which gender do you currently most identify?",
"editable": false
}
},
{
"model": "experiment.question",
"pk": "P01_1",
"fields": {
"question": "Can you clap in time with a musical beat?",
"editable": false
}
},
{
"model": "experiment.question",
"pk": "P01_2",
"fields": {
"question": "I can tap my foot in time with the beat of the music I hear.",
"editable": false
}
},
{
"model": "experiment.question",
"pk": "P01_3",
"fields": {
"question": "When listening to music, can you move in time with the beat?",
"editable": false
}
},
{
"model": "experiment.question",
"pk": "msi_01_music_activities",
"fields": {
"question": "I spend a lot of my free time doing music-related activities.",
"editable": false
}
},
{
"model": "experiment.question",
"pk": "msi_03_writing",
"fields": {
"question": "I enjoy writing about music, for example on blogs and forums.",
"editable": false
}
},
{
"model": "experiment.question",
"pk": "msi_08_intrigued_styles",
"fields": {
"question": "I’m intrigued by musical styles I’m not familiar with and want to find out more.",
"editable": false
}
}
]
74 changes: 74 additions & 0 deletions backend/experiment/fixtures/questioninseries.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
[
{
"model": "experiment.questioninseries",
"pk": 1,
"fields": {
"question_series": 1,
"question": "msi_01_music_activities",
"index": 1
}
},
{
"model": "experiment.questioninseries",
"pk": 2,
"fields": {
"question_series": 1,
"question": "msi_03_writing",
"index": 2
}
},
{
"model": "experiment.questioninseries",
"pk": 3,
"fields": {
"question_series": 1,
"question": "msi_08_intrigued_styles",
"index": 3
}
},
{
"model": "experiment.questioninseries",
"pk": 4,
"fields": {
"question_series": 2,
"question": "dgf_generation",
"index": 1
}
},
{
"model": "experiment.questioninseries",
"pk": 5,
"fields": {
"question_series": 2,
"question": "dgf_gender_identity",
"index": 2
}
},
{
"model": "experiment.questioninseries",
"pk": 6,
"fields": {
"question_series": 2,
"question": "P01_1",
"index": 3
}
},
{
"model": "experiment.questioninseries",
"pk": 7,
"fields": {
"question_series": 2,
"question": "P01_2",
"index": 4
}
},
{
"model": "experiment.questioninseries",
"pk": 8,
"fields": {
"question_series": 2,
"question": "P01_3",
"index": 5
}
}
]
20 changes: 20 additions & 0 deletions backend/experiment/fixtures/questionseries.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{
"model": "experiment.questionseries",
"pk": 1,
"fields": {
"experiment": 14,
"index": 1,
"randomize": false
}
},
{
"model": "experiment.questionseries",
"pk": 2,
"fields": {
"experiment": 20,
"index": 1,
"randomize": false
}
}
]
12 changes: 5 additions & 7 deletions backend/experiment/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from experiment.models import ExperimentCollection, Experiment
from experiment.rules import EXPERIMENT_RULES

from .questions import QUESTIONS_CHOICES

# session_keys for Export CSV
SESSION_CHOICES = [('experiment_id', 'Experiment ID'),
Expand Down Expand Up @@ -184,12 +183,6 @@ def __init__(self, *args, **kwargs):
choices=sorted(choices)
)

self.fields['questions'] = TypedMultipleChoiceField(
choices=QUESTIONS_CHOICES,
widget=CheckboxSelectMultiple,
required=False
)

class Meta:
model = Experiment
fields = ['name', 'slug', 'active', 'rules',
Expand Down Expand Up @@ -229,3 +222,8 @@ class TemplateForm(Form):
select_template = ChoiceField(
widget=Select,
choices=TEMPLATE_CHOICES)


class QuestionSeriesAdminForm(ModelForm):
class Media:
js = ["questionseries_admin.js"]
11 changes: 11 additions & 0 deletions backend/experiment/management/commands/createquestions.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a situation where you would want to run the bootstrap command and not the createquestions command, or vice versa? If the answer is "no", it would be nicer to not have to call this extra command from the docker startup, and rather include this utility in bootstrap.py.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

from django.core.management.base import BaseCommand

from experiment.questions import create_default_questions


class Command(BaseCommand):
help = "Creates default questions and question groups in the database"

def handle(self, *args, **options):
create_default_questions()
19 changes: 12 additions & 7 deletions backend/experiment/management/commands/templates/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ class NewExperimentRuleset(Base):
def __init__(self):

# Add your questions here
self.questions = [
question_by_key('dgf_gender_identity'),
question_by_key('dgf_generation'),
question_by_key('dgf_musical_experience', EXTRA_DEMOGRAPHICS),
question_by_key('dgf_country_of_origin'),
question_by_key('dgf_education', drop_choices=[
'isced-2', 'isced-5'])
self.question_series = [
{
"name": "Demographics",
"keys": [
'dgf_gender_identity',
'dgf_generation',
'dgf_musical_experience',
'dgf_country_of_origin',
'dgf_education_matching_pairs'
],
"randomize": False
},
]

def first_round(self, experiment):
Expand Down
77 changes: 77 additions & 0 deletions backend/experiment/migrations/0034_add_question_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Generated by Django 3.2.25 on 2024-04-16 11:06

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('experiment', '0033_rename_related'),
]

operations = [
migrations.CreateModel(
name='Question',
fields=[
('key', models.CharField(max_length=128, primary_key=True, serialize=False)),
('question', models.CharField(max_length=1024)),
('editable', models.BooleanField(default=True, editable=False)),
],
options={
'ordering': ['key'],
},
),
migrations.CreateModel(
name='QuestionInSeries',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('index', models.PositiveIntegerField()),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='experiment.question')),
],
options={
'verbose_name_plural': 'Question In Series objects',
'ordering': ['index'],
},
),
migrations.RemoveField(
model_name='experiment',
name='questions',
),
migrations.CreateModel(
name='QuestionSeries',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(default='', max_length=128)),
('index', models.PositiveIntegerField()),
('randomize', models.BooleanField(default=False)),
('experiment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='experiment.experiment')),
('questions', models.ManyToManyField(through='experiment.QuestionInSeries', to='experiment.Question')),
],
options={
'verbose_name_plural': 'Question Series',
'ordering': ['index'],
},
),
migrations.AddField(
model_name='questioninseries',
name='question_series',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='experiment.questionseries'),
),
migrations.CreateModel(
name='QuestionGroup',
fields=[
('key', models.CharField(max_length=128, primary_key=True, serialize=False)),
('editable', models.BooleanField(default=True, editable=False)),
('questions', models.ManyToManyField(to='experiment.Question')),
],
options={
'verbose_name_plural': 'Question Groups',
'ordering': ['key'],
},
),
migrations.AlterUniqueTogether(
name='questioninseries',
unique_together={('question_series', 'question')},
),
]
Loading
Loading