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