From b4330dabd89f4184ea041c43c9b8ff45be104cc3 Mon Sep 17 00:00:00 2001
From: Carlos Vega <carlos.vega@uni.lu>
Date: Wed, 31 Oct 2018 11:40:11 +0100
Subject: [PATCH] added excluded and excluded reason fields to studysubject

---
 smash/web/api_views/subject.py               | 130 ++++++++++++------
 smash/web/models/study_columns.py            | 135 ++++++++++---------
 smash/web/models/study_subject.py            |  14 ++
 smash/web/tests/models/test_study_subject.py |  15 +++
 4 files changed, 187 insertions(+), 107 deletions(-)

diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py
index 65039448..881fb347 100644
--- a/smash/web/api_views/subject.py
+++ b/smash/web/api_views/subject.py
@@ -19,7 +19,8 @@ logger = logging.getLogger(__name__)
 
 # noinspection PyUnusedLocal
 def cities(request):
-    result_subjects = Subject.objects.filter(city__isnull=False).values_list('city').distinct()
+    result_subjects = Subject.objects.filter(
+        city__isnull=False).values_list('city').distinct()
     return JsonResponse({
         "cities": [x[0] for x in result_subjects]
     })
@@ -27,7 +28,8 @@ def cities(request):
 
 # noinspection PyUnusedLocal
 def referrals(request):
-    result_subjects = StudySubject.objects.filter(referral__isnull=False).values_list('referral').distinct()
+    result_subjects = StudySubject.objects.filter(
+        referral__isnull=False).values_list('referral').distinct()
     return JsonResponse({
         "referrals": [x[0] for x in result_subjects]
     })
@@ -35,7 +37,8 @@ def referrals(request):
 
 def get_subject_columns(request, subject_list_type):
     study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
-    study_subject_lists = StudySubjectList.objects.filter(study=study, type=subject_list_type)
+    study_subject_lists = StudySubjectList.objects.filter(
+        study=study, type=subject_list_type)
     if len(study_subject_lists) > 0:
         study_subject_list = study_subject_lists[0]
         subject_columns = study_subject_list.visible_subject_columns
@@ -46,29 +49,44 @@ def get_subject_columns(request, subject_list_type):
         study_subject_columns = StudyColumns()
 
     result = []
-    add_column(result, "ND", "nd_number", study_subject_columns, "string_filter", study.columns)
-    add_column(result, "Screening", "screening_number", study_subject_columns, "string_filter", study.columns)
-    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, "Social Security Number", "social_security_number", subject_columns, "string_filter")
+    add_column(result, "ND", "nd_number", study_subject_columns,
+               "string_filter", study.columns)
+    add_column(result, "Screening", "screening_number",
+               study_subject_columns, "string_filter", study.columns)
+    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, "Social Security Number",
+               "social_security_number", 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, 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, "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, "Health partner name", "health_partner_first_name", None, "string_filter",
                add_param=study.columns.health_partner,
                visible_param=study_subject_columns.health_partner)
     add_column(result, "Health partner last name", "health_partner_last_name", None, "string_filter",
                add_param=study.columns.health_partner,
                visible_param=study_subject_columns.health_partner)
-    add_column(result, "Location", "default_location", study_subject_columns, "location_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, "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, "Excluded", "excluded",
+               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, sortable=False)
     for visit_number in range(1, 9):
         visit_key = "visit_" + str(visit_number)
@@ -104,9 +122,11 @@ def get_subjects_order(subjects_to_be_ordered, order_column, order_direction, co
     else:
         order_direction = "-"
     if order_column == "first_name":
-        result = subjects_to_be_ordered.order_by(order_direction + 'subject__first_name')
+        result = subjects_to_be_ordered.order_by(
+            order_direction + 'subject__first_name')
     elif order_column == "last_name":
-        result = subjects_to_be_ordered.order_by(order_direction + 'subject__last_name')
+        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":
@@ -117,40 +137,53 @@ def get_subjects_order(subjects_to_be_ordered, order_column, order_direction, co
         else:
             pattern = column_filters[u'screening_number']
         result = subjects_to_be_ordered.all()
-        result = sorted(result, key=lambda t: t.sort_matched_screening_first(pattern, reverse=order_direction == '-'), reverse = order_direction == '-' )
+        result = sorted(result, key=lambda t: t.sort_matched_screening_first(
+            pattern, reverse=order_direction == '-'), reverse=order_direction == '-')
     elif order_column == "default_location":
-        result = subjects_to_be_ordered.order_by(order_direction + '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')
+        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')
+        result = subjects_to_be_ordered.order_by(
+            order_direction + 'subject__dead')
     elif order_column == "resigned":
         result = subjects_to_be_ordered.order_by(order_direction + 'resigned')
     elif order_column == "information_sent":
-        result = subjects_to_be_ordered.order_by(order_direction + 'information_sent')
+        result = subjects_to_be_ordered.order_by(
+            order_direction + 'information_sent')
     elif order_column == "health_partner_first_name":
-        result = subjects_to_be_ordered.order_by(order_direction + 'health_partner__first_name')
+        result = subjects_to_be_ordered.order_by(
+            order_direction + 'health_partner__first_name')
     elif order_column == "health_partner_last_name":
-        result = subjects_to_be_ordered.order_by(order_direction + 'health_partner__last_name')
+        result = subjects_to_be_ordered.order_by(
+            order_direction + 'health_partner__last_name')
     elif order_column == "social_security_number":
-        result = subjects_to_be_ordered.order_by(order_direction + 'subject__social_security_number')
+        result = subjects_to_be_ordered.order_by(
+            order_direction + 'subject__social_security_number')
     elif order_column == "postponed":
         result = subjects_to_be_ordered.order_by(order_direction + 'postponed')
+    elif order_column == "excluded":
+        result = subjects_to_be_ordered.order_by(order_direction + 'excluded')
     elif order_column == "type":
         result = subjects_to_be_ordered.order_by(order_direction + 'type')
     elif order_column == "id":
         result = subjects_to_be_ordered.order_by(order_direction + 'id')
     elif order_column == "date_born":
-        result = subjects_to_be_ordered.order_by(order_direction + 'subject__date_born')
+        result = subjects_to_be_ordered.order_by(
+            order_direction + 'subject__date_born')
     elif order_column == "datetime_contact_reminder":
-        result = subjects_to_be_ordered.order_by(order_direction + 'datetime_contact_reminder')
+        result = subjects_to_be_ordered.order_by(
+            order_direction + 'datetime_contact_reminder')
     elif order_column == "last_contact_attempt":
         # noinspection SpellCheckingInspection
         result = subjects_to_be_ordered.annotate(sort_contact_attempt=Max("contactattempt__datetime_when")).order_by(
             order_direction + 'sort_contact_attempt')
     elif str(order_column).startswith("visit_"):
         visit_number = get_visit_number_from_visit_x_string(order_column)
-        result = order_by_visit(subjects_to_be_ordered, order_direction, visit_number)
+        result = order_by_visit(subjects_to_be_ordered,
+                                order_direction, visit_number)
     else:
         logger.warn("Unknown sort column: " + str(order_column))
     return result
@@ -166,8 +199,10 @@ def filter_by_visit(result, visit_number, visit_type):
     datetime_begin_filter = 'visit_' + str(visit_number) + '_datetime_begin'
     datetime_end_filter = 'visit_' + str(visit_number) + '_datetime_end'
     is_finished_filter = 'visit_' + str(visit_number) + '_is_finished'
-    finished_appointments_filter = 'visit_' + str(visit_number) + '_finished_appointments'
-    scheduled_appointments_filter = 'visit_' + str(visit_number) + '_scheduled_appointments'
+    finished_appointments_filter = 'visit_' + \
+        str(visit_number) + '_finished_appointments'
+    scheduled_appointments_filter = 'visit_' + \
+        str(visit_number) + '_scheduled_appointments'
 
     # this is hack... instead of providing True/False value this field contain 1/0 value, the problem is that we need
     # to provide aggregate function for the interacting parameter
@@ -179,11 +214,13 @@ def filter_by_visit(result, visit_number, visit_type):
 
     # number of finished appointments
     result = result.annotate(**{finished_appointments_filter: Count(Case(When(
-        Q(visit__appointment__status=Appointment.APPOINTMENT_STATUS_FINISHED) & Q(visit__visit_number=visit_number),
+        Q(visit__appointment__status=Appointment.APPOINTMENT_STATUS_FINISHED) & Q(
+            visit__visit_number=visit_number),
         then=1)))})
     # number of scheduled appointments
     result = result.annotate(**{scheduled_appointments_filter: Count(Case(When(
-        Q(visit__appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED) & Q(visit__visit_number=visit_number),
+        Q(visit__appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED) & Q(
+            visit__visit_number=visit_number),
         then=1)))})
 
     # when visit starts
@@ -216,7 +253,8 @@ def filter_by_visit(result, visit_number, visit_type):
             filter(**{datetime_end_filter + "__gt": get_today_midnight_date()}). \
             filter(**{scheduled_appointments_filter: 0})
     elif visit_type == "UPCOMING":
-        result = result.filter(**{datetime_begin_filter + "__gt": get_today_midnight_date()})
+        result = result.filter(
+            **{datetime_begin_filter + "__gt": get_today_midnight_date()})
 
     return result
 
@@ -242,6 +280,8 @@ def get_subjects_filtered(subjects_to_be_filtered, filters):
             result = result.filter(resigned=(value == "true"))
         elif column == "postponed":
             result = result.filter(postponed=(value == "true"))
+        elif column == "excluded":
+            result = result.filter(excluded=(value == "true"))
         elif column == "information_sent":
             result = result.filter(information_sent=(value == "true"))
         elif column == "health_partner_first_name":
@@ -249,7 +289,8 @@ def get_subjects_filtered(subjects_to_be_filtered, filters):
         elif column == "health_partner_last_name":
             result = result.filter(health_partner__last_name__icontains=value)
         elif column == "social_security_number":
-            result = result.filter(subject__social_security_number__icontains=value)
+            result = result.filter(
+                subject__social_security_number__icontains=value)
         elif column == "default_location":
             result = result.filter(default_location=value)
         elif column == "flying_team":
@@ -273,14 +314,16 @@ def get_subjects_filtered(subjects_to_be_filtered, filters):
 
 def subjects(request, type):
     try:
-        # id of the query from dataTable: https://datatables.net/manual/server-side
+        # 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")
+        order_column = request.GET.get(
+            "columns[" + str(order) + "][data]", "last_name")
 
         filters = get_filters_for_data_table_request(request)
 
@@ -289,7 +332,8 @@ def subjects(request, type):
         count = all_subjects.count()
 
         filtered_subjects = get_subjects_filtered(all_subjects, filters)
-        ordered_subjects = get_subjects_order(filtered_subjects, order_column, order_dir, column_filters=dict(filters))
+        ordered_subjects = get_subjects_order(
+            filtered_subjects, order_column, order_dir, column_filters=dict(filters))
         sliced_subjects = ordered_subjects[start:(start + length)]
 
         result_subjects = sliced_subjects
@@ -323,7 +367,8 @@ def types(request):
 def serialize_subject(study_subject):
     location = location_to_str(study_subject.default_location)
     flying_team = flying_team_to_str(study_subject.flying_team)
-    visits = Visit.objects.filter(subject=study_subject).order_by('visit_number')
+    visits = Visit.objects.filter(
+        subject=study_subject).order_by('visit_number')
     serialized_visits = []
     for visit in visits:
         if visit.datetime_begin < get_today_midnight_date():
@@ -355,8 +400,10 @@ def serialize_subject(study_subject):
             "datetime_start": serialize_date(visit.datetime_begin),
             "datetime_end": serialize_date(visit.datetime_end),
         })
-    contact_reminder = serialize_datetime(study_subject.datetime_contact_reminder)
-    contact_attempts = ContactAttempt.objects.filter(subject=study_subject).order_by("-datetime_when")
+    contact_reminder = serialize_datetime(
+        study_subject.datetime_contact_reminder)
+    contact_attempts = ContactAttempt.objects.filter(
+        subject=study_subject).order_by("-datetime_when")
     if len(contact_attempts) > 0:
         last_contact_attempt = contact_attempts[0]
         last_contact_attempt_string = serialize_datetime(last_contact_attempt.datetime_when) + "<br/>" + unicode(
@@ -385,6 +432,7 @@ def serialize_subject(study_subject):
         "dead": bool_to_yes_no(study_subject.subject.dead),
         "resigned": bool_to_yes_no(study_subject.resigned),
         "postponed": bool_to_yes_no(study_subject.postponed),
+        "excluded": bool_to_yes_no(study_subject.excluded),
         "information_sent": bool_to_yes_no(study_subject.information_sent),
         "health_partner_first_name": health_partner_first_name,
         "health_partner_last_name": health_partner_last_name,
diff --git a/smash/web/models/study_columns.py b/smash/web/models/study_columns.py
index 565ad55d..aae985a0 100644
--- a/smash/web/models/study_columns.py
+++ b/smash/web/models/study_columns.py
@@ -5,6 +5,7 @@ from web.models.constants import BOOL_CHOICES
 
 
 class StudyColumns(models.Model):
+
     class Meta:
         app_label = 'web'
 
@@ -14,101 +15,103 @@ class StudyColumns(models.Model):
     )
 
     datetime_contact_reminder = models.BooleanField(
-                                                    default=True,
-                                                    verbose_name='Please make a contact on'
-                                                    )
+        default=True,
+        verbose_name='Please make a contact on'
+    )
     type = models.BooleanField(
-                               default=True,
-                               verbose_name='Type'
-                               )
+        default=True,
+        verbose_name='Type'
+    )
 
     default_location = models.BooleanField(
-                                           default=True,
-                                           verbose_name='Default appointment location',
-                                           )
+        default=True,
+        verbose_name='Default appointment location',
+    )
 
     flying_team = models.BooleanField(
-                                      default=True,
-                                      verbose_name='Default flying team location (if applicable)',
-                                      )
+        default=True,
+        verbose_name='Default flying team location (if applicable)',
+    )
 
     screening_number = models.BooleanField(
-                                           default=True,
-                                           verbose_name='Screening number',
-                                           )
+        default=True,
+        verbose_name='Screening number',
+    )
     nd_number = models.BooleanField(
-                                    default=True,
-                                    verbose_name='ND number',
-                                    )
+        default=True,
+        verbose_name='ND number',
+    )
     mpower_id = models.BooleanField(
-                                    default=True,
-                                    verbose_name='MPower ID'
-                                    )
+        default=True,
+        verbose_name='MPower ID'
+    )
     comments = models.BooleanField(
-                                   default=True,
-                                   verbose_name='Comments'
-                                   )
+        default=True,
+        verbose_name='Comments'
+    )
     referral = models.BooleanField(
-                                   default=True,
-                                   verbose_name='Referred by'
-                                   )
+        default=True,
+        verbose_name='Referred by'
+    )
     diagnosis = models.BooleanField(
-                                    default=True,
-                                    verbose_name='Diagnosis'
-                                    )
+        default=True,
+        verbose_name='Diagnosis'
+    )
     year_of_diagnosis = models.BooleanField(
-                                            default=True,
-                                            verbose_name='Year of diagnosis (YYYY)'
-                                            )
+        default=True,
+        verbose_name='Year of diagnosis (YYYY)'
+    )
 
     information_sent = models.BooleanField(
-                                           default=True,
-                                           verbose_name='Information sent',
-                                           )
+        default=True,
+        verbose_name='Information sent',
+    )
     pd_in_family = models.BooleanField(
-                                       default=True,
-                                       verbose_name='PD in family',
-                                       )
+        default=True,
+        verbose_name='PD in family',
+    )
     resigned = models.BooleanField(
-                                   default=True,
-                                   verbose_name='Resigned',
-                                   )
+        default=True,
+        verbose_name='Resigned',
+    )
     resign_reason = models.BooleanField(
-                                        default=True,
-                                        verbose_name='Resign reason'
-                                        )
+        default=True,
+        verbose_name='Resign reason'
+    )
+
+    excluded = models.BooleanField(default=False, verbose_name='Excluded')
 
     referral_letter = models.BooleanField(
-                                          default=False,
-                                          verbose_name='Referral letter'
-                                          )
+        default=False,
+        verbose_name='Referral letter'
+    )
 
     health_partner = models.BooleanField(
-                                         default=False,
-                                         verbose_name='Health partner'
-                                         )
+        default=False,
+        verbose_name='Health partner'
+    )
 
     health_partner_feedback_agreement = models.BooleanField(
-                                                            default=False,
-                                                            verbose_name='Agrees to give information to referral'
-                                                            )
+        default=False,
+        verbose_name='Agrees to give information to referral'
+    )
 
     screening = models.BooleanField(
-                                    default=False,
-                                    verbose_name='Screening'
-                                    )
+        default=False,
+        verbose_name='Screening'
+    )
 
     previously_in_study = models.BooleanField(
-                                              default=False,
-                                              verbose_name='Previously in PDP study',
-                                              )
+        default=False,
+        verbose_name='Previously in PDP study',
+    )
 
     voucher_types = models.BooleanField(
-                                        default=False,
-                                        verbose_name='Voucher types',
-                                        )
+        default=False,
+        verbose_name='Voucher types',
+    )
 
     vouchers = models.BooleanField(
-                                   default=False,
-                                   verbose_name='Vouchers',
-                                   )
+        default=False,
+        verbose_name='Vouchers',
+    )
diff --git a/smash/web/models/study_subject.py b/smash/web/models/study_subject.py
index e6242553..dc7cedb0 100644
--- a/smash/web/models/study_subject.py
+++ b/smash/web/models/study_subject.py
@@ -32,6 +32,11 @@ class StudySubject(models.Model):
         self.finish_all_visits()
         self.finish_all_appointments()
 
+    def mark_as_excluded(self):
+        self.excluded = True
+        self.finish_all_visits()
+        self.finish_all_appointments()
+
     subject = models.ForeignKey("web.Subject",
                                 verbose_name='Subject',
                                 editable=False,
@@ -161,6 +166,15 @@ class StudySubject(models.Model):
                                      blank=True,
                                      verbose_name='Resign reason'
                                      )
+    excluded = models.BooleanField(
+        verbose_name='Excluded',
+        default=False,
+        editable=True
+    )
+    exclude_reason = models.TextField(max_length=2000,
+                                      blank=True,
+                                      verbose_name='Exclude reason'
+                                      )
 
     def sort_matched_screening_first(self, pattern, reverse=False):
         parts = self.screening_number.split(';')
diff --git a/smash/web/tests/models/test_study_subject.py b/smash/web/tests/models/test_study_subject.py
index f91762f3..50a850a5 100644
--- a/smash/web/tests/models/test_study_subject.py
+++ b/smash/web/tests/models/test_study_subject.py
@@ -24,6 +24,21 @@ class SubjectModelTests(TestCase):
         self.assertEquals(
             Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status)
 
+    def test_mark_as_excluded(self):
+        subject = create_study_subject()
+        visit = create_visit(subject)
+        appointment = create_appointment(visit)
+
+        subject.mark_as_excluded()
+        appointment_status = Appointment.objects.filter(id=appointment.id)[
+            0].status
+        visit_finished = Visit.objects.filter(id=visit.id)[0].is_finished
+
+        self.assertTrue(subject.excluded)
+        self.assertTrue(visit_finished)
+        self.assertEquals(
+            Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status)
+
     def test_check_nd_number_regex(self):
         # delete everything
         StudySubject.objects.all().delete()
-- 
GitLab