diff --git a/smash/web/api_urls.py b/smash/web/api_urls.py index b6eb174006ab53e076b1e057e6deee497419c46c..098a31aabd9b1032c24cd7a5d8b1ff0530df86b6 100644 --- a/smash/web/api_urls.py +++ b/smash/web/api_urls.py @@ -16,7 +16,7 @@ Including another URLconf from django.conf.urls import url from web.api_views import worker, location, subject, appointment_type, appointment, configuration, daily_planning, \ - redcap + redcap, flying_team urlpatterns = [ # appointments @@ -41,6 +41,9 @@ urlpatterns = [ # locations url(r'^locations$', location.locations, name='web.api.locations'), + # flying_teams + url(r'^flying_teams$', flying_team.flying_teams, name='web.api.flying_teams'), + # worker data url(r'^specializations$', worker.specializations, name='web.api.specializations'), url(r'^units$', worker.units, name='web.api.units'), diff --git a/smash/web/api_views/flying_team.py b/smash/web/api_views/flying_team.py new file mode 100644 index 0000000000000000000000000000000000000000..26b4a166618606fe24e0f57736345126cbbf98eb --- /dev/null +++ b/smash/web/api_views/flying_team.py @@ -0,0 +1,15 @@ +from django.contrib.auth.decorators import login_required +from django.http import JsonResponse + +from web.models import FlyingTeam + + +@login_required +def flying_teams(request): + all_flying_teams = FlyingTeam.objects.all() + data = [] + for flying_team in all_flying_teams: + data.append({"id": flying_team.id, "name": flying_team.place}) + return JsonResponse({ + "flying_teams": data + }) diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py index 946b45b25de13468f0322604072b172b1fa9581a..ab558c01959f748304588899bebd91204d84ced0 100644 --- a/smash/web/api_views/subject.py +++ b/smash/web/api_views/subject.py @@ -31,12 +31,14 @@ def referrals(request): }) -def add_column(result, name, field_name, column_list, param, columns_used_in_study=None): +def add_column(result, name, field_name, column_list, param, columns_used_in_study=None, visible_param=None): add = True if columns_used_in_study: add = getattr(columns_used_in_study, field_name) if add: - if column_list is None: + if visible_param is not None: + visible = visible_param + elif column_list is None: visible = True else: visible = getattr(column_list, field_name) @@ -67,23 +69,22 @@ def get_subject_columns(request, subject_list_type): add_column(result, "First name", "first_name", subject_columns, "string_filter") add_column(result, "Last name", "last_name", subject_columns, "string_filter") add_column(result, "Date of birth", "date_born", subject_columns, None) - add_column(result, "Contact on", "datetime_contact_reminder", study_subject_columns, None) + add_column(result, "Contact on", "datetime_contact_reminder", study_subject_columns, None, study.columns) add_column(result, "Last contact attempt", "last_contact_attempt", study_subject_list, None) + add_column(result, "Referred by", "referral", study_subject_columns, "string_filter", study.columns) add_column(result, "Location", "default_location", study_subject_columns, "location_filter", study.columns) + add_column(result, "Flying team location", "flying_team", study_subject_columns, "flying_team_filter", + study.columns) add_column(result, "Deceased", "dead", subject_columns, "yes_no_filter") add_column(result, "Resigned", "resigned", study_subject_columns, "yes_no_filter", study.columns) add_column(result, "Postponed", "postponed", study_subject_columns, "yes_no_filter", study.columns) add_column(result, "Info sent", "information_sent", study_subject_columns, "yes_no_filter", study.columns) add_column(result, "Type", "type", study_subject_columns, "type_filter", study.columns) add_column(result, "Edit", "edit", None, None) - add_column(result, "Visit 1", "visit_1", None, "visit_filter") - add_column(result, "Visit 2", "visit_2", None, "visit_filter") - add_column(result, "Visit 3", "visit_3", None, "visit_filter") - add_column(result, "Visit 4", "visit_4", None, "visit_filter") - add_column(result, "Visit 5", "visit_5", None, "visit_filter") - add_column(result, "Visit 6", "visit_6", None, "visit_filter") - add_column(result, "Visit 7", "visit_7", None, "visit_filter") - add_column(result, "Visit 8", "visit_8", None, "visit_filter") + for visit_number in range(1, 9): + visit_key = "visit_" + str(visit_number) + add_column(result, "Visit " + str(visit_number), visit_key, None, "visit_filter", + visible_param=study_subject_list.visits) return JsonResponse({"columns": result}) @@ -118,10 +119,14 @@ def get_subjects_order(subjects_to_be_ordered, order_column, order_direction): result = subjects_to_be_ordered.order_by(order_direction + 'subject__last_name') elif order_column == "nd_number": result = subjects_to_be_ordered.order_by(order_direction + 'nd_number') + elif order_column == "referral": + result = subjects_to_be_ordered.order_by(order_direction + 'referral') elif order_column == "screening_number": result = subjects_to_be_ordered.order_by(order_direction + 'screening_number') elif order_column == "default_location": result = subjects_to_be_ordered.order_by(order_direction + 'default_location') + elif order_column == "flying_team": + result = subjects_to_be_ordered.order_by(order_direction + 'flying_team') elif order_column == "dead": result = subjects_to_be_ordered.order_by(order_direction + 'subject__dead') elif order_column == "resigned": @@ -226,6 +231,8 @@ def get_subjects_filtered(subjects_to_be_filtered, filters): result = result.filter(subject__last_name__icontains=value) elif column == "nd_number": result = result.filter(nd_number__icontains=value) + elif column == "referral": + result = result.filter(referral__icontains=value) elif column == "screening_number": result = result.filter(screening_number__icontains=value) elif column == "dead": @@ -238,6 +245,8 @@ def get_subjects_filtered(subjects_to_be_filtered, filters): result = result.filter(information_sent=(value == "true")) elif column == "default_location": result = result.filter(default_location=value) + elif column == "flying_team": + result = result.filter(flying_team=value) elif column == "type": result = result.filter(type=value) elif str(column).startswith("visit_"): @@ -323,6 +332,9 @@ def serialize_subject(study_subject): location = "" if study_subject.default_location is not None: location = study_subject.default_location.name + flying_team = "" + if study_subject.flying_team is not None: + flying_team = unicode(study_subject.flying_team) visits = Visit.objects.filter(subject=study_subject).order_by('visit_number') serialized_visits = [] for visit in visits: @@ -370,7 +382,9 @@ def serialize_subject(study_subject): "last_contact_attempt": last_contact_attempt_string, "nd_number": study_subject.nd_number, "screening_number": study_subject.screening_number, + "referral": study_subject.referral, "default_location": location, + "flying_team": flying_team, "dead": get_yes_no(study_subject.subject.dead), "resigned": get_yes_no(study_subject.resigned), "postponed": get_yes_no(study_subject.postponed), diff --git a/smash/web/migrations/0081_studysubjectlist_last_contact_attempt.py b/smash/web/migrations/0081_studysubjectlist_last_contact_attempt.py index 571ce6737560d8d0f74f5689d790ff29202f8867..f08fc38f8c309472850c83f755bc79c5ed3742dd 100644 --- a/smash/web/migrations/0081_studysubjectlist_last_contact_attempt.py +++ b/smash/web/migrations/0081_studysubjectlist_last_contact_attempt.py @@ -5,16 +5,81 @@ from __future__ import unicode_literals from django.db import migrations, models +# noinspection PyUnusedLocal +# noinspection PyPep8Naming +def create_default_columns_for_SUBJECT_LIST_NO_VISIT(apps, schema_editor): + # We can't import the Study model directly as it may be a newer + # version than this migration expects. We use the historical version. + SubjectColumns = apps.get_model("web", "SubjectColumns") + subject_columns = SubjectColumns.objects.create() + subject_columns.sex = False + subject_columns.first_name = True + subject_columns.last_name = True + subject_columns.languages = False + subject_columns.default_written_communication_language = False + subject_columns.phone_number = False + subject_columns.phone_number_2 = False + subject_columns.phone_number_3 = False + subject_columns.email = False + subject_columns.date_born = False + subject_columns.address = False + subject_columns.postal_code = False + subject_columns.city = False + subject_columns.country = False + subject_columns.dead = False + subject_columns.save() + + StudyColumns = apps.get_model("web", "StudyColumns") + study_columns = StudyColumns.objects.create() + study_columns.postponed = False + study_columns.datetime_contact_reminder = False + study_columns.type = True + study_columns.default_location = True + study_columns.flying_team = True + study_columns.screening_number = True + study_columns.nd_number = False + study_columns.mpower_id = False + study_columns.comments = False + study_columns.referral = True + study_columns.diagnosis = False + study_columns.year_of_diagnosis = False + study_columns.information_sent = True + study_columns.pd_in_family = False + study_columns.resigned = False + study_columns.resign_reason = False + study_columns.save() + + class Migration(migrations.Migration): dependencies = [ ('web', '0080_auto_20171204_1341'), ] operations = [ + migrations.AddField( + model_name='studysubjectlist', + name='visits', + field=models.BooleanField(default=True, verbose_name=b'Visits summary'), + ), migrations.AddField( model_name='studysubjectlist', name='last_contact_attempt', field=models.BooleanField(default=False, verbose_name=b'Last contact attempt'), ), - migrations.RunSQL("UPDATE web_studysubjectlist SET last_contact_attempt=TRUE WHERE type='REQUIRE_CONTACT'"), + migrations.RunPython(create_default_columns_for_SUBJECT_LIST_NO_VISIT), + + migrations.RunSQL('INSERT INTO web_studysubjectlist (' + + 'study_id, ' + + 'visible_subject_study_columns_id, ' + + 'visible_subject_columns_id, ' + + 'last_contact_attempt,' + 'visits,' + 'type) ' + + "SELECT " + + "1, " + + "max(web_studycolumns.id), " + + "max(web_subjectcolumns.id), " + + "false, " + + "false, " + + "'NO_VISIT' FROM web_studycolumns, web_subjectcolumns;"), ] diff --git a/smash/web/migrations/0082_studysubjectlist_visits.py b/smash/web/migrations/0082_studysubjectlist_visits.py new file mode 100644 index 0000000000000000000000000000000000000000..a14a9f01e6749a562a5d3d03802c23e9d80741da --- /dev/null +++ b/smash/web/migrations/0082_studysubjectlist_visits.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-04 16:47 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0081_studysubjectlist_last_contact_attempt'), + ] + + operations = [ + ] diff --git a/smash/web/models/study_subject_list.py b/smash/web/models/study_subject_list.py index 887f7719e46aa3b7951909693ddbbf3465e89840..1b09054fd1391f85de1bbaccf8a390c63bc2a09c 100644 --- a/smash/web/models/study_subject_list.py +++ b/smash/web/models/study_subject_list.py @@ -41,6 +41,11 @@ class StudySubjectList(models.Model): verbose_name='Last contact attempt' ) + visits = models.BooleanField( + default=True, + verbose_name='Visits summary' + ) + type = models.CharField(max_length=50, choices=SUBJECT_LIST_CHOICES.items(), verbose_name='Type o list', diff --git a/smash/web/static/js/subject.js b/smash/web/static/js/subject.js index d6f5ac20e5bb8c737c667e3a333594a69fde9303..585d1b9f5b568e31ffa03dcc29fbe23fafcb45d1 100644 --- a/smash/web/static/js/subject.js +++ b/smash/web/static/js/subject.js @@ -128,9 +128,9 @@ function createVisibilityCheckboxes(checkboxesElement, columns) { function createSubjectsTable(params) { var tableElement = params.tableElement; var worker_locations = params.worker_locations; - var getSubjectEditUrl = params.getSubjectEditUrl; var subject_types_url = params.subject_types_url; var locations_url = params.locations_url; + var flying_teams_url = params.flying_teams_url; var subjects_url = params.subjects_url; var columnsDefinition = params.columns; @@ -173,8 +173,24 @@ function createSubjectsTable(params) { select.val(worker_locations[0].id); } }); + }); + $(tableElement).find('tfoot div[name="flying_team_filter"]').each(function () { + var obj = $(this); + obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); + var select = $('select', obj); + $.get(flying_teams_url, function (data) { + $.each(data.flying_teams, function (index, flying_team) { + select.append('<option value="' + flying_team.id + '">' + flying_team.name + '</option>'); + }); + if (worker_locations.length === 1) { + select.val(worker_locations[0].id); + } + }); }); + + + $(tableElement).find('tfoot div[name="type_filter"]').each(function () { var obj = $(this); obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); diff --git a/smash/web/templates/subjects/index.html b/smash/web/templates/subjects/index.html index 2c39cc33e2518e07eb385778dd682e48d11b94b0..b3b4aaa0e0067bce58fcf61b60877ae8a2402610 100644 --- a/smash/web/templates/subjects/index.html +++ b/smash/web/templates/subjects/index.html @@ -58,6 +58,7 @@ subject_types_url: "{% url 'web.api.subject_types' %}", locations_url: "{% url 'web.api.locations' %}", subjects_url: "{% url 'web.api.subjects' list_type %}", + flying_teams_url: "{% url 'web.api.flying_teams' %}", tableElement: document.getElementById("table"), columns: getColumns(data.columns, getSubjectEditUrl), checkboxesElement: document.getElementById("visible-column-checkboxes") diff --git a/smash/web/tests/api_views/test_flying_team.py b/smash/web/tests/api_views/test_flying_team.py new file mode 100644 index 0000000000000000000000000000000000000000..ca1895f2b74b795a382f4a5fc7c844675ecdc9eb --- /dev/null +++ b/smash/web/tests/api_views/test_flying_team.py @@ -0,0 +1,38 @@ +# coding=utf-8 +import json + +from django.contrib.auth.models import User +from django.test import Client +from django.test import TestCase +from django.urls import reverse + +from web.tests.functions import create_worker, create_flying_team + + +class TestFlyingTeamApi(TestCase): + def setUp(self): + self.client = Client() + username = 'piotr' + password = 'top_secret' + self.user = User.objects.create_user( + username=username, email='jacob@bla', password=password) + self.worker = create_worker(self.user) + self.client.login(username=username, password=password) + + def test_flying_teams(self): + flying_team_name = "some flying_team" + + response = self.client.get(reverse('web.api.flying_teams')) + self.assertEqual(response.status_code, 200) + + create_flying_team(flying_team_name) + + response = self.client.get(reverse('web.api.flying_teams')) + flying_teams = json.loads(response.content)['flying_teams'] + + found = False + for flying_team in flying_teams: + if flying_team['name'] == flying_team_name: + found = True + + self.assertTrue(found) diff --git a/smash/web/tests/api_views/test_subject.py b/smash/web/tests/api_views/test_subject.py index 71f3adc756e5c49e2cd25ceba76ad23e3aacdc78..b3da6195882493d7c95980d006977720ebf2bb12 100644 --- a/smash/web/tests/api_views/test_subject.py +++ b/smash/web/tests/api_views/test_subject.py @@ -10,11 +10,11 @@ from django.urls import reverse from web.api_views.subject import get_subjects_order, get_subjects_filtered, serialize_subject from web.models import StudySubject, Appointment, Study -from web.models.constants import GLOBAL_STUDY_ID +from web.models.constants import GLOBAL_STUDY_ID, SUBJECT_TYPE_CHOICES_PATIENT, SUBJECT_TYPE_CHOICES_CONTROL from web.models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT, \ StudySubjectList from web.tests.functions import create_study_subject, create_worker, create_get_suffix, create_visit, \ - create_appointment, create_empty_study_columns, create_contact_attempt + create_appointment, create_empty_study_columns, create_contact_attempt, create_flying_team from web.views.notifications import get_today_midnight_date logger = logging.getLogger(__name__) @@ -207,6 +207,13 @@ class TestApi(TestCase): self.check_subject_ordered("default_location", [subject]) + def test_subjects_sort_flying_team(self): + subject = self.study_subject + subject.flying_team = create_flying_team() + subject.save() + + self.check_subject_ordered("flying_team", [subject]) + def test_subjects_sort_screening_number(self): subject = self.study_subject subject.screening_number = "PPP" @@ -229,6 +236,17 @@ class TestApi(TestCase): self.check_subject_ordered("last_name", [subject, subject2]) + def test_subjects_sort_referral(self): + subject = self.study_subject + subject.referral = "XXX" + subject.save() + + subject2 = create_study_subject(2) + subject2.referral = "YYY" + subject2.save() + + self.check_subject_ordered("referral", [subject, subject2]) + def test_subjects_sort_dead(self): study_subject = self.study_subject study_subject.subject.dead = True @@ -337,15 +355,50 @@ class TestApi(TestCase): self.check_subject_filtered([["default_location", str(subject.default_location.id)]], [subject]) self.check_subject_filtered([["default_location", "-1"]], []) + def test_subjects_filter_flying_team(self): + subject = self.study_subject + subject.flying_team = create_flying_team() + subject.save() + + self.check_subject_filtered([["flying_team", str(subject.flying_team.id)]], [subject]) + self.check_subject_filtered([["flying_team", "-1"]], []) + + def test_subjects_filter_information_sent(self): + subject = self.study_subject + subject.information_sent = True + subject.save() + + self.check_subject_filtered([["information_sent", "true"]], [subject]) + self.check_subject_filtered([["information_sent", "false"]], []) + + def test_subjects_filter_referral(self): + subject = self.study_subject + subject.referral = "xyz" + subject.save() + + self.check_subject_filtered([["referral", "xyz"]], [subject]) + self.check_subject_filtered([["referral", "false"]], []) + + def test_subjects_filter_type(self): + subject = self.study_subject + subject.type = SUBJECT_TYPE_CHOICES_PATIENT + subject.save() + + self.check_subject_filtered([["type", SUBJECT_TYPE_CHOICES_PATIENT]], [subject]) + self.check_subject_filtered([["type", SUBJECT_TYPE_CHOICES_CONTROL]], []) + def test_subjects_filter_unknown(self): subject = self.study_subject self.check_subject_filtered([["some_unknown", "unknown data"]], [subject]) self.check_subject_filtered([["", ""]], [subject]) + self.check_subject_filtered([["", None]], [subject]) def test_serialize_subject(self): study_subject = self.study_subject study_subject.subject.dead = True + study_subject.flying_team = create_flying_team() + study_subject.datetime_contact_reminder = get_today_midnight_date() create_contact_attempt(subject=study_subject) create_visit(subject=study_subject) diff --git a/smash/web/tests/functions.py b/smash/web/tests/functions.py index b7089c3b332d4ad18a560b6ab15645b02517e5a4..05db98cec598cf423bcc8ff80766687d79cef101 100644 --- a/smash/web/tests/functions.py +++ b/smash/web/tests/functions.py @@ -214,7 +214,7 @@ def create_configuration_item(): def create_flying_team(place=None): if place is None: - place = "CHEM" + place = "CHEM " result = FlyingTeam.objects.create(place=place) return result