From 914daae62b9cd563ec7d0b897d6f43a4fbe2f6cc Mon Sep 17 00:00:00 2001
From: bbonf
Date: Thu, 14 Nov 2024 10:51:22 +0100
Subject: [PATCH 1/4] changed experiment duration from free-text field to
minutes (integer)
---
integration_tests/test_invite.py | 17 +++--
lab/conftest.py | 2 +-
lab/experiments/email.py | 4 +-
lab/experiments/forms/experiment_forms.py | 2 -
.../locale/en/LC_MESSAGES/django.po | 5 +-
.../locale/nl/LC_MESSAGES/django.po | 5 +-
..._experiment_confirmation_email_and_more.py | 33 +++++++++
lab/experiments/models/appointment_models.py | 6 +-
lab/experiments/models/experiment_models.py | 4 +-
lab/experiments/templates/call/home.html | 4 +-
.../experiments/detail_parts/info.html | 4 +-
lab/experiments/test_exclusion.py | 70 ++++++++++++-------
lab/experiments/test_views.py | 8 +--
lab/experiments/views/call_views.py | 5 +-
lab/integration_tests/conftest.py | 5 +-
lab/integration_tests/test_experiments.py | 4 +-
parent/parent/templates/parent/overview.html | 2 +-
17 files changed, 117 insertions(+), 63 deletions(-)
create mode 100644 lab/experiments/migrations/0023_alter_experiment_confirmation_email_and_more.py
diff --git a/integration_tests/test_invite.py b/integration_tests/test_invite.py
index 50266814..5904aeea 100644
--- a/integration_tests/test_invite.py
+++ b/integration_tests/test_invite.py
@@ -33,7 +33,7 @@ def test_cancel_appointment_from_email(apps, participant, mailbox, link_from_mai
DefaultCriteria = apps.lab.get_model('experiments', 'DefaultCriteria')
User = apps.lab.get_model('main', 'User')
Appointment = apps.lab.get_model('experiments', 'Appointment')
- experiment = Experiment.objects.create(defaultcriteria=DefaultCriteria.objects.create())
+ experiment = Experiment.objects.create(duration=10, session_duration=20)
# somewhat abusing the get_model() calls above to setup django for the following to work
from experiments.models import make_appointment
@@ -41,10 +41,9 @@ def test_cancel_appointment_from_email(apps, participant, mailbox, link_from_mai
leader = User.objects.first() # admin
start = timezone.now()
- end = start + timedelta(hours=1)
experiment.leaders.add(leader)
experiment.save()
- appointment = make_appointment(experiment, participant, leader, start, end)
+ appointment = make_appointment(experiment, participant, leader, start)
send_appointment_mail(appointment, prepare_appointment_mail(appointment))
@@ -71,7 +70,8 @@ def test_appointment_in_parent_overview(apps, participant, mailbox, page, login_
Experiment = apps.lab.get_model('experiments', 'Experiment')
DefaultCriteria = apps.lab.get_model('experiments', 'DefaultCriteria')
User = apps.lab.get_model('main', 'User')
- experiment = Experiment.objects.create(defaultcriteria=DefaultCriteria.objects.create(),
+ experiment = Experiment.objects.create(duration=10,
+ session_duration=20,
name='Test Experiment')
# somewhat abusing the get_model() calls above to setup django for the following to work
@@ -79,10 +79,9 @@ def test_appointment_in_parent_overview(apps, participant, mailbox, page, login_
leader = User.objects.first() # admin
start = timezone.now()
- end = start + timedelta(hours=1)
experiment.leaders.add(leader)
experiment.save()
- appointment = make_appointment(experiment, participant, leader, start, end)
+ appointment = make_appointment(experiment, participant, leader, start)
login_as(participant.email)
try:
@@ -99,7 +98,8 @@ def test_past_appointment_not_in_parent_overview(apps, participant, mailbox, pag
Experiment = apps.lab.get_model('experiments', 'Experiment')
DefaultCriteria = apps.lab.get_model('experiments', 'DefaultCriteria')
User = apps.lab.get_model('main', 'User')
- experiment = Experiment.objects.create(defaultcriteria=DefaultCriteria.objects.create(),
+ experiment = Experiment.objects.create(duration=10,
+ session_duration=20,
name='Test Experiment')
# somewhat abusing the get_model() calls above to setup django for the following to work
@@ -107,10 +107,9 @@ def test_past_appointment_not_in_parent_overview(apps, participant, mailbox, pag
leader = User.objects.first() # admin
start = timezone.now() - timedelta(days=30)
- end = start + timedelta(hours=1)
experiment.leaders.add(leader)
experiment.save()
- appointment = make_appointment(experiment, participant, leader, start, end)
+ appointment = make_appointment(experiment, participant, leader, start)
login_as(participant.email)
try:
diff --git a/lab/conftest.py b/lab/conftest.py
index 74b4da58..14ad253a 100644
--- a/lab/conftest.py
+++ b/lab/conftest.py
@@ -9,7 +9,7 @@
@pytest.fixture
def sample_experiment(admin_user, db):
- yield admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create())
+ yield admin_user.experiments.create(duration=15, session_duration=30)
@pytest.fixture
diff --git a/lab/experiments/email.py b/lab/experiments/email.py
index dd9a8c50..2abd579f 100644
--- a/lab/experiments/email.py
+++ b/lab/experiments/email.py
@@ -43,8 +43,8 @@ def __init__(self, *args, **kwargs):
Het experiment
- Het experiment duurt maximaal {{experiment_duration}}.
- Omdat we ook de procedure uitleggen en er achteraf tijd is voor vragen, zult u ongeveer {{session_duration}} kwijt zijn
+ Het experiment duurt maximaal {{experiment_duration}} minuten.
+ Omdat we ook de procedure uitleggen en er achteraf tijd is voor vragen, zult u ongeveer {{session_duration}} minuten kwijt zijn
aan uw bezoek aan het Babylab. In de bijlage van deze mail vindt u meer informatie over het experiment en onze werkwijze.
diff --git a/lab/experiments/forms/experiment_forms.py b/lab/experiments/forms/experiment_forms.py
index 408ff316..9726414b 100644
--- a/lab/experiments/forms/experiment_forms.py
+++ b/lab/experiments/forms/experiment_forms.py
@@ -48,8 +48,6 @@ class Meta:
exclude = ("defaultcriteria",)
widgets = {
"name": forms.TextInput,
- "duration": forms.TextInput,
- "session_duration": forms.TextInput,
"task_description": forms.Textarea(
{
"rows": 7,
diff --git a/lab/experiments/locale/en/LC_MESSAGES/django.po b/lab/experiments/locale/en/LC_MESSAGES/django.po
index bbf5add2..e0abeeaa 100644
--- a/lab/experiments/locale/en/LC_MESSAGES/django.po
+++ b/lab/experiments/locale/en/LC_MESSAGES/django.po
@@ -917,4 +917,7 @@ msgid "default_criteria:attribute:min_age"
msgstr "Min. age"
msgid "default_criteria:attribute:max_age"
-msgstr "Max. age"
\ No newline at end of file
+msgstr "Max. age"
+
+msgid "experiments:duration:minutes"
+msgstr "minutes"
\ No newline at end of file
diff --git a/lab/experiments/locale/nl/LC_MESSAGES/django.po b/lab/experiments/locale/nl/LC_MESSAGES/django.po
index 8e08fa5e..9008921c 100644
--- a/lab/experiments/locale/nl/LC_MESSAGES/django.po
+++ b/lab/experiments/locale/nl/LC_MESSAGES/django.po
@@ -949,4 +949,7 @@ msgstr "Geëxcludeerd"
#: experiments/models/appointment_models.py:38
msgid "experiments:appointment:outcome:canceled"
-msgstr "Geannuleerd"
\ No newline at end of file
+msgstr "Geannuleerd"
+
+msgid "experiments:duration:minutes"
+msgstr "minuten"
\ No newline at end of file
diff --git a/lab/experiments/migrations/0023_alter_experiment_confirmation_email_and_more.py b/lab/experiments/migrations/0023_alter_experiment_confirmation_email_and_more.py
new file mode 100644
index 00000000..418576fd
--- /dev/null
+++ b/lab/experiments/migrations/0023_alter_experiment_confirmation_email_and_more.py
@@ -0,0 +1,33 @@
+# Generated by Django 4.2.11 on 2024-11-14 09:09
+
+from django.db import migrations, models
+import experiments.email
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("experiments", "0022_alter_experiment_confirmation_email"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="experiment",
+ name="confirmation_email",
+ field=models.TextField(
+ default='
Beste {{parent_name}},
\n \n U heeft een afspraak gemaakt om mee te doen met het experiment:\n {{experiment_name}} \n We verwachten u en {{participant_name}} op: \n Datum: {{date}} \n Tijd: {{time}} uur \n Locatie: Janskerkhof 13a (let op: dit is de groene voordeur met de helling ervoor). \n
\n \n Gezondheidsklachten \n Wij vragen u de afspraak te verzetten wanneer uw kind vlak voor de afspraak gehoorproblemen\n en/of oorontsteking heeft en dus mogelijk minder goed hoort.\n
\n \n Aankomst in het Babylab \n Als u aanbelt bij Janskerkhof 13a en via de intercom zegt dat u voor het Babylab komt, dan wordt de deur op afstand voor u geopend.\n Wanneer u binnenkomt, kunt u gelijk na de hal met de lift (of met de trap) naar beneden.\n Daar vindt u aan uw rechterhand de wachtkamer, waar u plaats kunt nemen. De onderzoeksassistent zal u daar komen ophalen.\n
\n \n Het experiment \n Het experiment duurt maximaal {{experiment_duration}} minuten.\n Omdat we ook de procedure uitleggen en er achteraf tijd is voor vragen, zult u ongeveer {{session_duration}} minuten kwijt zijn\n aan uw bezoek aan het Babylab. In de bijlage van deze mail vindt u meer informatie over het experiment en onze werkwijze.\n
\n \n Het is belangrijk voor ons onderzoek dat er geen broertje of zusje meekomt tijdens het bezoek aan het lab.\n Als u hierover van tevoren een andere afspraak heeft gemaakt met de assistent van het Babylab, dan geldt uiteraard die afspraak.\n
\n \n Afspraak verzetten/afzeggen \n Als u deze afspraak wilt afzeggen, kunt u dat doen via deze link .\n Doe dat a.u.b. minstens 24 uur van tevoren. Als u vlak van tevoren ontdekt dat u verhinderd bent,\n neem dan contact op met de testleider ({{leader_name}}, email: babylab.ilslabs@uu.nl).\n
\n \n Meer informatie over het Babylab, bijvoorbeeld de routebeschrijving, kunt u vinden op de\n website van het Babylab .\n Wij danken u alvast hartelijk voor uw medewerking. Zonder uw deelname kunnen wij geen onderzoek doen!\n
\n \n Vriendelijke groet, \n Het team van het Babylab voor Taalonderzoek\n
',
+ help_text=experiments.email.AppointmentConfirmEmail.help_text,
+ verbose_name="experiment:attribute:confirmation_email",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="experiment",
+ name="duration",
+ field=models.IntegerField(verbose_name="experiment:attribute:duration"),
+ ),
+ migrations.AlterField(
+ model_name="experiment",
+ name="session_duration",
+ field=models.IntegerField(verbose_name="experiment:attribute:session_duration"),
+ ),
+ ]
diff --git a/lab/experiments/models/appointment_models.py b/lab/experiments/models/appointment_models.py
index 138eb13e..086af65e 100644
--- a/lab/experiments/models/appointment_models.py
+++ b/lab/experiments/models/appointment_models.py
@@ -1,4 +1,4 @@
-from datetime import datetime
+from datetime import datetime, timedelta
from cdh.mail.classes import TemplateEmail
from django.db import models
@@ -108,10 +108,12 @@ def is_canceled(self):
return self.outcome == Appointment.Outcome.CANCELED
-def make_appointment(experiment: Experiment, participant: Participant, leader: User, start: datetime, end: datetime):
+def make_appointment(experiment: Experiment, participant: Participant, leader: User, start: datetime):
if leader not in experiment.leaders.all():
raise ValueError(f'{leader} is not a leader in the experiment "{experiment}"')
+ # appointment end is determined by the experiment's session duration
+ end = start + timedelta(minutes=experiment.session_duration)
timeslot = TimeSlot.objects.create(start=start, end=end, experiment=experiment)
appointment = Appointment.objects.create(
participant=participant, timeslot=timeslot, experiment=experiment, leader=leader
diff --git a/lab/experiments/models/experiment_models.py b/lab/experiments/models/experiment_models.py
index 9545b7f6..97276953 100644
--- a/lab/experiments/models/experiment_models.py
+++ b/lab/experiments/models/experiment_models.py
@@ -25,9 +25,9 @@ def _get_dt_2_hours_ago() -> datetime:
class Experiment(models.Model):
name = models.TextField(_("experiment:attribute:name"))
- duration = models.TextField(_("experiment:attribute:duration"))
+ duration = models.IntegerField(_("experiment:attribute:duration"))
- session_duration = models.TextField(_("experiment:attribute:session_duration"))
+ session_duration = models.IntegerField(_("experiment:attribute:session_duration"))
# how many participants are aimed for
recruitment_target = models.PositiveIntegerField(
diff --git a/lab/experiments/templates/call/home.html b/lab/experiments/templates/call/home.html
index 019110a3..1eb3ec74 100644
--- a/lab/experiments/templates/call/home.html
+++ b/lab/experiments/templates/call/home.html
@@ -57,11 +57,11 @@ Experiment details
{% get_verbose_field_name "experiments" "Experiment" "duration" %}
- {{ experiment.duration }}
+ {{ experiment.duration }} {% translate "experiments:duration:minutes" %}
{% get_verbose_field_name "experiments" "Experiment" "session_duration" %}
- {{ experiment.session_duration }}
+ {{ experiment.session_duration }} {% translate "experiments:duration:minutes" %}
{% get_verbose_field_name "experiments" "Experiment" "location" %}
diff --git a/lab/experiments/templates/experiments/detail_parts/info.html b/lab/experiments/templates/experiments/detail_parts/info.html
index ac4f27c5..298fbc1f 100644
--- a/lab/experiments/templates/experiments/detail_parts/info.html
+++ b/lab/experiments/templates/experiments/detail_parts/info.html
@@ -58,7 +58,7 @@
{% trans 'experiment:attribute:duration' %}
- {{ experiment.duration }}
+ {{ experiment.duration }} {% translate "experiments:duration:minutes" %}
@@ -66,7 +66,7 @@
{% trans 'experiment:attribute:session_duration' %}
- {{ experiment.session_duration }}
+ {{ experiment.session_duration }} {% translate "experiments:duration:minutes" %}
diff --git a/lab/experiments/test_exclusion.py b/lab/experiments/test_exclusion.py
index 1bae0f0f..9f2d9fb8 100644
--- a/lab/experiments/test_exclusion.py
+++ b/lab/experiments/test_exclusion.py
@@ -8,9 +8,9 @@
def test_excluded_experiment(admin_user, sample_participant):
- experiment_1 = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create())
- experiment_2 = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create())
- experiment_3 = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create())
+ experiment_1 = admin_user.experiments.create(duration=10, session_duration=20)
+ experiment_2 = admin_user.experiments.create(duration=10, session_duration=20)
+ experiment_3 = admin_user.experiments.create(duration=10, session_duration=20)
# mark participant as having participated in experiment #1
timeslot = TimeSlot.objects.create(
@@ -29,8 +29,8 @@ def test_excluded_experiment(admin_user, sample_participant):
def test_required_experiment(admin_user, sample_participant):
- experiment_1 = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create())
- experiment_2 = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create())
+ experiment_1 = admin_user.experiments.create(duration=10, session_duration=20)
+ experiment_2 = admin_user.experiments.create(duration=10, session_duration=20)
# cannot participate in #2 unless participated in #1
experiment_2.required_experiments.add(experiment_1)
@@ -50,9 +50,9 @@ def test_required_experiment(admin_user, sample_participant):
def test_required_experiment_multiple(admin_user, sample_participant):
- experiment_1 = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create())
- experiment_2 = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create())
- experiment_3 = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create())
+ experiment_1 = admin_user.experiments.create(duration=10, session_duration=20)
+ experiment_2 = admin_user.experiments.create(duration=10, session_duration=20)
+ experiment_3 = admin_user.experiments.create(duration=10, session_duration=20)
# cannot participat in #3 unless participated in #1 and #2
experiment_3.required_experiments.add(experiment_1)
@@ -82,7 +82,9 @@ def set_participant_field(participant, field_name, value):
def test_parent_criterion_required(admin_user, sample_participant):
for criterion in ["dyslexic_parent", "tos_parent"]:
- experiment = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create(**{criterion: "Y"}))
+ experiment = admin_user.experiments.create(
+ duration=10, session_duration=20, defaultcriteria=DefaultCriteria.objects.create(**{criterion: "Y"})
+ )
for value in [Participant.WhichParent.FEMALE, Participant.WhichParent.MALE, Participant.WhichParent.BOTH]:
set_participant_field(sample_participant, criterion, value)
assert sample_participant in get_eligible_participants_for_experiment(
@@ -97,7 +99,9 @@ def test_parent_criterion_required(admin_user, sample_participant):
def test_parent_criterion_excluded(admin_user, sample_participant):
for criterion in ["dyslexic_parent", "tos_parent"]:
- experiment = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create(**{criterion: "N"}))
+ experiment = admin_user.experiments.create(
+ duration=10, session_duration=20, defaultcriteria=DefaultCriteria.objects.create(**{criterion: "N"})
+ )
for value in [
Participant.WhichParent.FEMALE,
Participant.WhichParent.MALE,
@@ -113,7 +117,7 @@ def test_parent_criterion_excluded(admin_user, sample_participant):
def test_parent_criterion_indifferent(admin_user, sample_participant):
for criterion in ["dyslexic_parent", "tos_parent"]:
- experiment = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create())
+ experiment = admin_user.experiments.create(duration=10, session_duration=20)
for value in [choice[0] for choice in Participant.WhichParent.choices]:
set_participant_field(sample_participant, criterion, value)
assert sample_participant in get_eligible_participants_for_experiment(
@@ -121,7 +125,7 @@ def test_parent_criterion_indifferent(admin_user, sample_participant):
), f"Participant with field {criterion} set to {value} is missing from eligible set"
experiment = admin_user.experiments.create(
- defaultcriteria=DefaultCriteria.objects.create(**{criterion: ["Y", "N"]})
+ duration=10, session_duration=20, defaultcriteria=DefaultCriteria.objects.create(**{criterion: ["Y", "N"]})
)
for value in [choice[0] for choice in Participant.WhichParent.choices]:
set_participant_field(sample_participant, criterion, value)
@@ -138,14 +142,16 @@ def test_participant_criteria(admin_user, sample_participant):
set_participant_field(sample_participant, criterion_name, option)
# indifferent
- experiment = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create())
+ experiment = admin_user.experiments.create(duration=10, session_duration=20)
assert sample_participant in get_eligible_participants_for_experiment(
experiment
), f"Participant with field {criterion_name} set to {option} is missing from eligible set"
# included
experiment = admin_user.experiments.create(
- defaultcriteria=DefaultCriteria.objects.create(**{criterion_name: [option]})
+ duration=10,
+ session_duration=20,
+ defaultcriteria=DefaultCriteria.objects.create(**{criterion_name: [option]}),
)
assert sample_participant in get_eligible_participants_for_experiment(
experiment
@@ -153,7 +159,9 @@ def test_participant_criteria(admin_user, sample_participant):
# excluded
experiment = admin_user.experiments.create(
- defaultcriteria=DefaultCriteria.objects.create(**{criterion_name: list(options - set([option]))})
+ duration=10,
+ session_duration=20,
+ defaultcriteria=DefaultCriteria.objects.create(**{criterion_name: list(options - set([option]))}),
)
assert sample_participant not in get_eligible_participants_for_experiment(
experiment
@@ -168,36 +176,46 @@ def test_multilingual_criterion(admin_user, sample_participant):
sample_participant.data.languages.set([en, nl])
# indifferent
- experiment = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create())
+ experiment = admin_user.experiments.create(duration=10, session_duration=20)
assert sample_participant in get_eligible_participants_for_experiment(experiment)
# included
- experiment = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create(multilingual=["Y"]))
+ experiment = admin_user.experiments.create(
+ duration=10, session_duration=20, defaultcriteria=DefaultCriteria.objects.create(multilingual=["Y"])
+ )
assert sample_participant in get_eligible_participants_for_experiment(experiment)
# excluded
- experiment = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create(multilingual=["N"]))
+ experiment = admin_user.experiments.create(
+ duration=10, session_duration=20, defaultcriteria=DefaultCriteria.objects.create(multilingual=["N"])
+ )
assert sample_participant not in get_eligible_participants_for_experiment(experiment)
# monolingual participant
sample_participant.data.languages.set([en])
# indifferent
- experiment = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create())
+ experiment = admin_user.experiments.create(duration=10, session_duration=20)
assert sample_participant in get_eligible_participants_for_experiment(experiment)
# included
- experiment = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create(multilingual=["N"]))
+ experiment = admin_user.experiments.create(
+ duration=10, session_duration=20, defaultcriteria=DefaultCriteria.objects.create(multilingual=["N"])
+ )
assert sample_participant in get_eligible_participants_for_experiment(experiment)
# excluded
- experiment = admin_user.experiments.create(defaultcriteria=DefaultCriteria.objects.create(multilingual=["Y"]))
+ experiment = admin_user.experiments.create(
+ duration=10, session_duration=20, defaultcriteria=DefaultCriteria.objects.create(multilingual=["Y"])
+ )
assert sample_participant not in get_eligible_participants_for_experiment(experiment)
def test_age_filters(admin_user, sample_participant):
experiment = admin_user.experiments.create(
- defaultcriteria=DefaultCriteria.objects.create(min_age_days=5, min_age_months=3)
+ duration=10,
+ session_duration=20,
+ defaultcriteria=DefaultCriteria.objects.create(min_age_days=5, min_age_months=3),
)
# too young
@@ -211,7 +229,9 @@ def test_age_filters(admin_user, sample_participant):
assert sample_participant in get_eligible_participants_for_experiment(experiment)
experiment = admin_user.experiments.create(
- defaultcriteria=DefaultCriteria.objects.create(max_age_days=5, max_age_months=3)
+ duration=10,
+ session_duration=20,
+ defaultcriteria=DefaultCriteria.objects.create(max_age_days=5, max_age_months=3),
)
# too old
@@ -225,9 +245,11 @@ def test_age_filters(admin_user, sample_participant):
assert sample_participant in get_eligible_participants_for_experiment(experiment)
experiment = admin_user.experiments.create(
+ duration=10,
+ session_duration=20,
defaultcriteria=DefaultCriteria.objects.create(
min_age_days=5, min_age_months=3, max_age_days=15, max_age_months=6
- )
+ ),
)
# too old
diff --git a/lab/experiments/test_views.py b/lab/experiments/test_views.py
index 757c2486..09a7bbef 100644
--- a/lab/experiments/test_views.py
+++ b/lab/experiments/test_views.py
@@ -21,7 +21,7 @@ class AppointmentTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = User.objects.create(username="test", is_staff=True)
- cls.experiment = Experiment.objects.create()
+ cls.experiment = Experiment.objects.create(duration=15, session_duration=30)
cls.experiment.leaders.add(cls.user)
cls.participant = Participant.objects.create(
@@ -35,7 +35,6 @@ def test_appointment_confirm(self):
data = {
"experiment": self.experiment.pk,
"start": timezone.now() + timedelta(days=1),
- "end": timezone.now() + timedelta(days=1, hours=1),
"leader": self.user.pk,
"participant": self.participant.pk,
}
@@ -49,7 +48,6 @@ def test_appointment_confirm_fail_in_past(self):
data = {
"experiment": self.experiment.pk,
"start": timezone.now() - timedelta(hours=2),
- "end": timezone.now() - timedelta(hours=1),
"leader": self.user.pk,
"participant": self.participant.pk,
}
@@ -71,7 +69,6 @@ def test_appointment_confirm_when_eligible_age(self):
data = {
"experiment": self.experiment.pk,
"start": timezone.now() + timedelta(days=7),
- "end": timezone.now() + timedelta(days=7, hours=1),
"leader": self.user.pk,
"participant": self.participant.pk,
}
@@ -92,7 +89,6 @@ def test_appointment_confirm_fail_when_ineligible(self):
data = {
"experiment": self.experiment.pk,
"start": timezone.now() + timedelta(days=7),
- "end": timezone.now() + timedelta(days=7, hours=1),
"leader": self.user.pk,
"participant": self.participant.pk,
}
@@ -107,7 +103,7 @@ class InviteTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = User.objects.create(username="test", is_staff=True)
- cls.experiment = Experiment.objects.create()
+ cls.experiment = Experiment.objects.create(duration=15, session_duration=30)
cls.experiment.leaders.add(cls.user)
cls.participant = Participant.objects.create(
diff --git a/lab/experiments/views/call_views.py b/lab/experiments/views/call_views.py
index 0b12cc5e..8330ae0b 100644
--- a/lab/experiments/views/call_views.py
+++ b/lab/experiments/views/call_views.py
@@ -93,9 +93,8 @@ def create(self, request, *args, **kwargs):
raise PermissionDenied
start = parse_datetime(request.data["start"])
- end = parse_datetime(request.data["end"])
- if end < start or start < timezone.now():
+ if start < timezone.now():
raise BadRequest("Invalid appointment time")
participant = Participant.objects.get(pk=request.data["participant"])
@@ -110,7 +109,7 @@ def create(self, request, *args, **kwargs):
pk=request.data["leader"],
)
- appointment = make_appointment(experiment, participant, leader, start, end)
+ appointment = make_appointment(experiment, participant, leader, start)
return JsonResponse(self.serializer_class(appointment).data)
def check_age_at_appointment(self, participant: Participant, experiment: Experiment, start: datetime.date) -> bool:
diff --git a/lab/integration_tests/conftest.py b/lab/integration_tests/conftest.py
index e0440657..2c2e2368 100644
--- a/lab/integration_tests/conftest.py
+++ b/lab/integration_tests/conftest.py
@@ -44,10 +44,9 @@ def as_leader(page, django_user_model, live_server):
@pytest.fixture
def sample_experiment(admin_user, db, sample_location):
yield admin_user.experiments.create(
- defaultcriteria=DefaultCriteria.objects.create(),
name="sample experiment",
- duration="10 minutes",
- session_duration="30 minutes",
+ duration=10,
+ session_duration=30,
recruitment_target=50,
task_description="task description",
responsible_researcher="dr. Lin Guist",
diff --git a/lab/integration_tests/test_experiments.py b/lab/integration_tests/test_experiments.py
index 2a2282b0..ebf76a67 100644
--- a/lab/integration_tests/test_experiments.py
+++ b/lab/integration_tests/test_experiments.py
@@ -11,8 +11,8 @@ def test_create_experiment(page, as_admin, sample_leader, sample_location):
page.get_by_role("link", name="Create experiment").click()
page.get_by_role("textbox", name="Name").fill("Experiment name")
- page.get_by_role("textbox", name="Task duration").fill("10 minutes")
- page.get_by_role("textbox", name="Session duration").fill("25 minutes")
+ page.get_by_role("textbox", name="Task duration").fill("10")
+ page.get_by_role("textbox", name="Session duration").fill("25")
page.get_by_role("spinbutton", name="Recruitment target").fill("30")
page.locator('select[name="location"]').select_option(sample_location.name)
diff --git a/parent/parent/templates/parent/overview.html b/parent/parent/templates/parent/overview.html
index bd105a5b..81d05f41 100644
--- a/parent/parent/templates/parent/overview.html
+++ b/parent/parent/templates/parent/overview.html
@@ -27,7 +27,7 @@ {{ appointment.experiment }}
Participant: {{ appointment.participant }}
Datum: {{ appointment.start|date:"l d M Y, H:i"|lower }}
Tijd: {{ appointment.start|time }}
- Totale duur bezoek: {{ appointment.session_duration }}
+ Totale duur bezoek: {{ appointment.session_duration }} minuten
Naam onderzoeker: {{ appointment.leader }}
Telefoonnummer onderzoeker: {{ appointment.contact_phone }}
From 8a44b4841d02ba96aa5cab9a0822594cf149f64b Mon Sep 17 00:00:00 2001
From: bbonf
Date: Thu, 14 Nov 2024 12:33:33 +0100
Subject: [PATCH 2/4] appointment end should be determined by start + duration
---
babex-vue/src/api.ts | 1 -
.../src/components/agenda/AgendaCalendar.vue | 6 ++++--
.../src/components/agenda/AppointmentForm.vue | 2 +-
babex-vue/src/components/invite/CallHome.vue | 20 +++++++++----------
lab/agenda/views.py | 2 +-
lab/experiments/models/appointment_models.py | 4 ++++
lab/experiments/serializers.py | 5 +++--
lab/integration_tests/test_experiments.py | 4 ++--
8 files changed, 24 insertions(+), 20 deletions(-)
diff --git a/babex-vue/src/api.ts b/babex-vue/src/api.ts
index 3ac63e92..0b8f9781 100644
--- a/babex-vue/src/api.ts
+++ b/babex-vue/src/api.ts
@@ -217,7 +217,6 @@ class GenericApiPart extends ApiPart {
interface AppointmentCreate {
start: Date,
- end: Date,
experiment: number,
leader: number,
participant: number,
diff --git a/babex-vue/src/components/agenda/AgendaCalendar.vue b/babex-vue/src/components/agenda/AgendaCalendar.vue
index ed895e25..1d229fc8 100644
--- a/babex-vue/src/components/agenda/AgendaCalendar.vue
+++ b/babex-vue/src/components/agenda/AgendaCalendar.vue
@@ -20,7 +20,7 @@
// optional experiment id for limiting feeds
experiment?: number,
- scheduling?: boolean,
+ duration?: number,
}>();
// from https://stackoverflow.com/a/64090995
@@ -130,7 +130,9 @@
allDaySlot: false,
slotMinTime: "07:00:00",
slotMaxTime: "20:00:00",
- slotDuration: props.scheduling ? "00:15:00" : "00:30:00",
+ slotDuration: {minutes: props.duration ?? 30},
+ defaultTimedEventDuration: {minutes: props.duration},
+ forceEventDuration: props.duration ? true : false,
eventTimeFormat: {
hour: '2-digit',
minute: '2-digit',
diff --git a/babex-vue/src/components/agenda/AppointmentForm.vue b/babex-vue/src/components/agenda/AppointmentForm.vue
index 7c9ee271..6a3efc99 100644
--- a/babex-vue/src/components/agenda/AppointmentForm.vue
+++ b/babex-vue/src/components/agenda/AppointmentForm.vue
@@ -101,7 +101,7 @@
{{ _('To:') }}
-
+
{{ _('Comments:') }}
diff --git a/babex-vue/src/components/invite/CallHome.vue b/babex-vue/src/components/invite/CallHome.vue
index b3965d85..fc3b1c5c 100644
--- a/babex-vue/src/components/invite/CallHome.vue
+++ b/babex-vue/src/components/invite/CallHome.vue
@@ -1,6 +1,6 @@
@@ -204,7 +202,7 @@
-
+
{{ _('Appointment details') }}
@@ -225,7 +223,7 @@
{{ _('To') }}
-
+
diff --git a/lab/agenda/views.py b/lab/agenda/views.py
index e160c81c..5460b039 100644
--- a/lab/agenda/views.py
+++ b/lab/agenda/views.py
@@ -96,7 +96,7 @@ def perform_update(self, serializer):
updated_timeslot = updated.timeslot
# check if we should inform the participant about changed time
- if original_timeslot.start != updated_timeslot.start or original_timeslot.end != updated_timeslot.end:
+ if original_timeslot.start != updated_timeslot.start:
send_appointment_mail(updated, prepare_appointment_mail(updated))
diff --git a/lab/experiments/models/appointment_models.py b/lab/experiments/models/appointment_models.py
index 086af65e..b6f2d211 100644
--- a/lab/experiments/models/appointment_models.py
+++ b/lab/experiments/models/appointment_models.py
@@ -69,7 +69,11 @@ def save(self, *args, **kwargs):
# verify that the leader is valid
if self.leader not in self.experiment.leaders.all():
raise ValueError("Leader {} is not part of experiment {}".format(self.leader, self.experiment))
+
+ # make sure the end time is correct
+ self.end = self.start + timedelta(minutes=self.experiment.session_duration)
self.timeslot.save()
+
super().save(*args, **kwargs)
def __str__(self):
diff --git a/lab/experiments/serializers.py b/lab/experiments/serializers.py
index f5bcf560..6129ae86 100644
--- a/lab/experiments/serializers.py
+++ b/lab/experiments/serializers.py
@@ -51,7 +51,7 @@ class Meta:
contact_phone = serializers.ReadOnlyField(source="leader.phonenumber")
start = serializers.DateTimeField()
- end = serializers.DateTimeField()
+ end = serializers.ReadOnlyField()
session_duration = serializers.ReadOnlyField(source="experiment.session_duration")
@@ -70,6 +70,7 @@ class Meta:
"id",
"name",
"duration",
+ "session_duration",
"task_description",
"location",
"leaders",
@@ -104,6 +105,6 @@ class Meta:
contact_phone = serializers.ReadOnlyField(source="leader.phonenumber")
start = serializers.DateTimeField()
- end = serializers.DateTimeField()
+ end = serializers.ReadOnlyField()
session_duration = serializers.ReadOnlyField(source="experiment.session_duration")
diff --git a/lab/integration_tests/test_experiments.py b/lab/integration_tests/test_experiments.py
index ebf76a67..7956924a 100644
--- a/lab/integration_tests/test_experiments.py
+++ b/lab/integration_tests/test_experiments.py
@@ -11,8 +11,8 @@ def test_create_experiment(page, as_admin, sample_leader, sample_location):
page.get_by_role("link", name="Create experiment").click()
page.get_by_role("textbox", name="Name").fill("Experiment name")
- page.get_by_role("textbox", name="Task duration").fill("10")
- page.get_by_role("textbox", name="Session duration").fill("25")
+ page.get_by_role("spinbutton", name="Task duration").fill("10")
+ page.get_by_role("spinbutton", name="Session duration").fill("25")
page.get_by_role("spinbutton", name="Recruitment target").fill("30")
page.locator('select[name="location"]').select_option(sample_location.name)
From c8f3eab074bcbc9a5bb41ca3f7be034c1ddab5bd Mon Sep 17 00:00:00 2001
From: bbonf
Date: Fri, 15 Nov 2024 10:51:51 +0100
Subject: [PATCH 3/4] appeasing the linter
---
babex-vue/src/components/invite/CallHome.vue | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/babex-vue/src/components/invite/CallHome.vue b/babex-vue/src/components/invite/CallHome.vue
index fc3b1c5c..df77dc4f 100644
--- a/babex-vue/src/components/invite/CallHome.vue
+++ b/babex-vue/src/components/invite/CallHome.vue
@@ -32,7 +32,7 @@
// event start and end times saved as separate refs because
// our DateTimePicker doesn't play nicely with fullcalendar's event object
const eventStart = ref(null);
- const eventEnd = computed(() => new Date(eventStart.value.getTime() + 60 * 1000 * props.experiment.session_duration));
+ const eventEnd = computed(() => new Date(eventStart.value!.getTime() + 60 * 1000 * props.experiment.session_duration));
const callStatus = ref(null);
const comment = ref('');
@@ -113,7 +113,7 @@
start: selectionInfo.start,
startEditable: true
});
- eventStart.value = event.value.start;
+ eventStart.value = event.value!.start;
}
else {
step.value = 1;
From 691dbe06695241a8cfd90852408d40ce9d482f5e Mon Sep 17 00:00:00 2001
From: bbonf
Date: Fri, 15 Nov 2024 11:48:27 +0100
Subject: [PATCH 4/4] computed end time in AppointmentForm + updated tests
---
.../src/components/agenda/AppointmentForm.vue | 10 ++++++----
lab/experiments/serializers.py | 2 +-
lab/integration_tests/test_agenda.py | 14 --------------
3 files changed, 7 insertions(+), 19 deletions(-)
diff --git a/babex-vue/src/components/agenda/AppointmentForm.vue b/babex-vue/src/components/agenda/AppointmentForm.vue
index 6a3efc99..75cf35be 100644
--- a/babex-vue/src/components/agenda/AppointmentForm.vue
+++ b/babex-vue/src/components/agenda/AppointmentForm.vue
@@ -1,5 +1,5 @@
@@ -101,7 +103,7 @@
{{ _('To:') }}
-
+
{{ _('Comments:') }}
@@ -121,7 +123,7 @@
-
{{ _('Save') }}
+
{{ _('Save') }}
diff --git a/lab/experiments/serializers.py b/lab/experiments/serializers.py
index 6129ae86..5ccae25e 100644
--- a/lab/experiments/serializers.py
+++ b/lab/experiments/serializers.py
@@ -17,7 +17,7 @@ class AppointmentSerializer(serializers.ModelSerializer):
class AppointmentExperimentSerializer(serializers.ModelSerializer):
class Meta:
model = Experiment
- fields = ["id", "name", "leaders"]
+ fields = ["id", "name", "leaders", "session_duration"]
leaders = ExperimentLeadersSerializer(read_only=True, many=True)
diff --git a/lab/integration_tests/test_agenda.py b/lab/integration_tests/test_agenda.py
index 12b4de2c..afa0fa1f 100644
--- a/lab/integration_tests/test_agenda.py
+++ b/lab/integration_tests/test_agenda.py
@@ -117,7 +117,6 @@ def test_agenda_modify_appointment(page, appointment_tomorrow, as_leader):
new_time = original_time + timedelta(days=3)
page.fill(".appointment-start input", new_time.strftime("%d-%m-%Y %H:%M"))
- page.fill(".appointment-end input", (new_time + timedelta(hours=1)).strftime("%d-%m-%Y %H:%M"))
page.click(".action-panel .save")
page.locator(".action-panel .save").wait_for(state="hidden")
@@ -130,16 +129,3 @@ def test_agenda_modify_appointment(page, appointment_tomorrow, as_leader):
# check that an appointment update email was sent
assert len(mail.outbox) == 1
assert mail.outbox[0].to[0] == appointment_tomorrow.participant.email
-
-
-def test_agenda_modify_appointment_illegal(page, appointment_tomorrow, as_leader):
- appointment_tomorrow.experiment.leaders.add(as_leader)
- page.locator("a").get_by_text("Agenda").click()
-
- page.click(f'td[data-date="{appointment_tomorrow.start.date()}"]')
- original_time = appointment_tomorrow.timeslot.start
- new_time = original_time + timedelta(days=3)
-
- page.fill(".appointment-start input", new_time.strftime("%d-%m-%Y %H:%M"))
-
- expect(page.locator(".action-panel .save")).to_be_disabled()