From 05e189b6bb74dcb14b9759b9a4094baaaa6e7007 Mon Sep 17 00:00:00 2001 From: Piotr Gawron <piotr.gawron@uni.lu> Date: Tue, 5 Dec 2017 16:33:19 +0100 Subject: [PATCH] list of visits is visualized dynamically with pagination and sorting --- smash/web/api_views/visit.py | 32 ++- smash/web/models/study_visit_list.py | 10 + smash/web/static/js/smash.js | 264 +++++++++++++++++++++++- smash/web/static/js/subject.js | 254 +---------------------- smash/web/static/js/visit.js | 5 + smash/web/templates/visits/index.html | 87 ++------ smash/web/tests/api_views/test_visit.py | 31 ++- smash/web/views/visit.py | 57 ++--- 8 files changed, 377 insertions(+), 363 deletions(-) diff --git a/smash/web/api_views/visit.py b/smash/web/api_views/visit.py index d8ce0613..e426440a 100644 --- a/smash/web/api_views/visit.py +++ b/smash/web/api_views/visit.py @@ -6,7 +6,11 @@ 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 +from web.models.study_visit_list import VISIT_LIST_GENERIC, VISIT_LIST_EXCEEDED_TIME, VISIT_LIST_UNFINISHED, \ + VISIT_LIST_MISSING_APPOINTMENTS, VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS, \ + VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT +from web.views.notifications import get_unfinished_visits, get_active_visits_with_missing_appointments, \ + get_approaching_visits_without_appointments, get_approaching_visits_for_mail_contact, get_exceeded_visits logger = logging.getLogger(__name__) @@ -62,6 +66,16 @@ def get_visit_columns(request, visit_list_type): def get_visits(request, visit_type): if visit_type == VISIT_LIST_GENERIC: return Visit.objects.all() + elif visit_type == VISIT_LIST_EXCEEDED_TIME: + return get_exceeded_visits(request.user).all() + elif visit_type == VISIT_LIST_UNFINISHED: + return get_unfinished_visits(request.user).all() + elif visit_type == VISIT_LIST_MISSING_APPOINTMENTS: + return get_active_visits_with_missing_appointments(request.user).all() + elif visit_type == VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS: + return get_approaching_visits_without_appointments(request.user).all() + elif visit_type == VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT: + return get_approaching_visits_for_mail_contact(request.user).all() else: raise TypeError("Unknown query type: " + visit_type) @@ -76,6 +90,16 @@ def get_visits_order(visits_to_be_ordered, order_column, order_direction): 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') + elif order_column == "datetime_begin": + result = visits_to_be_ordered.order_by(order_direction + 'datetime_begin') + elif order_column == "datetime_end": + result = visits_to_be_ordered.order_by(order_direction + 'datetime_end') + elif order_column == "is_finished": + result = visits_to_be_ordered.order_by(order_direction + 'is_finished') + elif order_column == "post_mail_sent": + result = visits_to_be_ordered.order_by(order_direction + 'post_mail_sent') + elif order_column == "visit_number": + result = visits_to_be_ordered.order_by(order_direction + 'visit_number') else: logger.warn("Unknown sort column: " + str(order_column)) return result @@ -90,6 +114,12 @@ def get_visits_filtered(visits_to_be_filtered, filters): result = result.filter(subject__subject__first_name__icontains=value) elif column == "last_name": result = result.filter(subject__subject__last_name__icontains=value) + elif column == "is_finished": + result = result.filter(is_finished=(value == "true")) + elif column == "post_mail_sent": + result = result.filter(post_mail_sent=(value == "true")) + elif column == "visit_number": + result = result.filter(visit_number=int(value)) else: message = "UNKNOWN filter: " if column is None: diff --git a/smash/web/models/study_visit_list.py b/smash/web/models/study_visit_list.py index b9c926a5..10c1bf85 100644 --- a/smash/web/models/study_visit_list.py +++ b/smash/web/models/study_visit_list.py @@ -4,9 +4,19 @@ from django.db import models from web.models import Study, SubjectColumns, VisitColumns VISIT_LIST_GENERIC = "GENERIC" +VISIT_LIST_EXCEEDED_TIME = "EXCEEDED_TIME" +VISIT_LIST_UNFINISHED = "UNFINISHED" +VISIT_LIST_MISSING_APPOINTMENTS = "MISSING_APPOINTMENTS" +VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS = "APPROACHING_WITHOUT_APPOINTMENTS" +VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT = "APPROACHING_FOR_MAIL_CONTACT" VISIT_LIST_CHOICES = { VISIT_LIST_GENERIC: 'Generic', + VISIT_LIST_EXCEEDED_TIME: 'exceeded visit time', + VISIT_LIST_UNFINISHED: 'unfinished visits', + VISIT_LIST_MISSING_APPOINTMENTS: 'visits with missing appointments', + VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS: 'approaching visits', + VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT: 'post mail for approaching visits', } diff --git a/smash/web/static/js/smash.js b/smash/web/static/js/smash.js index 3bfb6789..51626488 100644 --- a/smash/web/static/js/smash.js +++ b/smash/web/static/js/smash.js @@ -70,4 +70,266 @@ function showErrorInfo(content) { $(errorDialogDiv).attr("role", "dialog"); $(errorDialogDiv).addClass("modal modal-danger fade"); $(errorDialogDiv).modal("show"); -} \ No newline at end of file +} + +//------------------------------------------------------ +// common code for dynamic Data Tables +//------------------------------------------------------ + +if (!String.prototype.startsWith) { + String.prototype.startsWith = function (searchString, position) { + position = position || 0; + return this.indexOf(searchString, position) === position; + }; +} + +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(columns, getSubjectEditUrl) { + var result = []; + for (var i = 0; i < columns.length; i++) { + var columnRow = columns[i]; + if (columnRow.type === "edit") { + result.push(createColumn("id", columnRow.name, columnRow.filter, columnRow.visible, function (data, type, row) { + var url = getSubjectEditUrl(row.id.toString()); + + return '<a href="' + url + '" type="button" class="btn btn-block btn-default">Edit</a>'; + })); + } else if (/^visit_[0-9]+$/.test(columnRow.type)) { + var renderFunction = (function () { + var x = columnRow.type.replace("visit_", ""); + return function (data, type, row) { + console.log("render", row.visits); + return create_visit_row(row.visits[x - 1]); + }; + })(); + + result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible, renderFunction)); + + } else { + result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible)); + } + } + 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 createTable(params) { + var tableElement = params.tableElement; + var worker_locations = params.worker_locations; + var subject_types_url = params.subject_types_url; + var locations_url = params.locations_url; + var flying_teams_url = params.flying_teams_url; + var subjects_url = params.subjects_url; + var columnsDefinition = params.columns; + + 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="flying_team_filter"]').each(function () { + var obj = $(this); + obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); + var select = $('select', obj); + $.get(flying_teams_url, function (data) { + $.each(data.flying_teams, function (index, flying_team) { + select.append('<option value="' + flying_team.id + '">' + flying_team.name + '</option>'); + }); + if (worker_locations.length === 1) { + select.val(worker_locations[0].id); + } + }); + }); + + + $(tableElement).find('tfoot div[name="type_filter"]').each(function () { + var obj = $(this); + obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); + 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']] + }); + + // 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); + }); +} + +//------------------------------------------------------ +// END common code for dynamic Data Tables +//------------------------------------------------------ diff --git a/smash/web/static/js/subject.js b/smash/web/static/js/subject.js index 585d1b9f..434b796a 100644 --- a/smash/web/static/js/subject.js +++ b/smash/web/static/js/subject.js @@ -1,253 +1,3 @@ -if (!String.prototype.startsWith) { - String.prototype.startsWith = function (searchString, position) { - position = position || 0; - return this.indexOf(searchString, position) === position; - }; -} - -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(columns, getSubjectEditUrl) { - var result = []; - for (var i = 0; i < columns.length; i++) { - var columnRow = columns[i]; - if (columnRow.type === "edit") { - result.push(createColumn("id", columnRow.name, columnRow.filter, columnRow.visible, function (data, type, row) { - var url = getSubjectEditUrl(row.id.toString()); - - return '<a href="' + url + '" type="button" class="btn btn-block btn-default">Edit</a>'; - })); - } else if (columnRow.type.startsWith("visit")) { - var renderFunction = (function () { - var x = i; - return function (data, type, row) { - return create_visit_row(row.visits[x - 1]); - }; - })(); - - result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible, renderFunction)); - - } else { - result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible)); - } - } - 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 subject_types_url = params.subject_types_url; - var locations_url = params.locations_url; - var flying_teams_url = params.flying_teams_url; - var subjects_url = params.subjects_url; - var columnsDefinition = params.columns; - - 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="flying_team_filter"]').each(function () { - var obj = $(this); - obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); - var select = $('select', obj); - $.get(flying_teams_url, function (data) { - $.each(data.flying_teams, function (index, flying_team) { - select.append('<option value="' + flying_team.id + '">' + flying_team.name + '</option>'); - }); - if (worker_locations.length === 1) { - select.val(worker_locations[0].id); - } - }); - }); - - - - $(tableElement).find('tfoot div[name="type_filter"]').each(function () { - var obj = $(this); - obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); - 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']] - }); - - // 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 + return createTable(params); +} diff --git a/smash/web/static/js/visit.js b/smash/web/static/js/visit.js index c4eac088..a2636ed2 100644 --- a/smash/web/static/js/visit.js +++ b/smash/web/static/js/visit.js @@ -10,3 +10,8 @@ function visit_dates_behaviour(startDateInput, endDateInput) { } }); } + + +function createVisitsTable(params) { + return createTable(params); +} diff --git a/smash/web/templates/visits/index.html b/smash/web/templates/visits/index.html index 6d82fe90..d3dc4a02 100644 --- a/smash/web/templates/visits/index.html +++ b/smash/web/templates/visits/index.html @@ -7,9 +7,6 @@ <!-- DataTables --> <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}"> - <!-- fullCalendar 2.2.5--> - <link rel="stylesheet" href="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.min.css' %}"> - <link rel="stylesheet" href="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.print.css' %}" media="print"> {% endblock styles %} {% block ui_active_tab %}'visits'{% endblock ui_active_tab %} @@ -23,62 +20,12 @@ {% endblock breadcrumb %} {% block maincontent %} - - <div> - <a href="{% url 'web.views.visit_add' %}" class="btn btn-app"> - <i class="fa fa-plus"></i> - Add new visit - </a> + <div class="box-body"> + <table id="table" class="table table-bordered table-striped table-responsive"> + </table> </div> - - <div> - <div> - {% if visit_list %} - <table id="visit_table" class="table table-bordered table-striped"> - <thead> - <tr> - <th>Subject name</th> - <th>Full information</th> - <th>Visit begins</th> - <th>Visit ends</th> - <th>Finished?</th> - </tr> - </thead> - <tbody> - {% for visit in visit_list %} - <tr> - <td>{{ visit.subject.first_name }} {{ visit.subject.last_name }}</td> - <td> - <a href="{% url 'web.views.visit_details' visit.id %}" type="button" - class="btn btn-block btn-default">Details</a> - </td> - <td> - {{ visit.datetime_begin }} - </td> - <td> - {{ visit.datetime_end }} - </td> - <td> - {% if visit.is_finished %} - <button type="button" class="btn btn-block btn-success">YES</button> - {% else %} - <button type="button" class="btn btn-block btn-danger">NO</button> - {% endif %} - </td> - - </tr> - {% endfor %} - </tbody> - </table> - - {% else %} - <p>No visits to display.</p> - {% endif %} - - <hr/> - - </div> - + <h3>Visible columns</h3> + <div id="visible-column-checkboxes" style="display:table; width:100%"> </div> {% endblock maincontent %} @@ -87,19 +34,21 @@ <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 'AdminLTE/plugins/moment.js/moment.min.js' %}"></script> - <script src="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.min.js' %}"></script> + <script src="{% static 'js/visit.js' %}"></script> <script> - $(function () { - $('#planning_table, #approaching_table').DataTable({ - "paging": true, - "lengthChange": false, - "searching": true, - "ordering": true, - "info": true, - "autoWidth": false - }); + function getVisitEditUrl(id) { + return "{% url 'web.views.visit_details' 12345678 %}".replace(/12345678/, id); + } + $.get("{% url 'web.api.visits.columns' visit_list_type %}", function (data) { + createVisitsTable({ + locations_url: "{% url 'web.api.locations' %}", + flying_teams_url: "{% url 'web.api.flying_teams' %}", + subjects_url: "{% url 'web.api.visits' visit_list_type %}", + tableElement: document.getElementById("table"), + columns: getColumns(data.columns, getVisitEditUrl), + checkboxesElement: document.getElementById("visible-column-checkboxes") + }) }); </script> {% endblock scripts %} diff --git a/smash/web/tests/api_views/test_visit.py b/smash/web/tests/api_views/test_visit.py index 09760d48..63049c96 100644 --- a/smash/web/tests/api_views/test_visit.py +++ b/smash/web/tests/api_views/test_visit.py @@ -4,7 +4,9 @@ import logging from django.urls import reverse -from web.models.study_visit_list import VISIT_LIST_GENERIC +from web.models.study_visit_list import VISIT_LIST_GENERIC, VISIT_LIST_EXCEEDED_TIME, VISIT_LIST_UNFINISHED, \ + VISIT_LIST_MISSING_APPOINTMENTS, VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS, \ + VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT from web.tests import LoggedInWithWorkerTestCase from web.tests.functions import create_study_subject, create_get_suffix, create_visit @@ -23,10 +25,33 @@ class TestVisitApi(LoggedInWithWorkerTestCase): 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})) + def test_visits_exceeded(self): + response = self.client.get(reverse('web.api.visits', kwargs={'visit_list_type': VISIT_LIST_EXCEEDED_TIME})) self.assertEqual(response.status_code, 200) + def test_visits_unfinished(self): + response = self.client.get(reverse('web.api.visits', kwargs={'visit_list_type': VISIT_LIST_UNFINISHED})) + self.assertEqual(response.status_code, 200) + + def test_visits_missing_appointments(self): + response = self.client.get( + reverse('web.api.visits', kwargs={'visit_list_type': VISIT_LIST_MISSING_APPOINTMENTS})) + self.assertEqual(response.status_code, 200) + + def test_visits_approaching_without_appointments(self): + response = self.client.get( + reverse('web.api.visits', kwargs={'visit_list_type': VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS})) + self.assertEqual(response.status_code, 200) + + def test_visits_approaching_for_mail_contact(self): + response = self.client.get( + reverse('web.api.visits', kwargs={'visit_list_type': VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT})) + self.assertEqual(response.status_code, 200) + + def test_visits_invalid(self): + with self.assertRaises(TypeError): + self.client.get(reverse('web.api.visits', kwargs={'visit_list_type': "invalid_type"})) + def test_visits_general_search(self): name = "Piotrek" self.study_subject.subject.first_name = name diff --git a/smash/web/views/visit.py b/smash/web/views/visit.py index 0dd67c3b..7a1e821b 100644 --- a/smash/web/views/visit.py +++ b/smash/web/views/visit.py @@ -3,9 +3,10 @@ import logging from django.shortcuts import get_object_or_404, redirect -from notifications import get_active_visits_with_missing_appointments, get_unfinished_visits, \ - get_approaching_visits_without_appointments, get_approaching_visits_for_mail_contact, get_exceeded_visits, \ - waiting_for_appointment +from notifications import waiting_for_appointment +from web.models.study_visit_list import VISIT_LIST_GENERIC, VISIT_LIST_MISSING_APPOINTMENTS, \ + VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS, VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT, VISIT_LIST_EXCEEDED_TIME, \ + VISIT_LIST_UNFINISHED from . import wrap_response from ..forms import VisitDetailForm, VisitAddForm, SubjectDetailForm, StudySubjectDetailForm from ..models import Visit, Appointment, StudySubject, MailTemplate @@ -13,35 +14,32 @@ from ..models import Visit, Appointment, StudySubject, MailTemplate logger = logging.getLogger(__name__) -def visits(request): - visit_list = Visit.objects.order_by('-datetime_begin') - context = { - 'visit_list': visit_list - } +def show_visits(request, visit_list_type): + return wrap_response(request, 'visits/index.html', {'visit_list_type': visit_list_type}) - return wrap_response(request, 'visits/index.html', context) +def visits(request): + return show_visits(request, VISIT_LIST_GENERIC) -def visits_with_missing_appointments(request): - context = { - 'visit_list': get_active_visits_with_missing_appointments(request.user) - } - return wrap_response(request, 'visits/index.html', context) +def visits_with_missing_appointments(request): + return show_visits(request, VISIT_LIST_MISSING_APPOINTMENTS) def approaching_visits_without_appointments(request): - context = { - 'visit_list': get_approaching_visits_without_appointments(request.user) - } - return wrap_response(request, 'visits/index.html', context) + return show_visits(request, VISIT_LIST_APPROACHING_WITHOUT_APPOINTMENTS) def approaching_visits_for_mail_contact(request): - context = { - 'visit_list': get_approaching_visits_for_mail_contact(request.user) - } - return wrap_response(request, 'visits/index.html', context) + return show_visits(request, VISIT_LIST_APPROACHING_FOR_MAIL_CONTACT) + + +def exceeded_visits(request): + return show_visits(request, VISIT_LIST_EXCEEDED_TIME) + + +def unfinished_visits(request): + return show_visits(request, VISIT_LIST_UNFINISHED) def visit_details(request, id): @@ -105,18 +103,3 @@ def visit_add(request, subject_id=-1): form = VisitAddForm(initial={'subject': subject}) return wrap_response(request, 'visits/add.html', {'form': form}) - - -def exceeded_visits(request): - context = { - 'visit_list': get_exceeded_visits(request.user) - } - return wrap_response(request, 'visits/index.html', context) - - -def unfinished_visits(request): - context = { - 'visit_list': get_unfinished_visits(request.user) - } - - return wrap_response(request, 'visits/index.html', context) -- GitLab