Skip to content

Commit

Permalink
#52 - Global settings (#79)
Browse files Browse the repository at this point in the history
* Add SiteConfiguration

* Add views and urls

* Fix "active" nav class implementation

* Add site config to experiment config endpoint

* Fix mypy issues

* Add T&Cs API endpoint
  • Loading branch information
mixxorz authored Dec 22, 2020
1 parent 55085b5 commit 7aed325
Show file tree
Hide file tree
Showing 26 changed files with 357 additions and 11 deletions.
13 changes: 12 additions & 1 deletion flare_portal/api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,18 @@
},
)

SiteConfigurationType = TypedDict(
"SiteConfigurationType",
{
"terms_and_conditions": str,
},
)

ConfigType = TypedDict(
"ConfigType",
{"experiment": ExperimentType, "modules": List[ModuleConfigType]},
{
"experiment": ExperimentType,
"config": SiteConfigurationType,
"modules": List[ModuleConfigType],
},
)
14 changes: 14 additions & 0 deletions flare_portal/api/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,17 @@ class ConfigurationForm(forms.Form):
to_field_name="participant_id",
error_messages={"invalid_choice": "Invalid participant"},
)


class TermsAndConditionsForm(forms.Form):
participant = forms.ModelChoiceField(
queryset=Participant.objects.all(),
to_field_name="participant_id",
error_messages={"invalid_choice": "Invalid participant"},
)

def save(self) -> Participant:
participant = self.cleaned_data["participant"]
participant.agreed_to_terms_and_conditions = True
participant.save()
return participant
36 changes: 36 additions & 0 deletions flare_portal/api/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@
FearConditioningModule,
Participant,
)
from flare_portal.site_config.models import SiteConfiguration


class ConfigurationAPIViewTest(TestCase):
def test_post(self) -> None:
config = SiteConfiguration.get_solo()
config.terms_and_conditions = "Some T&Cs"
config.save()

experiment: Experiment = ExperimentFactory()
ParticipantFactory(participant_id="Flare.ABCDEF", experiment=experiment)

Expand Down Expand Up @@ -62,6 +67,12 @@ def test_post(self) -> None:
),
},
)
self.assertEqual(
data["config"],
{
"terms_and_conditions": "Some T&Cs",
},
)
self.assertEqual(
data["modules"],
[
Expand Down Expand Up @@ -102,6 +113,31 @@ def test_validation(self) -> None:
self.assertEqual(resp.json(), {"participant": ["Invalid participant"]})


class TermsAndConditionsAPIViewTest(TestCase):
def test_agree_to_terms(self) -> None:
participant = ParticipantFactory(participant_id="Flare.ABCDEF")

resp = self.client.post(
reverse("api:terms_and_conditions"),
{"participant": "Flare.ABCDEF"},
content_type="application/json",
)

self.assertEqual(200, resp.status_code)

self.assertEqual(
resp.json(),
{
"participant": "Flare.ABCDEF",
"agreed_to_terms_and_conditions": True,
},
)

participant.refresh_from_db()

self.assertTrue(participant.agreed_to_terms_and_conditions)


class ModuleDataAPIViewTest(TestCase):
def test_post(self) -> None:
experiment: Experiment = ExperimentFactory()
Expand Down
5 changes: 5 additions & 0 deletions flare_portal/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@
app_name = "api"
urlpatterns = [
path("configuration/", views.configuration_api_view, name="configuration"),
path(
"terms-and-conditions/",
views.terms_and_conditions_api_view,
name="terms_and_conditions",
),
] + data_api_registry.urls
28 changes: 27 additions & 1 deletion flare_portal/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
from rest_framework.views import APIView

from flare_portal.experiments.models import Experiment
from flare_portal.site_config.models import SiteConfiguration

from . import constants
from .forms import ConfigurationForm
from .forms import ConfigurationForm, TermsAndConditionsForm


class ConfigurationAPIView(APIView):
Expand All @@ -15,6 +16,7 @@ def post(self, request: Request, format: str = None) -> Response:

if form.is_valid():
experiment: Experiment = form.cleaned_data["participant"].experiment
config = SiteConfiguration.get_solo()
return Response(
constants.ConfigType(
experiment=constants.ExperimentType(
Expand All @@ -35,6 +37,9 @@ def post(self, request: Request, format: str = None) -> Response:
experiment.rating_scale_anchor_label_right
),
),
config=constants.SiteConfigurationType(
terms_and_conditions=config.terms_and_conditions,
),
modules=[
module.get_module_config()
for module in (
Expand All @@ -48,3 +53,24 @@ def post(self, request: Request, format: str = None) -> Response:


configuration_api_view = ConfigurationAPIView.as_view()


class TermsAndConditionsAPIView(APIView):
def post(self, request: Request, format: str = None) -> Response:
form = TermsAndConditionsForm(request.data)

if form.is_valid():
participant = form.save()
return Response(
{
"participant": participant.participant_id,
"agreed_to_terms_and_conditions": (
participant.agreed_to_terms_and_conditions
),
}
)

raise serializers.ValidationError(form.errors)


terms_and_conditions_api_view = TermsAndConditionsAPIView.as_view()
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.1.4 on 2020-12-22 05:26

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("experiments", "0027_module_ordering"),
]

operations = [
migrations.AddField(
model_name="participant",
name="agreed_to_terms_and_conditions",
field=models.BooleanField(default=False),
),
]
1 change: 1 addition & 0 deletions flare_portal/experiments/models/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class Participant(models.Model):
experiment = models.ForeignKey(
"experiments.Experiment", on_delete=models.CASCADE, related_name="participants"
)
agreed_to_terms_and_conditions = models.BooleanField(default=False)

created_at = models.DateTimeField(auto_now_add=True)
udpated_at = models.DateTimeField(auto_now=True)
Expand Down
2 changes: 2 additions & 0 deletions flare_portal/experiments/models/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ class Meta:


class BasicInfoData(BaseData):
# Note: When changing the gender options, also change the corresponding
# list in the flare-app repo.
GENDERS = Choices(
("male", "Male"),
("female", "Female"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% load experiments_tags %}
{% load experiments_tags util_tags %}

<div class="card">
<div class="card-header">
Expand Down Expand Up @@ -47,11 +47,12 @@ <h6>Last updated</h6>
<span class="icon mr-3"><i class="fe fe-layers"></i></span>Modules
</a>
<div class="dropdown">
<a href="#" class="list-group-item list-group-item-action d-flex align-items-center" data-toggle="dropdown">
{% get_module_data_types as data_types %}

<a href="#" class="list-group-item list-group-item-action d-flex align-items-center{% for dt in data_types %}{% get_data_list_url dt as path %} {% active path %}{% endfor %}" data-toggle="dropdown">
<span class="icon mr-3"><i class="fe fe-database"></i></span>Data
</a>
<div class="dropdown-menu">
{% get_module_data_types as data_types %}
{% for data_type in data_types %}
<a class="dropdown-item" href="{% get_data_list_url data_type %}">{{ data_type.get_module_name|capfirst }}</a>
{% endfor %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ <h3 class="card-title">Participants</h3>
<th></th>
<th>Login ID</th>
<th>Created</th>
<th>Agreed to T&amp;Cs</th>
<th>Delete</th>
<th></th>
</tr>
Expand All @@ -65,6 +66,11 @@ <h3 class="card-title">Participants</h3>
{% endfor %}
</td>
<td>{{ form.instance.created_at|default:"" }}</td>

<td>
<span class="status-icon {% if form.instance.agreed_to_terms_and_conditions %}bg-success{% else %}bg-danger{% endif %}"></span> {{ form.instance.agreed_to_terms_and_conditions|yesno|title }}
</td>

<td>
{% if form.instance.pk %}
<label class="custom-control custom-checkbox" for="{{ form.DELETE.id_for_label }}">
Expand Down
2 changes: 2 additions & 0 deletions flare_portal/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"scout_apm.django",
"flare_portal.api",
"flare_portal.experiments",
"flare_portal.site_config",
"flare_portal.users",
"flare_portal.utils",
"django.contrib.admin",
Expand All @@ -64,6 +65,7 @@
"django.contrib.staticfiles",
"django.contrib.sitemaps",
"django.forms",
"solo",
"widget_tweaks",
]

Expand Down
Empty file.
33 changes: 33 additions & 0 deletions flare_portal/site_config/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 3.1.4 on 2020-12-21 16:33

from typing import List, Tuple

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies: List[Tuple[str, str]] = []

operations = [
migrations.CreateModel(
name="SiteConfiguration",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("terms_and_conditions", models.TextField(blank=True)),
],
options={
"abstract": False,
},
),
]
Empty file.
10 changes: 10 additions & 0 deletions flare_portal/site_config/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.db import models

from solo.models import SingletonModel


class SiteConfiguration(SingletonModel):
terms_and_conditions = models.TextField(blank=True)

def __str__(self) -> str:
return "Site configuration"
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{% extends "base.html" %}

{% block title %}Update global FLARe settings{% endblock title %}

{% block content %}
<div class="container">
<div class="page-header">
<h1 class="page-title">Update global FLARe settings</h1>
</div>
<div class="row">
<div class="col-lg-6">
<div class="card">
<form action="" method="POST">
{% csrf_token %}

<div class="card-body">
{% if form.non_field_errors %}
<div class="card-alert alert alert-danger my-3">
{% for error in form.non_field_errors %}{{ error }}{% endfor %}
</div>
{% endif %}

{% for field in form %}
{% include "includes/form-group.html" %}
{% endfor %}

</div>

<div class="card-footer">
<div class="d-flex">
<div class="btn-list ml-auto">
<button type="submit" class="btn btn-primary">Save</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock content %}
Empty file.
Loading

0 comments on commit 7aed325

Please sign in to comment.