diff --git a/smash/web/api_views/appointment.py b/smash/web/api_views/appointment.py index 227d8353e60b80d3c2bb9a51f115889c86fd78ba..a51ddfc058c410f8f65bef590804cf9e793ef35e 100644 --- a/smash/web/api_views/appointment.py +++ b/smash/web/api_views/appointment.py @@ -1,5 +1,4 @@ import logging -import traceback from datetime import datetime from django.contrib.auth.decorators import login_required @@ -87,7 +86,7 @@ def serialize_appointment(appointment): subject_string = "" nd_number = screening_number = phone_numbers = appointment_types = None if appointment.visit is not None: - title = appointment.visit.follow_up_title() + title = "Visit " + str(appointment.visit.visit_number) subject = appointment.visit.subject subject_string = subject.last_name + " " + subject.first_name nd_number = subject.nd_number diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py index c1394a0dc93124e12d5f168e905989c574167baa..6d1bb1f598bf7371d94cb3e5a4e12a085f125306 100644 --- a/smash/web/api_views/subject.py +++ b/smash/web/api_views/subject.py @@ -1,12 +1,14 @@ import logging from django.contrib.auth.decorators import login_required +from django.db.models import Count, Case, When, Min +from django.db.models import Q from django.http import JsonResponse -from web.models import Subject +from web.models import Subject, Visit, Appointment from web.models.constants import SUBJECT_TYPE_CHOICES from web.views import e500_error -from web.views.notifications import get_subjects_with_no_visit, get_subjects_with_reminder +from web.views.notifications import get_subjects_with_no_visit, get_subjects_with_reminder, get_today_midnight_date from web.views.subject import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT logger = logging.getLogger(__name__) @@ -48,37 +50,126 @@ def get_subjects(request, type): raise TypeError("Unknown query type: " + type) -def get_subjects_order(subjects, order_column, order_direction): - result = subjects +def order_by_visit(subjects_to_be_ordered, order_direction, visit_number): + return subjects_to_be_ordered.annotate( + sort_visit_date=Min(Case(When(visit__visit_number=visit_number, then='visit__datetime_begin')))).order_by( + order_direction + 'sort_visit_date') + + +def get_subjects_order(subjects_to_be_ordered, order_column, order_direction): + result = subjects_to_be_ordered if order_direction == "asc": order_direction = "" else: order_direction = "-" if order_column == "first_name": - result = subjects.order_by(order_direction + 'first_name') + result = subjects_to_be_ordered.order_by(order_direction + 'first_name') elif order_column == "last_name": - result = subjects.order_by(order_direction + 'last_name') + result = subjects_to_be_ordered.order_by(order_direction + 'last_name') elif order_column == "nd_number": - result = subjects.order_by(order_direction + 'nd_number') + result = subjects_to_be_ordered.order_by(order_direction + 'nd_number') elif order_column == "screening_number": - result = subjects.order_by(order_direction + 'screening_number') + result = subjects_to_be_ordered.order_by(order_direction + 'screening_number') elif order_column == "default_location": - result = subjects.order_by(order_direction + 'default_location') + result = subjects_to_be_ordered.order_by(order_direction + 'default_location') elif order_column == "dead": - result = subjects.order_by(order_direction + 'dead') + result = subjects_to_be_ordered.order_by(order_direction + 'dead') elif order_column == "resigned": - result = subjects.order_by(order_direction + 'resigned') + result = subjects_to_be_ordered.order_by(order_direction + 'resigned') elif order_column == "information_sent": - result = subjects.order_by(order_direction + 'information_sent') + result = subjects_to_be_ordered.order_by(order_direction + 'information_sent') elif order_column == "postponed": - result = subjects.order_by(order_direction + 'postponed') + result = subjects_to_be_ordered.order_by(order_direction + 'postponed') elif order_column == "type": - result = subjects.order_by(order_direction + '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 + 'date_born') + elif order_column == "visit_1": + result = order_by_visit(subjects_to_be_ordered, order_direction, 1) + elif order_column == "visit_2": + result = order_by_visit(subjects_to_be_ordered, order_direction, 2) + elif order_column == "visit_3": + result = order_by_visit(subjects_to_be_ordered, order_direction, 3) + elif order_column == "visit_4": + result = order_by_visit(subjects_to_be_ordered, order_direction, 4) + elif order_column == "visit_5": + result = order_by_visit(subjects_to_be_ordered, order_direction, 5) + elif order_column == "visit_6": + result = order_by_visit(subjects_to_be_ordered, order_direction, 6) + elif order_column == "visit_7": + result = order_by_visit(subjects_to_be_ordered, order_direction, 7) + elif order_column == "visit_8": + result = order_by_visit(subjects_to_be_ordered, order_direction, 8) + else: + logger.warn("Unknown sort column: "+order_column) + return result + + +def filter_by_visit(result, visit_number, visit_type): + # we need to give custom names for filtering params that contain visit_number in it + # because we might want to filter by few visits and they shouldn't collide + 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' + + # 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 + # If we try to assign it with pure Case(When...) (without Count/Min/Max/Avg) we obtain duplicates + # of the results which are later on messing up with the subject list + result = result.annotate(**{ + is_finished_filter: Count(Case(When(Q(visit__is_finished=True) & Q(visit__visit_number=visit_number), then=1))) + }) + + # 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), + 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), + then=1)))}) + + # when visit starts + result = result.annotate( + **{datetime_begin_filter: Min(Case(When(visit__visit_number=visit_number, then='visit__datetime_begin')))}) + # when visit finish + result = result.annotate( + **{datetime_end_filter: Min(Case(When(visit__visit_number=visit_number, then='visit__datetime_end')))}) + + if visit_type == "DONE": + result = result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}). \ + filter(**{is_finished_filter + "__gt": 0}). \ + filter(**{finished_appointments_filter + "__gt": 0}) + elif visit_type == "MISSED": + result = result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}). \ + filter(**{is_finished_filter + "__gt": 0}). \ + filter(**{finished_appointments_filter: 0}) + elif visit_type == "EXCEED": + result = result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}). \ + filter(**{is_finished_filter: 0}). \ + filter(**{datetime_end_filter + "__lt": get_today_midnight_date()}) + elif visit_type == "IN_PROGRESS": + result = result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}). \ + filter(**{is_finished_filter: 0}). \ + filter(**{datetime_end_filter + "__gt": get_today_midnight_date()}). \ + filter(**{scheduled_appointments_filter + "__gt": 0}) + elif visit_type == "SHOULD_BE_IN_PROGRESS": + result = result.filter(**{datetime_begin_filter + "__lt": get_today_midnight_date()}). \ + filter(**{is_finished_filter: 0}). \ + 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()}) + return result -def get_subjects_filtered(subjects, filters): - result = subjects +def get_subjects_filtered(subjects_to_be_filtered, filters): + result = subjects_to_be_filtered for row in filters: column = row[0] value = row[1] @@ -102,6 +193,22 @@ def get_subjects_filtered(subjects, filters): result = result.filter(default_location=value) elif column == "type": result = result.filter(type=value) + elif column == "visit_1": + result = filter_by_visit(result, 1, value) + elif column == "visit_2": + result = filter_by_visit(result, 2, value) + elif column == "visit_3": + result = filter_by_visit(result, 3, value) + elif column == "visit_4": + result = filter_by_visit(result, 4, value) + elif column == "visit_5": + result = filter_by_visit(result, 5, value) + elif column == "visit_6": + result = filter_by_visit(result, 6, value) + elif column == "visit_7": + result = filter_by_visit(result, 7, value) + elif column == "visit_8": + result = filter_by_visit(result, 8, value) elif column == "": pass else: @@ -177,10 +284,43 @@ def get_yes_no(val): return "NO" +def serialize_subject_visit(visit): + status = "---" + appointments = visit.appointment_set.filter() + pass + + def serialize_subject(subject): location = "" if subject.default_location is not None: location = subject.default_location.name + visits = Visit.objects.filter(subject=subject).order_by('visit_number') + serialized_visits = [] + for visit in visits: + if visit.datetime_begin < get_today_midnight_date(): + if visit.is_finished: + finished_appointments_count = visit.appointment_set.filter( + status=Appointment.APPOINTMENT_STATUS_FINISHED).count() + if finished_appointments_count > 0: + status = "DONE" + else: + status = "MISSED" + elif visit.datetime_end < get_today_midnight_date(): + status = "EXCEEDED" + else: + scheduled_appointments_count = visit.appointment_set.filter( + status=Appointment.APPOINTMENT_STATUS_SCHEDULED).count() + if scheduled_appointments_count > 0: + status = "IN_PROGRESS" + else: + status = "SHOULD_BE_IN_PROGRESS" + else: + status = "UPCOMING" + serialized_visits.append({ + "status": status, + "datetime_start": visit.datetime_begin.strftime('%Y-%m-%d'), + "datetime_end": visit.datetime_end.strftime('%Y-%m-%d'), + }) result = { "first_name": subject.first_name, @@ -195,5 +335,6 @@ def serialize_subject(subject): "information_sent": get_yes_no(subject.information_sent), "type": subject.get_type_display(), "id": subject.id, + "visits": serialized_visits, } return result diff --git a/smash/web/api_views/worker.py b/smash/web/api_views/worker.py index ece5fc05954e7a2423e2c71164b237143efc1f5e..5183e3cd3fa5ae515102ed8e8d860f27f52c0c7f 100644 --- a/smash/web/api_views/worker.py +++ b/smash/web/api_views/worker.py @@ -24,7 +24,6 @@ def units(request): @login_required def workers_for_daily_planning(request): workers = get_workers_for_daily_planning(request) - print workers workers_list_for_json = [] for worker in workers: worker_dict_for_json = { diff --git a/smash/web/forms.py b/smash/web/forms.py index 10b925e99020bc6fcb3443316e8df7f4c3b9c00a..e4e4c2bb8b0594b62ea16fba51132eb68282f19b 100644 --- a/smash/web/forms.py +++ b/smash/web/forms.py @@ -320,7 +320,7 @@ class VisitDetailForm(ModelForm): class Meta: model = Visit - exclude = ['is_finished'] + exclude = ['is_finished', 'visit_number'] class VisitAddForm(ModelForm): @@ -336,7 +336,7 @@ class VisitAddForm(ModelForm): class Meta: model = Visit - exclude = ['is_finished'] + exclude = ['is_finished', 'visit_number'] def clean(self): super(VisitAddForm, self).clean() diff --git a/smash/web/migrations/0056_visit_visit_number.py b/smash/web/migrations/0056_visit_visit_number.py new file mode 100644 index 0000000000000000000000000000000000000000..3be1cb193f27909dfbb70c8cddbd9156540ba146 --- /dev/null +++ b/smash/web/migrations/0056_visit_visit_number.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-27 10:22 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0055_auto_20170925_0905'), + ] + + operations = [ + migrations.AddField( + model_name='visit', + name='visit_number', + field=models.IntegerField(default=1, verbose_name=b'Visit number'), + ), + ] diff --git a/smash/web/migrations/0057_update_visit_number_for_existing_visits.py b/smash/web/migrations/0057_update_visit_number_for_existing_visits.py new file mode 100644 index 0000000000000000000000000000000000000000..6697af45d131464e03ef50b75f4a4820dff48143 --- /dev/null +++ b/smash/web/migrations/0057_update_visit_number_for_existing_visits.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-10-27 10:22 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('web', '0056_visit_visit_number'), + ] + + operations = [ + migrations.RunSQL( + "update web_visit s set visit_number= (select count(*) from web_visit t " + + "where t.subject_id=s.subject_id and t.datetime_begin<= s.datetime_begin);") + ] diff --git a/smash/web/models/subject.py b/smash/web/models/subject.py index c0a0b4b86d55c2054a3e2ddd6d6839ffddd2cb04..756cb960ea2bbf9ec58382fefe724bce9d4c8c7e 100644 --- a/smash/web/models/subject.py +++ b/smash/web/models/subject.py @@ -168,16 +168,6 @@ class Subject(models.Model): editable=True ) - def latest_visit(self): - visits = self.visit_set.all() - if len(visits) == 0: - return None - result = visits[0] - for visit in visits: - if visit.datetime_begin > result.datetime_begin: - result = visit - return result - def __str__(self): return "%s %s" % (self.first_name, self.last_name) diff --git a/smash/web/models/visit.py b/smash/web/models/visit.py index 7554e3984307d09fd55ccaae81c12e8547fc8aef..e11b52d74a7b54d2e837369b839e68a87276be31 100644 --- a/smash/web/models/visit.py +++ b/smash/web/models/visit.py @@ -2,6 +2,8 @@ import datetime from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver from constants import BOOL_CHOICES, SUBJECT_TYPE_CHOICES_CONTROL @@ -33,16 +35,18 @@ class Visit(models.Model): blank=True, ) + # this value is automatically computed by signal handled by update_visit_number method + visit_number = models.IntegerField( + verbose_name='Visit number', + default=1 + ) + def __unicode__(self): return "%s %s" % (self.subject.first_name, self.subject.last_name) def __str__(self): return "%s %s" % (self.subject.first_name, self.subject.last_name) - def follow_up_title(self): - count = Visit.objects.filter(subject=self.subject, datetime_begin__lt=self.datetime_begin).count() - return "Visit " + str(count + 1) - def mark_as_finished(self): self.is_finished = True self.save() @@ -59,3 +63,16 @@ class Visit(models.Model): datetime_begin=visit_started + time_to_next_visit, datetime_end=visit_started + time_to_next_visit + datetime.timedelta(days=93) ) + + +@receiver(post_save, sender=Visit) +def update_visit_number(sender, instance, **kwargs): + visit = instance + if visit.subject is not None: + count = Visit.objects.filter(subject=visit.subject).filter(datetime_begin__lte=visit.datetime_begin).count() + if count != visit.visit_number: + visit.visit_number = count + visit.save() + subject_visits = Visit.objects.filter(subject=visit.subject).all() + for subject_visit in subject_visits: + update_visit_number(sender, subject_visit) diff --git a/smash/web/static/js/redcap.js b/smash/web/static/js/redcap.js index 4cdb49ae41ebb824ec9af9b79a494c61e05e5869..75efd59ee27e17650932c357a9b5ccbb94a9ce6c 100644 --- a/smash/web/static/js/redcap.js +++ b/smash/web/static/js/redcap.js @@ -2,8 +2,8 @@ function ignore_missing_subject(id, url) { $.ajax({ url: url, success: function () { - $("#ignore_" + id).hide() - $("#unignore_" + id).show() + $("#ignore_" + id).hide(); + $("#unignore_" + id).show(); $("#row_" + id).find('td').addClass("ignore-row"); } }); @@ -14,8 +14,8 @@ function unignore_missing_subject(id, url) { $.ajax({ url: url, success: function () { - $("#unignore_" + id).hide() - $("#ignore_" + id).show() + $("#unignore_" + id).hide(); + $("#ignore_" + id).show(); $("#row_" + id).find('td').removeClass("ignore-row"); } }); diff --git a/smash/web/static/js/subject.js b/smash/web/static/js/subject.js new file mode 100644 index 0000000000000000000000000000000000000000..bfee2f14996e3bbf51dce37beb3796e0304d7993 --- /dev/null +++ b/smash/web/static/js/subject.js @@ -0,0 +1,241 @@ +function createColumn(dataType, name, filter, visible, renderFunction) { + if (renderFunction === undefined) { + renderFunction = function (data, type, row, meta) { + return row[dataType]; + } + } + return { + "type": dataType, + "name": name, + "filter": filter, + "visible": visible, + "render": renderFunction + }; +} + +function getColumns(type) { + var result = []; + // don't confuse end user + // result.push(createColumn("id", "Id", null, false)); + result.push(createColumn("nd_number", "ND", "string_filter", true)); + result.push(createColumn("screening_number", "Screening", "string_filter", true)); + result.push(createColumn("first_name", "First name", "string_filter", true)); + result.push(createColumn("last_name", "Last name", "string_filter", true)); + result.push(createColumn("date_born", "Date of birth", null, false)); + result.push(createColumn("default_location", "Location", "location_filter", true)); + result.push(createColumn("dead", "Deceased", "yes_no_filter", true)); + result.push(createColumn("resigned", "Resigned", "yes_no_filter", true)); + result.push(createColumn("postponed", "Postponed", "yes_no_filter", true)); + result.push(createColumn("information_sent", "Info sent", "yes_no_filter", true)); + result.push(createColumn("type", "Type", "type_filter", true)); + result.push(createColumn("id", "Edit", null, true, function (data, type, row, meta) { + return '<a href="#" type="button" class="btn btn-block btn-default">Edit</a>'; + })); + for (var i = 1; i <= 8; i++) { + var renderFunction = (function () { + var x = i; + return function (data, type, row, meta) { + return create_visit_row(row.visits[x - 1]); + }; + })(); + + result.push(createColumn("visit_" + i, "Visit " + i, "visit_filter", true, renderFunction)); + + } + return result; + +} + +function createHeader(columnsDefinition) { + var header = document.createElement("thead"); + var headerRow = document.createElement("tr"); + header.appendChild(headerRow); + for (var i = 0; i < columnsDefinition.length; i++) { + var column = columnsDefinition[i]; + var element = document.createElement("th"); + element.innerHTML = column.name; + headerRow.appendChild(element); + } + return header; +} + +function createFilter(columnsDefinition) { + var footer = document.createElement("tfoot"); + footer.style.display = "table-header-group"; + var footerRow = document.createElement("tr"); + footer.appendChild(footerRow); + for (var i = 0; i < columnsDefinition.length; i++) { + var column = columnsDefinition[i]; + var element = document.createElement("th"); + if (column.filter !== null) { + element.innerHTML = "<div name='" + column.filter + "'>" + column.name + "</div>"; + } + footerRow.appendChild(element); + } + return footer; +} + +function create_visit_row(visit) { + var color = "white"; + var text = "---"; + if (visit !== undefined && visit !== null) { + if (visit.status === "DONE") { + color = "green"; + text = "OK"; + } else if (visit.status === "MISSED") { + color = "pink"; + text = "MISSED"; + } else if (visit.status === "UPCOMING") { + color = "#00ffff"; + text = "UPCOMING"; + } else if (visit.status === "EXCEEDED") { + color = "orange"; + text = "EXCEEDED"; + } else if (visit.status === "SHOULD_BE_IN_PROGRESS") { + color = "orange"; + text = "IN PROGRESS (NO APPOINTMENTS)"; + } else if (visit.status === "IN_PROGRESS") { + color = "lightgreen"; + text = "IN PROGRESS"; + } + text += "<br/>" + visit.datetime_start + " - " + visit.datetime_end; + } + return "<div style='background-color:" + color + "';width:100%;height:100%>" + text + "</div>"; +} + +function createVisibilityCheckboxes(checkboxesElement, columns) { + var row = null; + for (var i = 0; i < columns.length; i++) { + if (i % 10 === 0) { + row = document.createElement("div"); + row.style.display = "table-row"; + checkboxesElement.appendChild(row); + } + var column = columns[i]; + var element = document.createElement("div"); + element.style.display = "table-cell"; + var checked = ""; + if (column.visible) { + checked = "checked"; + } + element.innerHTML = "<input type='checkbox' " + checked + " data-column='" + i + "' name='" + column.type + "'/>" + column.name; + row.appendChild(element); + } + +} + +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 subjects_url = params.subjects_url; + var columnsDefinition = params.columns; + + tableElement.appendChild(createHeader(columnsDefinition)); + tableElement.appendChild(createFilter(columnsDefinition)); + tableElement.appendChild(document.createElement("tbody")); + createVisibilityCheckboxes(params.checkboxesElement, columnsDefinition); + + var table; + $(tableElement).find('tfoot div[name="string_filter"]').each(function () { + var title = $(this).text(); + $(this).html('<input type="text" style="width:80px" placeholder="' + title + '" />'); + }); + + $(tableElement).find('tfoot div[name="yes_no_filter"]').each(function () { + $(this).html('<select style="width:60px" ><option value selected="selected">---</option><option value="true">YES</option><option value="false">NO</option></select>'); + }); + + $(tableElement).find('tfoot div[name="visit_filter"]').each(function () { + $(this).html('<select style="width:60px" >' + + '<option value selected="selected">---</option>' + + '<option value="MISSED">MISSED</option>' + + '<option value="DONE">DONE</option>' + + '<option value="EXCEED">EXCEED</option>' + + '<option value="IN_PROGRESS">IN PROGRESS</option>' + + '<option value="SHOULD_BE_IN_PROGRESS">SHOULD BE IN PROGRESS</option>' + + '<option value="UPCOMING">UPCOMING</option>' + + '</select>'); + }); + + $(tableElement).find('tfoot div[name="location_filter"]').each(function () { + var obj = $(this); + obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); + var select = $('select', obj); + $.get(locations_url, function (data) { + $.each(data.locations, function (index, location) { + select.append('<option value="' + location.id + '">' + location.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>'); + var select = $('select', obj); + $.get(subject_types_url, function (data) { + $.each(data.types, function (index, type) { + select.append('<option value="' + type.id + '">' + type.name + '</option>'); + }); + }); + }); + + + $(function () { + var columns = []; + var columnDefs = []; + for (var i = 0; i < columnsDefinition.length; i++) { + var column = columnsDefinition[i]; + columns.push({"data": column.type}); + columnDefs.push({"targets": i, "render": column.render, visible: column.visible}); + } + + + table = $('#table').DataTable({ + pageLength: 25, + serverSide: true, + processing: true, + responsive: true, + ajax: subjects_url, + columns: columns, + columnDefs: columnDefs, + order: [[0, 'desc']] + }); + + $('#table tbody').on('click', 'a', function () { + var data = table.row($(this).parents('tr')).data(); + window.location.href = getSubjectEditUrl(data.id.toString()); + }); + + // Apply the search + table.columns().every(function () { + var that = this; + + $('input', this.footer()).on('keyup change', function () { + if (that.search() !== this.value) { + that.search(this.value).draw(); + } + }); + $('select', this.footer()).on('keyup change', function () { + if (that.search() !== this.value) { + that.search(this.value).draw(); + } + }); + }); + $('#table_filter').css("display", "null"); + }); + $('#visible-column-checkboxes input').on('click', function (e) { + var visible = $(this).is(":checked"); + + // Get the column API object + var column = table.column($(this).attr('data-column')); + console.log($(this).attr('data-column')); + // Toggle the visibility + column.visible(visible); + }); +} \ No newline at end of file diff --git a/smash/web/statistics.py b/smash/web/statistics.py index f52f54307ee4c65dea5ee6b2869eb888500c976e..63a710dca60acf2a2269e7a27d41ff9318ca2e92 100644 --- a/smash/web/statistics.py +++ b/smash/web/statistics.py @@ -74,7 +74,7 @@ class StatisticsManager(object): - number of appointments, - number of visits ended, - number of visits started - - number of appointements per type and per status + - number of appointments per type and per status :param month: the month number [1;12] :type month: int :param year: the year (4 digits) @@ -200,8 +200,8 @@ class StatisticsManager(object): subject_type_filter = Q(subject__type=subject_type) filters_month_year_visits_started.add(subject_type_filter, Q.AND) filters_month_year_visits_ended.add(subject_type_filter, Q.AND) - subject_type_filter_appointements = Q(visit__subject__type=subject_type) - filters_month_year_appointments.add(subject_type_filter_appointements, Q.AND) + subject_type_filter_appointments = Q(visit__subject__type=subject_type) + filters_month_year_appointments.add(subject_type_filter_appointments, Q.AND) return filters_month_year_appointments, filters_month_year_visits_ended, filters_month_year_visits_started @staticmethod diff --git a/smash/web/templates/subjects/add.html b/smash/web/templates/subjects/add.html index 0d22f5b169dc21f552d35d91a80075be3d141187..fc6931871cd604134379b9e519268ee3dc2a8472 100644 --- a/smash/web/templates/subjects/add.html +++ b/smash/web/templates/subjects/add.html @@ -94,7 +94,7 @@ var first_name = $("input[name='first_name']").val(); var last_name = $("input[name='last_name']").val(); if (last_name !== "") { - var url = "{% url 'web.api.subjects' 'GENERIC' %}" + var url = "{% url 'web.api.subjects' 'GENERIC' %}"; url += "?columns[0][data]=first_name&columns[0][search][value]=" + first_name; url += "&columns[1][data]=last_name&columns[1][search][value]=" + last_name; $.get(url, function (data) { @@ -106,7 +106,7 @@ }); if (subjects.length > 0) { $("#duplicate_warning").css("display", "block"); - var content = "There are possible duplicate(s) with the same name:<br/>" + var content = "There are possible duplicate(s) with the same name:<br/>"; $.each(subjects, function (index, subject) { content += subject.first_name + " " + subject.last_name + ", born: " + subjects.date_born + ", screening number: " + subject.screening_number + "<br/>" }); diff --git a/smash/web/templates/subjects/index.html b/smash/web/templates/subjects/index.html index 99bc0553185ef5d02cbac7af0239a4eb60c5b80e..5d1b71485ed61161555b8cf9ce0b087ae1cf0865 100644 --- a/smash/web/templates/subjects/index.html +++ b/smash/web/templates/subjects/index.html @@ -28,61 +28,11 @@ <div class="box-body"> <table id="table" class="table table-bordered table-striped table-responsive"> - <thead> - <tr> - <th>ND</th> - <th>Screening</th> - <th>First name - </th> - <th>Last name</th> - <th>Default location</th> - <th>Deceased</th> - <th>Resigned</th> - <th>Postponed</th> - <th>Info sent</th> - <th>Type</th> - <th>Edit</th> - </tr> - </thead> - <tfoot style="display: table-header-group;"> - <tr> - <th> - <div name="string_filter">ND</div> - </th> - <th> - <div name="string_filter">Screening number</div> - </th> - <th> - <div name="string_filter">Name</div> - </th> - <th> - <div name="string_filter">Surname</div> - </th> - <th> - <div name="location_filter">---</div> - </th> - <th> - <div name="yes_no_filter">---</div> - </th> - <th> - <div name="yes_no_filter">---</div> - </th> - <th> - <div name="yes_no_filter">---</div> - </th> - <th> - <div name="yes_no_filter">---</div> - </th> - <th> - <div name="type_filter">---</div> - </th> - </tr> - </tfoot> - - <tbody> - </tbody> </table> </div> + <h3>Visible columns</h3> + <div id="visible-column-checkboxes" style="display:table; width:100%"> + </div> {% endblock maincontent %} {% block scripts %} @@ -90,102 +40,29 @@ <script src="{% static 'AdminLTE/plugins/datatables/jquery.dataTables.min.js' %}"></script> <script src="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.min.js' %}"></script> + <script src="{% static 'js/subject.js' %}"></script> <script> - var table; - $('#table').find('tfoot div[name="string_filter"]').each(function () { - var title = $(this).text(); - $(this).html('<input type="text" style="width:80px" placeholder="' + title + '" />'); - }); - - $('#table').find('tfoot div[name="yes_no_filter"]').each(function () { - $(this).html('<select style="width:60px" ><option value selected="selected">---</option><option value="true">YES</option><option value="false">NO</option></select>'); - }); - - $('#table').find('tfoot div[name="location_filter"]').each(function () { - var obj = $(this); - obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); - var select = $('select', obj); - $.get("{% url 'web.api.locations' %}", function (data) { - $.each(data.locations, function (index, location) { - select.append('<option value="' + location.id + '">' + location.name + '</option>'); - }); - {% if worker.locations.all.count == 1 %} - select.val({{ worker.locations.first.id}}); - {% endif %} - }); - - }); - $('#table').find('tfoot div[name="type_filter"]').each(function () { - var obj = $(this); - obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); - var select = $('select', obj); - $.get("{% url 'web.api.subject_types' %}", function (data) { - $.each(data.types, function (index, type) { - select.append('<option value="' + type.id + '">' + type.name + '</option>'); - }); - }); - - }); - - $(function () { - table = $('#table').DataTable({ - pageLength: 25, - serverSide: true, - processing: true, - responsive: true, - ajax: "{% url 'web.api.subjects' list_type %}", - columns: [ - {"data": "nd_number"}, - {"data": "screening_number"}, - {"data": "first_name"}, - {"data": "last_name"}, - {"data": "default_location"}, - {"data": "dead"}, - {"data": "resigned"}, - {"data": "postponed"}, - {"data": "information_sent"}, - {"data": "type"}, - {"data": null} - ], - columnDefs: [{ - "targets": 10, - "data": "id", - "defaultContent": '<a href="#" type="button" class="btn btn-block btn-default">Edit</a>' - }], - order: [[0, 'desc']] - }); - {% if worker.locations.all.count == 1 %} - table.columns(4).search({{ worker.locations.first.id}}).draw(); - {% endif %} - - $('#table tbody').on('click', 'a', function () { - var data = table.row($(this).parents('tr')).data(); - var url = "{% url 'web.views.subject_edit' 12345 %}".replace(/12345/, data.id.toString()); - window.location.href = url; - }); - - // Apply the search - table.columns().every(function () { - var that = this; - - $('input', this.footer()).on('keyup change', function () { - if (that.search() !== this.value) { - that - .search(this.value) - .draw(); - } - }); - $('select', this.footer()).on('keyup change', function () { - if (that.search() !== this.value) { - that - .search(this.value) - .draw(); - } - }); - }); - $('#table_filter').css("display", "none"); - }); + function getSubjectEditUrl(id) { + return "{% url 'web.views.subject_edit' 12345 %}".replace(/12345/, id); + } + + var worker_locations = []; + {% for location in worker.locations.all %} + worker_locations.push({id: location.id, name: location.name}); + {% endfor %} + + createSubjectsTable({ + getSubjectEditUrl: getSubjectEditUrl, + worker_locations: worker_locations, + subject_types_url: "{% url 'web.api.subject_types' %}", + locations_url: "{% url 'web.api.locations' %}", + subjects_url: "{% url 'web.api.subjects' list_type %}", + tableElement: document.getElementById("table"), + columns: getColumns("{{ list_type }}"), + checkboxesElement: document.getElementById("visible-column-checkboxes") + }) + ; </script> {% endblock scripts %} diff --git a/smash/web/templates/subjects/visitdetails.html b/smash/web/templates/subjects/visit_details.html similarity index 100% rename from smash/web/templates/subjects/visitdetails.html rename to smash/web/templates/subjects/visit_details.html diff --git a/smash/web/templates/visits/details.html b/smash/web/templates/visits/details.html index 6953e3a90265956863eb65986af8e6ddac669c95..f6c632b961d953120b79982937a94a509a5cde74 100644 --- a/smash/web/templates/visits/details.html +++ b/smash/web/templates/visits/details.html @@ -11,10 +11,10 @@ {% endblock styles %} {% block ui_active_tab %}'visits'{% endblock ui_active_tab %} -{% block page_header %}Details of the visit ({{ visit.follow_up_title }}) {% endblock page_header %} +{% block page_header %}Details of the visit ({{ visit.visit_number }}) {% endblock page_header %} {% block page_description %}{% endblock page_description %} -{% block title %}{{ block.super }} - Details of visit ({{ visit.follow_up_title }}) {% endblock %} +{% block title %}{{ block.super }} - Details of visit ({{ visit.visit_number }}) {% endblock %} {% block breadcrumb %} {% include "subjects/breadcrumb.html" %} diff --git a/smash/web/tests/test_api_appointment.py b/smash/web/tests/api_views/test_appointment.py similarity index 100% rename from smash/web/tests/test_api_appointment.py rename to smash/web/tests/api_views/test_appointment.py diff --git a/smash/web/tests/test_api_appointment_type.py b/smash/web/tests/api_views/test_appointment_type.py similarity index 100% rename from smash/web/tests/test_api_appointment_type.py rename to smash/web/tests/api_views/test_appointment_type.py diff --git a/smash/web/tests/test_api_configuration_item.py b/smash/web/tests/api_views/test_configuration_item.py similarity index 98% rename from smash/web/tests/test_api_configuration_item.py rename to smash/web/tests/api_views/test_configuration_item.py index 102e3a5b6d2451e3d9238400affa0034171526a7..d4becf7cf6bf7b12b93de0a747e21e903c0e4eed 100644 --- a/smash/web/tests/test_api_configuration_item.py +++ b/smash/web/tests/api_views/test_configuration_item.py @@ -4,9 +4,9 @@ from django.urls import reverse from web.models import ConfigurationItem -from web.tests.functions import create_configuration_item -from . import LoggedInTestCase from web.models.constants import CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE +from web.tests import LoggedInTestCase +from web.tests.functions import create_configuration_item class TestConfigurationItemApi(LoggedInTestCase): diff --git a/smash/web/tests/test_api_location.py b/smash/web/tests/api_views/test_location.py similarity index 100% rename from smash/web/tests/test_api_location.py rename to smash/web/tests/api_views/test_location.py diff --git a/smash/web/tests/test_api_subject.py b/smash/web/tests/api_views/test_subject.py similarity index 60% rename from smash/web/tests/test_api_subject.py rename to smash/web/tests/api_views/test_subject.py index 8fbce03c8fd979bb726f96d305fd8e4fd4f57f9e..b4b074f3f031cdcc26d84a5c6890ad084f0aff6f 100644 --- a/smash/web/tests/test_api_subject.py +++ b/smash/web/tests/api_views/test_subject.py @@ -1,15 +1,17 @@ # coding=utf-8 import json +import datetime from django.contrib.auth.models import User from django.test import Client from django.test import TestCase from django.urls import reverse +from web.views.notifications import get_today_midnight_date from web.api_views.subject import get_subjects_order, get_subjects_filtered, serialize_subject, SUBJECT_LIST_GENERIC, \ SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT -from web.models import Subject -from web.tests.functions import create_subject, create_worker, create_get_suffix +from web.models import Subject, Appointment, Visit +from web.tests.functions import create_subject, create_worker, create_get_suffix, create_visit, create_appointment class TestApi(TestCase): @@ -142,6 +144,24 @@ class TestApi(TestCase): self.check_subject_ordered("nd_number", [subject, subject2]) + def test_subjects_sort_id(self): + subject = self.subject + + subject2 = create_subject(2) + + self.check_subject_ordered("id", [subject, subject2]) + + def test_subjects_sort_date_born(self): + subject = self.subject + subject.date_born = get_today_midnight_date() + subject.save() + + subject2 = create_subject(2) + subject2.date_born = get_today_midnight_date() + datetime.timedelta(days=1) + subject2.save() + + self.check_subject_ordered("date_born", [subject, subject2]) + def test_subjects_sort_default_location(self): subject = self.subject @@ -289,3 +309,132 @@ class TestApi(TestCase): subject_json = serialize_subject(subject) self.assertEqual("YES", subject_json["dead"]) + + def test_subjects_filter_visit_1_DONE(self): + subject = self.subject + subject.dead = True + subject.save() + + visit = create_visit(subject) + appointment = create_appointment(visit) + appointment.status = Appointment.APPOINTMENT_STATUS_FINISHED + appointment.save() + visit.mark_as_finished() + + self.check_subject_filtered([["visit_1", "DONE"]], [subject]) + self.check_subject_filtered([["visit_2", "DONE"]], []) + + self.check_subject_filtered([["visit_1", "MISSED"]], []) + self.check_subject_filtered([["visit_1", "EXCEED"]], []) + self.check_subject_filtered([["visit_1", "IN_PROGRESS"]], []) + self.check_subject_filtered([["visit_1", "SHOULD_BE_IN_PROGRESS"]], []) + self.check_subject_filtered([["visit_1", "UPCOMING"]], []) + + def test_subjects_filter_visit_1_MISSED(self): + subject = self.subject + subject.dead = True + subject.save() + + visit = create_visit(subject) + appointment = create_appointment(visit) + appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED + appointment.save() + visit.mark_as_finished() + + self.check_subject_filtered([["visit_1", "MISSED"]], [subject]) + self.check_subject_filtered([["visit_2", "MISSED"]], []) + + self.check_subject_filtered([["visit_1", "DONE"]], []) + self.check_subject_filtered([["visit_1", "EXCEED"]], []) + self.check_subject_filtered([["visit_1", "IN_PROGRESS"]], []) + self.check_subject_filtered([["visit_1", "SHOULD_BE_IN_PROGRESS"]], []) + self.check_subject_filtered([["visit_1", "UPCOMING"]], []) + + def test_subjects_filter_visit_1_EXCEED(self): + subject = self.subject + subject.dead = True + subject.save() + + visit = create_visit(subject) + visit.datetime_begin = get_today_midnight_date() + datetime.timedelta(days=-365 * 2) + visit.datetime_end = get_today_midnight_date() + datetime.timedelta(days=-365 * 1) + visit.save() + + self.check_subject_filtered([["visit_1", "EXCEED"]], [subject]) + self.check_subject_filtered([["visit_2", "EXCEED"]], []) + + self.check_subject_filtered([["visit_1", "DONE"]], []) + self.check_subject_filtered([["visit_1", "MISSED"]], []) + self.check_subject_filtered([["visit_1", "IN_PROGRESS"]], []) + self.check_subject_filtered([["visit_1", "SHOULD_BE_IN_PROGRESS"]], []) + self.check_subject_filtered([["visit_1", "UPCOMING"]], []) + + def test_subjects_filter_visit_1_IN_PROGRESS(self): + subject = self.subject + subject.dead = True + subject.save() + + visit = create_visit(subject) + appointment = create_appointment(visit) + appointment.status = Appointment.APPOINTMENT_STATUS_SCHEDULED + appointment.save() + + self.check_subject_filtered([["visit_1", "IN_PROGRESS"]], [subject]) + self.check_subject_filtered([["visit_2", "IN_PROGRESS"]], []) + + self.check_subject_filtered([["visit_1", "DONE"]], []) + self.check_subject_filtered([["visit_1", "MISSED"]], []) + self.check_subject_filtered([["visit_1", "EXCEED"]], []) + self.check_subject_filtered([["visit_1", "SHOULD_BE_IN_PROGRESS"]], []) + self.check_subject_filtered([["visit_1", "UPCOMING"]], []) + + def test_subjects_filter_visit_1_SHOULD_BE_IN_PROGRESS(self): + subject = self.subject + subject.dead = True + subject.save() + + visit = create_visit(subject) + + self.check_subject_filtered([["visit_1", "SHOULD_BE_IN_PROGRESS"]], [subject]) + self.check_subject_filtered([["visit_2", "SHOULD_BE_IN_PROGRESS"]], []) + + self.check_subject_filtered([["visit_1", "DONE"]], []) + self.check_subject_filtered([["visit_1", "MISSED"]], []) + self.check_subject_filtered([["visit_1", "EXCEED"]], []) + self.check_subject_filtered([["visit_1", "IN_PROGRESS"]], []) + self.check_subject_filtered([["visit_1", "UPCOMING"]], []) + + def test_subjects_filter_visit_1_UPCOMING(self): + subject = self.subject + subject.dead = True + subject.save() + + visit = create_visit(subject) + visit.datetime_begin = get_today_midnight_date() + datetime.timedelta(days=2) + visit.datetime_end = get_today_midnight_date() + datetime.timedelta(days=20) + visit.save() + + self.check_subject_filtered([["visit_1", "UPCOMING"]], [subject]) + self.check_subject_filtered([["visit_2", "UPCOMING"]], []) + + self.check_subject_filtered([["visit_1", "DONE"]], []) + self.check_subject_filtered([["visit_1", "MISSED"]], []) + self.check_subject_filtered([["visit_1", "EXCEED"]], []) + self.check_subject_filtered([["visit_1", "IN_PROGRESS"]], []) + self.check_subject_filtered([["visit_1", "SHOULD_BE_IN_PROGRESS"]], []) + + def test_subjects_filter_visit_1_visit_2_combined(self): + subject = self.subject + subject.save() + + visit = create_visit(subject) + appointment = create_appointment(visit) + appointment.status = Appointment.APPOINTMENT_STATUS_FINISHED + appointment.save() + visit.mark_as_finished() + + self.check_subject_filtered([["visit_1", "DONE"]], [subject]) + self.check_subject_filtered([["visit_2", "UPCOMING"]], [subject]) + + self.check_subject_filtered([["visit_1", "DONE"], ["visit_2", "UPCOMING"]], [subject]) + self.check_subject_filtered([["visit_1", "UPCOMING"], ["visit_2", "DONE"]], []) diff --git a/smash/web/tests/forms/__init__.py b/smash/web/tests/forms/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/smash/web/tests/test_AppointmentAddForm.py b/smash/web/tests/forms/test_AppointmentAddForm.py similarity index 89% rename from smash/web/tests/test_AppointmentAddForm.py rename to smash/web/tests/forms/test_AppointmentAddForm.py index 7861e12f9bb24e72c016e4e3a8101e9492cfdbb6..a63675e57ba5affc5d5e7b3c70a7dfda3d5074e7 100644 --- a/smash/web/tests/test_AppointmentAddForm.py +++ b/smash/web/tests/forms/test_AppointmentAddForm.py @@ -1,9 +1,9 @@ from django.test import TestCase -from functions import create_user, create_visit, create_location -from functions import get_test_location from web.forms import AppointmentAddForm from web.models import Worker +from web.tests.functions import create_user, create_visit, create_location +from web.tests.functions import get_test_location class AppointmentAddFormTests(TestCase): diff --git a/smash/web/tests/test_AppointmentEditForm.py b/smash/web/tests/forms/test_AppointmentEditForm.py similarity index 94% rename from smash/web/tests/test_AppointmentEditForm.py rename to smash/web/tests/forms/test_AppointmentEditForm.py index eba5c6d0970e19908dd90f34a5e224d3153f34e8..0a60144efbc9916be9c6eee7f3719a2f1fa937f7 100644 --- a/smash/web/tests/test_AppointmentEditForm.py +++ b/smash/web/tests/forms/test_AppointmentEditForm.py @@ -1,9 +1,9 @@ from django.test import TestCase -from functions import get_test_location, create_user, create_visit, create_location from web.forms import AppointmentAddForm from web.forms import AppointmentEditForm from web.models import Appointment, Worker +from web.tests.functions import get_test_location, create_user, create_visit, create_location class AppointmentEditFormTests(TestCase): diff --git a/smash/web/tests/test_AvailabilityAddForm.py b/smash/web/tests/forms/test_AvailabilityAddForm.py similarity index 93% rename from smash/web/tests/test_AvailabilityAddForm.py rename to smash/web/tests/forms/test_AvailabilityAddForm.py index 85987100244f414824869bec471a1e3d4aca0143..0d8f1beec26c159dc2d6807d359222c77b4a0618 100644 --- a/smash/web/tests/test_AvailabilityAddForm.py +++ b/smash/web/tests/forms/test_AvailabilityAddForm.py @@ -1,10 +1,10 @@ from django.test import TestCase -from functions import create_user, create_visit -from functions import get_test_location -from web.models.constants import MONDAY_AS_DAY_OF_WEEK from web.forms import AvailabilityAddForm from web.models import Worker +from web.models.constants import MONDAY_AS_DAY_OF_WEEK +from web.tests.functions import create_user, create_visit +from web.tests.functions import get_test_location class AvailabilityAddFormTests(TestCase): diff --git a/smash/web/tests/test_AvailabilityEditForm.py b/smash/web/tests/forms/test_AvailabilityEditForm.py similarity index 89% rename from smash/web/tests/test_AvailabilityEditForm.py rename to smash/web/tests/forms/test_AvailabilityEditForm.py index 9d2d3bd1106c50f5626e3ebaeca788ba94ce6232..0779eabecdfaf3c93c4206a7da65fb0bec05dd3d 100644 --- a/smash/web/tests/test_AvailabilityEditForm.py +++ b/smash/web/tests/forms/test_AvailabilityEditForm.py @@ -1,10 +1,10 @@ from django.test import TestCase -from functions import create_user, create_visit -from functions import get_test_location -from web.models.constants import MONDAY_AS_DAY_OF_WEEK from web.forms import AvailabilityEditForm, AvailabilityAddForm from web.models import Worker +from web.models.constants import MONDAY_AS_DAY_OF_WEEK +from web.tests.functions import create_user, create_visit +from web.tests.functions import get_test_location class AvailabilityEditFormTests(TestCase): diff --git a/smash/web/tests/test_SubjectAddForm.py b/smash/web/tests/forms/test_SubjectAddForm.py similarity index 98% rename from smash/web/tests/test_SubjectAddForm.py rename to smash/web/tests/forms/test_SubjectAddForm.py index 77e9c6d6044ed42cc320175d7ec6e9ba98db6938..c22fee47c48f9c39f4b4a4567b5f93cf0fe73673 100644 --- a/smash/web/tests/test_SubjectAddForm.py +++ b/smash/web/tests/forms/test_SubjectAddForm.py @@ -2,9 +2,9 @@ from django.contrib.auth.models import User from django.test import Client from django.test import TestCase -from functions import get_test_location, create_subject, create_worker from web.forms import SubjectAddForm, get_new_screening_number from web.models.constants import SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL +from web.tests.functions import get_test_location, create_subject, create_worker class SubjectAddFormTests(TestCase): diff --git a/smash/web/tests/test_SubjectEditForm.py b/smash/web/tests/forms/test_SubjectEditForm.py similarity index 97% rename from smash/web/tests/test_SubjectEditForm.py rename to smash/web/tests/forms/test_SubjectEditForm.py index bf9fb7314eb8a5109b03ecf8efc90d249fc40663..25f2047ac34f8da86c28b130ace30aa68b3832e9 100644 --- a/smash/web/tests/test_SubjectEditForm.py +++ b/smash/web/tests/forms/test_SubjectEditForm.py @@ -1,12 +1,12 @@ from django.contrib.auth.models import User -from django.test import TestCase from django.test import Client +from django.test import TestCase -from functions import get_test_location, create_worker from web.forms import SubjectAddForm from web.forms import SubjectEditForm from web.models import Subject from web.models.constants import SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL +from web.tests.functions import get_test_location, create_worker class SubjectEditFormTests(TestCase): diff --git a/smash/web/tests/test_VisitAddForm.py b/smash/web/tests/forms/test_VisitAddForm.py similarity index 90% rename from smash/web/tests/test_VisitAddForm.py rename to smash/web/tests/forms/test_VisitAddForm.py index 2a2dfe0c6b6d85ca86fb47cd6946d76df0d32189..b6a0d817ad2b577e83bcbf9a03fbfd71b3baf6f7 100644 --- a/smash/web/tests/test_VisitAddForm.py +++ b/smash/web/tests/forms/test_VisitAddForm.py @@ -2,12 +2,12 @@ from __future__ import print_function from django.test import TestCase -from functions import create_subject -from functions import get_test_location from web.forms import VisitAddForm +from web.tests.functions import create_subject +from web.tests.functions import get_test_location -class SubjectAddFormTests(TestCase): +class VisitAddFormTests(TestCase): def setUp(self): get_test_location() self.subject = create_subject() diff --git a/smash/web/tests/test_model_appointment.py b/smash/web/tests/models/test_appointment.py similarity index 87% rename from smash/web/tests/test_model_appointment.py rename to smash/web/tests/models/test_appointment.py index 36525f581b4dba2ebd12f93c49db85e1dbf4397d..a781e69c9ec36a99bd92e3cbf5616d16ee8d2b4d 100644 --- a/smash/web/tests/test_model_appointment.py +++ b/smash/web/tests/models/test_appointment.py @@ -1,6 +1,6 @@ from django.test import TestCase -from functions import create_appointment +from web.tests.functions import create_appointment class AppointmentModelTests(TestCase): diff --git a/smash/web/tests/test_model_configuration_item.py b/smash/web/tests/models/test_configuration_item.py similarity index 100% rename from smash/web/tests/test_model_configuration_item.py rename to smash/web/tests/models/test_configuration_item.py diff --git a/smash/web/tests/test_model_mail_template.py b/smash/web/tests/models/test_mail_template.py similarity index 98% rename from smash/web/tests/test_model_mail_template.py rename to smash/web/tests/models/test_mail_template.py index 0460314373a98c926c4dcabf9b725cf9949efd81..df6611fa60ba7e30d85dd82241d422b2508e3b0f 100644 --- a/smash/web/tests/test_model_mail_template.py +++ b/smash/web/tests/models/test_mail_template.py @@ -3,12 +3,12 @@ import StringIO from django.test import TestCase from docx import Document -from functions import create_language, get_resource_path, create_appointment, create_user, create_subject, \ - create_visit from web.models import MailTemplate from web.models.constants import MAIL_TEMPLATE_CONTEXT_APPOINTMENT, MAIL_TEMPLATE_CONTEXT_VISIT, \ MAIL_TEMPLATE_CONTEXT_SUBJECT from web.models.mail_template import DATE_FORMAT_SHORT +from web.tests.functions import create_language, get_resource_path, create_appointment, create_user, create_subject, \ + create_visit class MailTemplateModelTests(TestCase): diff --git a/smash/web/tests/test_model_visit.py b/smash/web/tests/models/test_visit.py similarity index 83% rename from smash/web/tests/test_model_visit.py rename to smash/web/tests/models/test_visit.py index 2a54bcb98df71668457065eefac420c2f0095071..835b7a66569ddbed84abf6212822e7dcf6ae8bee 100644 --- a/smash/web/tests/test_model_visit.py +++ b/smash/web/tests/models/test_visit.py @@ -1,7 +1,6 @@ from django.test import TestCase -from functions import create_subject -from functions import create_visit +from web.tests.functions import create_subject, create_visit from web.models import Visit @@ -36,3 +35,7 @@ class VisitModelTests(TestCase): visit_count = Visit.objects.filter(subject=subject).count() self.assertEquals(1, visit_count) + + def test_visit_to_string(self): + visit = create_visit(create_subject()) + self.assertIsNotNone(str(visit)) diff --git a/smash/web/tests/models/test_worker.py b/smash/web/tests/models/test_worker.py index be91b963bf4c9136f2432abf49f52a8489c1667b..9b193326f06a1cbbe7992c61ead7f00d6e8552e5 100644 --- a/smash/web/tests/models/test_worker.py +++ b/smash/web/tests/models/test_worker.py @@ -2,6 +2,7 @@ from django.contrib.auth.models import User from django.test import Client from django.test import TestCase +from web.models import Worker from web.tests.functions import create_worker @@ -29,3 +30,19 @@ class WorkerModelTests(TestCase): def test_is_active_for_invalid(self): worker = create_worker() self.assertFalse(worker.is_active()) + + def test_get_by_user_for_None(self): + worker = Worker.get_by_user(None) + self.assertIsNone(worker) + + def test_get_by_user_for_Worker(self): + worker = create_worker() + worker_2 = Worker.get_by_user(worker) + self.assertEqual(worker, worker_2) + + def test_get_by_user_for_invalid(self): + try: + Worker.get_by_user("invalid object") + self.fail("Exception expected") + except TypeError: + pass diff --git a/smash/web/tests/test_view_configuration.py b/smash/web/tests/view/test_configuration.py similarity index 87% rename from smash/web/tests/test_view_configuration.py rename to smash/web/tests/view/test_configuration.py index 4b9bbccdc61c23d13edd9b6e2906314a5c995573..23a147796de28f012dcf257e1dac12313160bcb3 100644 --- a/smash/web/tests/test_view_configuration.py +++ b/smash/web/tests/view/test_configuration.py @@ -2,7 +2,7 @@ import datetime from django.urls import reverse -from . import LoggedInTestCase +from web.tests import LoggedInTestCase class ConfigurationViewTests(LoggedInTestCase): diff --git a/smash/web/tests/test_view_contact_attempt.py b/smash/web/tests/view/test_contact_attempt.py similarity index 96% rename from smash/web/tests/test_view_contact_attempt.py rename to smash/web/tests/view/test_contact_attempt.py index 6a36fdaaaa2310d8e929e7e0440b05bd414a4197..9bb40d7191ef127995ec1a01f2a9618c91547971 100644 --- a/smash/web/tests/test_view_contact_attempt.py +++ b/smash/web/tests/view/test_contact_attempt.py @@ -4,10 +4,10 @@ from django.urls import reverse from django.utils import timezone from web.forms import ContactAttemptEditForm -from functions import create_subject, create_contact_attempt, format_form_field from web.models import ContactAttempt from web.models.constants import CONTACT_TYPES_EMAIL -from . import LoggedInWithWorkerTestCase +from web.tests import LoggedInWithWorkerTestCase +from web.tests.functions import create_subject, create_contact_attempt, format_form_field class ContactAttemptViewTests(LoggedInWithWorkerTestCase): diff --git a/smash/web/tests/test_view_export.py b/smash/web/tests/view/test_export.py similarity index 90% rename from smash/web/tests/test_view_export.py rename to smash/web/tests/view/test_export.py index f15c2cf439a9012999ff91ef88a9fc1e928a0679..f2c7850f269e95d3ddc8f051d676f2971e9d5761 100644 --- a/smash/web/tests/test_view_export.py +++ b/smash/web/tests/view/test_export.py @@ -1,8 +1,8 @@ # coding=utf-8 from django.urls import reverse -from functions import create_subject, create_appointment -from . import LoggedInTestCase +from web.tests import LoggedInTestCase +from web.tests.functions import create_subject, create_appointment class TestExportView(LoggedInTestCase): diff --git a/smash/web/tests/test_view_functions.py b/smash/web/tests/view/test_functions.py similarity index 88% rename from smash/web/tests/test_view_functions.py rename to smash/web/tests/view/test_functions.py index 7df3e0a0a13ae7b9fcb0d1dfd87c09cac5060f89..d03ab059ae136521a764883b443018f105fdc894 100644 --- a/smash/web/tests/test_view_functions.py +++ b/smash/web/tests/view/test_functions.py @@ -1,6 +1,6 @@ from django.test import TestCase -from functions import create_location, create_user, create_worker, get_test_location +from web.tests.functions import create_location, create_user, create_worker, get_test_location from web.views.notifications import get_filter_locations diff --git a/smash/web/tests/test_view_kit_request.py b/smash/web/tests/view/test_kit_request.py similarity index 97% rename from smash/web/tests/test_view_kit_request.py rename to smash/web/tests/view/test_kit_request.py index cc82b543178595e8433f7f5c26ac2aadb3090f39..f0d199b6fff21bc039695c1caa1e30e53330062a 100644 --- a/smash/web/tests/test_view_kit_request.py +++ b/smash/web/tests/view/test_kit_request.py @@ -3,11 +3,11 @@ import datetime from django.core import mail from django.urls import reverse -from functions import create_appointment_type, create_appointment, create_visit from web.models import Item, Appointment, AppointmentTypeLink +from web.tests import LoggedInTestCase +from web.tests.functions import create_appointment_type, create_appointment, create_visit from web.views.kit import get_kit_requests from web.views.notifications import get_today_midnight_date -from . import LoggedInTestCase class ViewFunctionsTests(LoggedInTestCase): diff --git a/smash/web/tests/test_view_login.py b/smash/web/tests/view/test_login.py similarity index 97% rename from smash/web/tests/test_view_login.py rename to smash/web/tests/view/test_login.py index f3fa9b05d3152c6fc7a6542e03bbaf4a8937c0d1..f66d376e1efa0d5dc7c11f6c0617f095a4b57af5 100644 --- a/smash/web/tests/test_view_login.py +++ b/smash/web/tests/view/test_login.py @@ -5,8 +5,8 @@ from django.test import Client from django.test import TestCase from django.urls import reverse -from functions import create_user from web.models import Worker +from web.tests.functions import create_user class TestLoginView(TestCase): diff --git a/smash/web/tests/test_view_notifications.py b/smash/web/tests/view/test_notifications.py similarity index 98% rename from smash/web/tests/test_view_notifications.py rename to smash/web/tests/view/test_notifications.py index 9c79dd8cbdac2dbdbfbd6e8608d659e65ddbfcd8..c2586124a76f713af2746ad99f1cf5063123c773 100644 --- a/smash/web/tests/test_view_notifications.py +++ b/smash/web/tests/view/test_notifications.py @@ -2,10 +2,11 @@ import datetime from django.contrib.auth.models import AnonymousUser -from functions import create_appointment, create_location, create_worker, create_appointment_type -from functions import create_subject -from functions import create_visit from web.models import Appointment, Location, AppointmentTypeLink +from web.tests import LoggedInTestCase +from web.tests.functions import create_appointment, create_location, create_worker, create_appointment_type +from web.tests.functions import create_subject +from web.tests.functions import create_visit from web.views.notifications import \ get_approaching_visits_for_mail_contact, \ get_approaching_visits_for_mail_contact_count, \ @@ -22,7 +23,6 @@ from web.views.notifications import \ get_unfinished_appointments, \ get_unfinished_appointments_count, \ get_unfinished_visits -from . import LoggedInTestCase class NotificationViewTests(LoggedInTestCase): diff --git a/smash/web/tests/test_view_statistics.py b/smash/web/tests/view/test_statistics.py similarity index 94% rename from smash/web/tests/test_view_statistics.py rename to smash/web/tests/view/test_statistics.py index eac349b67a9893e701d39d75606647b797e60592..795a1ed830cb1d8310daca986c651ff19e8a666b 100644 --- a/smash/web/tests/test_view_statistics.py +++ b/smash/web/tests/view/test_statistics.py @@ -3,7 +3,7 @@ from datetime import datetime from django.urls import reverse -from . import LoggedInTestCase +from web.tests import LoggedInTestCase __author__ = 'Valentin Grouès' diff --git a/smash/web/tests/test_view_subjects.py b/smash/web/tests/view/test_subjects.py similarity index 97% rename from smash/web/tests/test_view_subjects.py rename to smash/web/tests/view/test_subjects.py index ef13ec1fda116890e7c1f8af20f51c17291836d8..8c721780bb9852e2ca929198ca69515e12d90b1b 100644 --- a/smash/web/tests/test_view_subjects.py +++ b/smash/web/tests/view/test_subjects.py @@ -5,7 +5,7 @@ from django.test import Client from django.test import TestCase from django.urls import reverse -from functions import create_subject, create_visit, create_appointment, create_worker, get_test_location +from web.tests.functions import create_subject, create_visit, create_appointment, create_worker, get_test_location from web.forms import SubjectAddForm, SubjectEditForm from web.models import Subject from web.models.constants import SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL, SUBJECT_TYPE_CHOICES_PATIENT @@ -44,7 +44,7 @@ class SubjectsViewTests(TestCase): self.assertEqual(response.status_code, 200) self.assertFalse("Add visit" in response.content) - def test_render_subject_visit_details(self): + def test_render_subject_visit_details_without_visit(self): subject = create_subject() response = self.client.get(reverse('web.views.subject_visit_details', kwargs={'id': subject.id})) diff --git a/smash/web/tests/test_view_visit.py b/smash/web/tests/view/test_visit.py similarity index 96% rename from smash/web/tests/test_view_visit.py rename to smash/web/tests/view/test_visit.py index b8c0a28696966ba193af9e1bba78da8d8a5e56ba..217475e6822e106079f7fc52550b706bcddd3a55 100644 --- a/smash/web/tests/test_view_visit.py +++ b/smash/web/tests/view/test_visit.py @@ -2,11 +2,11 @@ import datetime from django.urls import reverse -from functions import create_subject, create_visit, create_appointment, create_appointment_type from web.forms import VisitDetailForm, VisitAddForm from web.models import Visit +from web.tests import LoggedInTestCase +from web.tests.functions import create_subject, create_visit, create_appointment, create_appointment_type from web.views.notifications import get_today_midnight_date -from . import LoggedInTestCase class VisitViewTests(LoggedInTestCase): diff --git a/smash/web/urls.py b/smash/web/urls.py index bc687f2603a8d20c5e863d6dac8c4e9584a812b2..984f6ca1e978d64565f016b2baa8e6df94d3d0ec 100644 --- a/smash/web/urls.py +++ b/smash/web/urls.py @@ -70,7 +70,7 @@ urlpatterns = [ url(r'^subjects$', views.subject.subjects, name='web.views.subjects'), url(r'^subjects/no_visit$', views.subject.subject_no_visits, name='web.views.subject_no_visits'), - url(r'^subjects/equire_contact$', views.subject.subject_require_contact, name='web.views.subject_require_contact'), + url(r'^subjects/require_contact$', views.subject.subject_require_contact, name='web.views.subject_require_contact'), url(r'^subjects/add$', views.subject.subject_add, name='web.views.subject_add'), url(r'^subjects/subject_visit_details/(?P<id>\d+)$', views.subject.subject_visit_details, name='web.views.subject_visit_details'), @@ -120,7 +120,7 @@ urlpatterns = [ #################### url(r'^equipment_and_rooms$', views.equipment.equipment_and_rooms, name='web.views.equipment_and_rooms'), - url(r'^equipment_and_rooms/eqdef$', views.equipment.equipment_def, name='web.views.equipment_def'), + url(r'^equipment_and_rooms/equipment_def$', views.equipment.equipment_def, name='web.views.equipment_def'), url(r'^equipment_and_rooms/kit_requests$', views.kit.kit_requests, name='web.views.kit_requests'), url(r'^equipment_and_rooms/kit_requests/(?P<start_date>[\w-]+)/$', views.kit.kit_requests_send_mail, name='web.views.kit_requests_send_mail'), diff --git a/smash/web/views/export.py b/smash/web/views/export.py index 359386c2552b4bd0c7e8afd46bb2353beba5917e..b1962c7e66b797f89b141da41266a584e470cdd4 100644 --- a/smash/web/views/export.py +++ b/smash/web/views/export.py @@ -91,7 +91,7 @@ def get_appointments_as_array(): for appointment in appointments: if appointment.visit is not None: row = [appointment.visit.subject.nd_number, appointment.visit.subject.last_name, - appointment.visit.subject.first_name, appointment.visit.follow_up_title()] + appointment.visit.subject.first_name, appointment.visit.visit_number] else: row = ["---", "---", "---", "---"] for field in appointments_fields: diff --git a/smash/web/views/subject.py b/smash/web/views/subject.py index da6675ea4c4a7a796d8394edf639290c3d0f93a7..9031c0d52635fad4c8c8dc027cd2883d093886e4 100644 --- a/smash/web/views/subject.py +++ b/smash/web/views/subject.py @@ -83,18 +83,18 @@ def subject_edit(request, id): def subject_visit_details(request, id): subject_to_be_viewed = get_object_or_404(Subject, id=id) - visits = subject_to_be_viewed.visit_set.all() + visits = subject_to_be_viewed.visit_set.order_by("-visit_number").all() visits_data = [] allow_add_visit = True for visit in visits: appointments = visit.appointment_set.all() finished = visit.is_finished visit_id = visit.id - visit_title = visit.follow_up_title() + visit_title = "Visit " + str(visit.visit_number) visit_form = VisitDetailForm(instance=visit) visits_data.append((visit_form, appointments, finished, visit_id, visit_title)) if not visit.is_finished: allow_add_visit = False - return wrap_response(request, 'subjects/visitdetails.html', + return wrap_response(request, 'subjects/visit_details.html', {'display': visits_data, "id": id, "allow_add_visit": allow_add_visit})