From 57d5b3ab99a803b20584d920831884451bb78634 Mon Sep 17 00:00:00 2001 From: Piotr Gawron <piotr.gawron@uni.lu> Date: Tue, 5 Dec 2017 14:25:18 +0100 Subject: [PATCH] api that provides list of visits --- smash/web/api_urls.py | 6 +- smash/web/api_views/visit.py | 170 ++++++++++++++++++ .../web/migrations/0083_auto_20171205_1251.py | 41 +++++ smash/web/models/__init__.py | 4 +- smash/web/models/study_visit_list.py | 38 ++++ smash/web/models/visit_columns.py | 34 ++++ smash/web/tests/api_views/test_visit.py | 51 ++++++ 7 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 smash/web/api_views/visit.py create mode 100644 smash/web/migrations/0083_auto_20171205_1251.py create mode 100644 smash/web/models/study_visit_list.py create mode 100644 smash/web/models/visit_columns.py create mode 100644 smash/web/tests/api_views/test_visit.py diff --git a/smash/web/api_urls.py b/smash/web/api_urls.py index 098a31aa..08be1500 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, flying_team + redcap, flying_team, visit urlpatterns = [ # appointments @@ -38,6 +38,10 @@ urlpatterns = [ name='web.api.subjects.columns'), url(r'^subject_types', subject.types, name='web.api.subject_types'), + # visits data + url(r'^visits/(?P<visit_list_type>[A-z]+)$', visit.visits, name='web.api.visits'), + url(r'^visits:columns/(?P<visit_list_type>[A-z]+)$', visit.get_visit_columns, name='web.api.visits.columns'), + # locations url(r'^locations$', location.locations, name='web.api.locations'), diff --git a/smash/web/api_views/visit.py b/smash/web/api_views/visit.py new file mode 100644 index 00000000..d8ce0613 --- /dev/null +++ b/smash/web/api_views/visit.py @@ -0,0 +1,170 @@ +import logging + +from django.contrib.auth.decorators import login_required +from django.http import JsonResponse + +from web.models import SubjectColumns +from web.models import Visit, Study, VisitColumns, StudyVisitList +from web.models.constants import GLOBAL_STUDY_ID +from web.models.study_visit_list import VISIT_LIST_GENERIC + +logger = logging.getLogger(__name__) + + +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 visible_param is not None: + visible = visible_param + elif column_list is None: + visible = True + else: + visible = getattr(column_list, field_name) + result.append({ + "type": field_name, + "name": name, + "filter": param, + "visible": visible + }) + + +# noinspection PyUnusedLocal +@login_required +def get_visit_columns(request, visit_list_type): + study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0] + study_visit_lists = StudyVisitList.objects.filter(study=study, type=visit_list_type) + if len(study_visit_lists) > 0: + visit_list = study_visit_lists[0] + visit_columns = visit_list.visible_visit_columns + visit_subject_columns = visit_list.visible_visit_columns + else: + visit_list = StudyVisitList() + visit_columns = VisitColumns() + visit_subject_columns = SubjectColumns() + + result = [] + add_column(result, "First name", "first_name", visit_subject_columns, "string_filter") + add_column(result, "Last name", "last_name", visit_subject_columns, "string_filter") + add_column(result, "Visit begins", "datetime_begin", visit_columns, None) + add_column(result, "Visit ends", "datetime_end", visit_columns, None) + add_column(result, "Finished", "is_finished", visit_columns, "yes_no_filter") + add_column(result, "Post mail sent", "post_mail_sent", visit_columns, "yes_no_filter") + add_column(result, "Visit number", "visit_number", visit_columns, "integer_filter") + add_column(result, "Edit", "edit", None, None) + + return JsonResponse({"columns": result}) + + +# noinspection PyUnusedLocal +@login_required +def get_visits(request, visit_type): + if visit_type == VISIT_LIST_GENERIC: + return Visit.objects.all() + else: + raise TypeError("Unknown query type: " + visit_type) + + +def get_visits_order(visits_to_be_ordered, order_column, order_direction): + result = visits_to_be_ordered + if order_direction == "asc": + order_direction = "" + else: + order_direction = "-" + if order_column == "first_name": + result = visits_to_be_ordered.order_by(order_direction + 'subject__subject__first_name') + elif order_column == "last_name": + result = visits_to_be_ordered.order_by(order_direction + 'subject__subject__last_name') + else: + logger.warn("Unknown sort column: " + str(order_column)) + return result + + +def get_visits_filtered(visits_to_be_filtered, filters): + result = visits_to_be_filtered + for row in filters: + column = row[0] + value = row[1] + if column == "first_name": + result = result.filter(subject__subject__first_name__icontains=value) + elif column == "last_name": + result = result.filter(subject__subject__last_name__icontains=value) + else: + message = "UNKNOWN filter: " + if column is None: + message += "[None]" + else: + message += str(column) + logger.warn(message) + + return result + + +@login_required +def visits(request, visit_list_type): + # id of the query from dataTable: https://datatables.net/manual/server-side + draw = int(request.GET.get("draw", "-1")) + + start = int(request.GET.get("start", "0")) + length = int(request.GET.get("length", "10")) + order = int(request.GET.get("order[0][column]", "0")) + order_dir = request.GET.get("order[0][dir]", "asc") + order_column = request.GET.get("columns[" + str(order) + "][data]", "last_name") + + filters = [] + column_id = 0 + while request.GET.get("columns[" + str(column_id) + "][search][value]", "unknown") != "unknown": + val = request.GET.get("columns[" + str(column_id) + "][search][value]", "unknown") + if val != "": + filters.append([request.GET.get("columns[" + str(column_id) + "][data]"), val]) + column_id += 1 + + all_visits = get_visits(request, visit_list_type) + + count = all_visits.count() + + ordered_visits = get_visits_order(all_visits, order_column, order_dir) + filtered_visits = get_visits_filtered(ordered_visits, filters) + sliced_visits = filtered_visits[start:(start + length)] + + result_visits = sliced_visits + + count_filtered = filtered_visits.count() + + data = [] + for visit in result_visits: + data.append(serialize_visit(visit)) + + return JsonResponse({ + "draw": draw, + "recordsTotal": count, + "recordsFiltered": count_filtered, + "data": data, + }) + + +def serialize_visit(visit): + datetime_begin = serialize_date(visit.datetime_begin) + datetime_end = serialize_date(visit.datetime_end) + + result = { + "first_name": visit.subject.subject.first_name, + "last_name": visit.subject.subject.last_name, + "datetime_begin": datetime_begin, + "datetime_end": datetime_end, + "is_finished": visit.is_finished, + "post_mail_sent": visit.post_mail_sent, + "visit_number": visit.visit_number, + "id": visit.id, + } + + return result + + +def serialize_date(date): + if date is not None: + result = date.strftime('%Y-%m-%d') + else: + result = "" + return result diff --git a/smash/web/migrations/0083_auto_20171205_1251.py b/smash/web/migrations/0083_auto_20171205_1251.py new file mode 100644 index 00000000..cce682bd --- /dev/null +++ b/smash/web/migrations/0083_auto_20171205_1251.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-12-05 12:51 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0082_studysubjectlist_visits'), + ] + + operations = [ + migrations.CreateModel( + name='StudyVisitList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.CharField(choices=[(b'GENERIC', b'Generic')], max_length=50, verbose_name=b'Type o list')), + ('study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.Study')), + ('visible_subject_columns', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.SubjectColumns')), + ], + ), + migrations.CreateModel( + name='VisitColumns', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('datetime_begin', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Visit starts date')), + ('datetime_end', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Visit ends date')), + ('is_finished', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Is finished')), + ('post_mail_sent', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Post mail sent')), + ('visit_number', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Visit number')), + ], + ), + migrations.AddField( + model_name='studyvisitlist', + name='visible_visit_columns', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.VisitColumns'), + ), + ] diff --git a/smash/web/models/__init__.py b/smash/web/models/__init__.py index 7d37f387..746b15bf 100644 --- a/smash/web/models/__init__.py +++ b/smash/web/models/__init__.py @@ -10,6 +10,7 @@ from appointment_type_link import AppointmentTypeLink from country import Country from subject_columns import SubjectColumns from study_columns import StudyColumns +from visit_columns import VisitColumns from notification_columns import StudyNotificationParameters from study import Study from room import Room @@ -24,6 +25,7 @@ from language import Language from subject import Subject from study_subject import StudySubject from study_subject_list import StudySubjectList +from study_visit_list import StudyVisitList from contact_attempt import ContactAttempt from mail_template import MailTemplate from missing_subject import MissingSubject @@ -32,4 +34,4 @@ from inconsistent_subject import InconsistentSubject, InconsistentField __all__ = [Study, FlyingTeam, Appointment, AppointmentType, Availability, Holiday, Item, Language, Location, Room, Subject, StudySubject, StudySubjectList, SubjectColumns, StudyNotificationParameters, Visit, Worker, ContactAttempt, ConfigurationItem, MailTemplate, AppointmentTypeLink, MissingSubject, - InconsistentSubject, InconsistentField, Country, StudyColumns] + InconsistentSubject, InconsistentField, Country, StudyColumns, VisitColumns, StudyVisitList] diff --git a/smash/web/models/study_visit_list.py b/smash/web/models/study_visit_list.py new file mode 100644 index 00000000..b9c926a5 --- /dev/null +++ b/smash/web/models/study_visit_list.py @@ -0,0 +1,38 @@ +# coding=utf-8 +from django.db import models + +from web.models import Study, SubjectColumns, VisitColumns + +VISIT_LIST_GENERIC = "GENERIC" + +VISIT_LIST_CHOICES = { + VISIT_LIST_GENERIC: 'Generic', +} + + +class StudyVisitList(models.Model): + class Meta: + app_label = 'web' + + study = models.ForeignKey( + Study, + on_delete=models.CASCADE, + null=False, + ) + + visible_visit_columns = models.ForeignKey( + VisitColumns, + on_delete=models.CASCADE, + null=False, + ) + + visible_subject_columns = models.ForeignKey( + SubjectColumns, + on_delete=models.CASCADE, + null=False, + ) + + type = models.CharField(max_length=50, + choices=VISIT_LIST_CHOICES.items(), + verbose_name='Type o list', + ) diff --git a/smash/web/models/visit_columns.py b/smash/web/models/visit_columns.py new file mode 100644 index 00000000..06958709 --- /dev/null +++ b/smash/web/models/visit_columns.py @@ -0,0 +1,34 @@ +# coding=utf-8 +from django.db import models + +from web.models.constants import BOOL_CHOICES + + +class VisitColumns(models.Model): + class Meta: + app_label = 'web' + + datetime_begin = models.BooleanField(choices=BOOL_CHOICES, + verbose_name='Visit starts date', + default=True + ) + + datetime_end = models.BooleanField(choices=BOOL_CHOICES, + verbose_name='Visit ends date', + default=True + ) + + is_finished = models.BooleanField(choices=BOOL_CHOICES, + verbose_name='Is finished', + default=True + ) + + post_mail_sent = models.BooleanField(choices=BOOL_CHOICES, + verbose_name='Post mail sent', + default=True + ) + + visit_number = models.BooleanField(choices=BOOL_CHOICES, + verbose_name='Visit number', + default=True + ) diff --git a/smash/web/tests/api_views/test_visit.py b/smash/web/tests/api_views/test_visit.py new file mode 100644 index 00000000..09760d48 --- /dev/null +++ b/smash/web/tests/api_views/test_visit.py @@ -0,0 +1,51 @@ +# coding=utf-8 +import json +import logging + +from django.urls import reverse + +from web.models.study_visit_list import VISIT_LIST_GENERIC +from web.tests import LoggedInWithWorkerTestCase +from web.tests.functions import create_study_subject, create_get_suffix, create_visit + +logger = logging.getLogger(__name__) + + +class TestVisitApi(LoggedInWithWorkerTestCase): + def setUp(self): + super(TestVisitApi, self).setUp() + self.study_subject = create_study_subject() + + def test_get_columns(self): + response = self.client.get(reverse('web.api.visits.columns', kwargs={'visit_list_type': VISIT_LIST_GENERIC})) + self.assertEqual(response.status_code, 200) + + columns = json.loads(response.content)['columns'] + self.assertTrue(len(columns) > 0) + + def test_visits_general(self): + response = self.client.get(reverse('web.api.visits', kwargs={'visit_list_type': VISIT_LIST_GENERIC})) + self.assertEqual(response.status_code, 200) + + def test_visits_general_search(self): + name = "Piotrek" + self.study_subject.subject.first_name = name + self.study_subject.subject.save() + create_visit(self.study_subject) + + params = { + "columns[0][search][value]": "another_name", + "columns[0][data]": "first_name" + } + url = ("%s" + create_get_suffix(params)) % reverse('web.api.visits', + kwargs={'visit_list_type': VISIT_LIST_GENERIC}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertFalse(name in response.content) + + params["columns[0][search][value]"] = name + url = ("%s" + create_get_suffix(params)) % reverse('web.api.visits', + kwargs={'visit_list_type': VISIT_LIST_GENERIC}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertTrue(name in response.content) -- GitLab