From 47ca62ef97fe1a4bd63b7cc2520fd4153e85f469 Mon Sep 17 00:00:00 2001 From: Piotr Gawron <piotr.gawron@uni.lu> Date: Thu, 30 Mar 2017 15:58:25 +0200 Subject: [PATCH] lazy loading with sorting and filtering (only by string fields) --- smash/web/api_urls.py | 1 + smash/web/api_views.py | 128 +++++++++++++++++++++++ smash/web/templates/subjects/index.html | 130 ++++++++++++------------ smash/web/views/subject.py | 4 +- 4 files changed, 198 insertions(+), 65 deletions(-) diff --git a/smash/web/api_urls.py b/smash/web/api_urls.py index d783526c..65db6f11 100644 --- a/smash/web/api_urls.py +++ b/smash/web/api_urls.py @@ -24,4 +24,5 @@ urlpatterns = [ url(r'^units$', api_views.units, name='web.api.units'), url(r'^referrals$', api_views.referrals, name='web.api.referrals'), url(r'^appointment_types$', api_views.appointment_types, name='web.api.appointment_types'), + url(r'^subjects/(?P<type>[A-z]+)$', api_views.subjects, name='web.api.subjects'), ] diff --git a/smash/web/api_views.py b/smash/web/api_views.py index c6c37379..aa9797fa 100644 --- a/smash/web/api_views.py +++ b/smash/web/api_views.py @@ -1,7 +1,10 @@ +import json + from django.contrib.auth.decorators import login_required from django.http import JsonResponse from models import Subject, Worker, AppointmentType +from views.subject import SUBJECT_LIST_GENERIC @login_required @@ -44,6 +47,131 @@ def units(request): }) +@login_required +def get_subjects(request, type): + if type == SUBJECT_LIST_GENERIC: + return Subject.objects.all() + else: + raise TypeError("Unknown query type: " + type) + + +def get_subjects_order(subjects, order_column, order_direction): + result = subjects + if order_direction == "asc": + order_direction = "" + else: + order_direction = "-" + if order_column == "first_name": + result = subjects.order_by(order_direction + 'first_name') + elif order_column == "last_name": + result = subjects.order_by(order_direction + 'last_name') + elif order_column == "nd_number": + result = subjects.order_by(order_direction + 'nd_number') + elif order_column == "screening_number": + result = subjects.order_by(order_direction + 'screening_number') + elif order_column == "default_location": + result = subjects.order_by(order_direction + 'default_location') + elif order_column == "dead": + result = subjects.order_by(order_direction + 'dead') + elif order_column == "resigned": + result = subjects.order_by(order_direction + 'resigned') + elif order_column == "postponed": + result = subjects.order_by(order_direction + 'postponed') + return result + + +def get_subjects_filtered(subjects, filters): + result = subjects + for row in filters: + column = row[0] + value = row[1] + if column == "first_name": + result = result.filter(first_name__contains=value) + elif column == "last_name": + result = result.filter(last_name__contains=value) + elif column == "nd_number": + result = result.filter(nd_number__contains=value) + elif column == "screening_number": + result = result.filter(screening_number__contains=value) + else: + print row + # elif order_column == "default_location": + # result = subjects.order_by(order_direction + 'default_location') + # elif order_column == "dead": + # result = subjects.order_by(order_direction + 'dead') + # elif order_column == "resigned": + # result = subjects.order_by(order_direction + 'resigned') + # elif order_column == "postponed": + # result = subjects.order_by(order_direction + 'postponed') + + return result + + +@login_required +def subjects(request, type): + # id of the query from dataTable: https://datatables.net/manual/server-side + draw = int(request.GET.get("draw", "-1")) + + start = int(request.GET.get("start", "0")) + length = int(request.GET.get("length", "10")) + order = int(request.GET.get("order[0][column]", "0")) + order_dir = request.GET.get("order[0][dir]", "asc") + order_column = request.GET.get("columns[" + str(order) + "][data]", "last_name") + + filters = [] + column_id = 0 + while request.GET.get("columns[" + str(column_id) + "][search][value]", "unknown") != "unknown": + val = request.GET.get("columns[" + str(column_id) + "][search][value]", "unknown") + if val != "": + filters.append([request.GET.get("columns[" + str(column_id) + "][data]"), val]) + column_id += 1 + + all_subjects = get_subjects(request, type) + + count = all_subjects.count() + + ordered_subjects = get_subjects_order(all_subjects, order_column, order_dir) + + filtered_subjects = get_subjects_filtered(ordered_subjects, filters) + + sliced_subjects = filtered_subjects[start:(start + length)] + + subjects = sliced_subjects + + count_filtered = filtered_subjects.count() + + print json.dumps(request.GET, indent=2) + + data = [] + for subject in subjects: + data.append(serialize_subject(subject)) + + return JsonResponse({ + "draw": draw, + "recordsTotal": count, + "recordsFiltered": count_filtered, + "data": data, + }) + + +def serialize_subject(subject): + location = "" + if subject.default_location is not None: + location = subject.default_location.name + + result = { + "first_name": subject.first_name, + "last_name": subject.last_name, + "nd_number": subject.nd_number, + "screening_number": subject.screening_number, + "default_location": location, + "dead": subject.dead, + "resigned": subject.resigned, + "postponed": subject.postponed, + } + return result + + @login_required def appointment_types(request): appointments = AppointmentType.objects.filter().all() diff --git a/smash/web/templates/subjects/index.html b/smash/web/templates/subjects/index.html index 7d870643..dfe108ac 100644 --- a/smash/web/templates/subjects/index.html +++ b/smash/web/templates/subjects/index.html @@ -3,7 +3,7 @@ {% block styles %} {{ block.super }} - {% include "includes/tablesorter.css.html" %} + <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}"> {% endblock styles %} {% block ui_active_tab %}'subjects'{% endblock ui_active_tab %} @@ -26,78 +26,80 @@ </div> <div class="box-body"> - {% if subjects_list %} - <table id="table" class="table table-bordered table-striped tablesorter"> - <thead> - <tr> - <th>ND</th> - <th>Screening</th> - <th>First name</th> - <th>Last name</th> - <th class="filter-select filter-exact" data-placeholder="Select location">Default location</th> - <th>Deceased</th> - <th>Resigned</th> - <th>Postponed</th> - <th>Edit</th> - </tr> - </thead> - - {% include "includes/tablesorter.tfoot.html" %} - - <tbody> - {% for subject in subjects_list %} - <tr> - <td>{{ subject.nd_number }}</td> - <td>{{ subject.screening_number }}</td> - <td>{{ subject.first_name }}</td> - <td>{{ subject.last_name }}</td> - <td>{{ subject.default_location }}</td> - <td>{% if subject.dead %} YES {% else %} NO {% endif %} </td> - <td>{% if subject.resigned %} YES {% else %} NO {% endif %} </td> - <td>{% if subject.postponed %} YES {% else %} NO {% endif %} </td> - <td><a href="{% url 'web.views.subject_edit' subject.id %}" type="button" - class="btn btn-block btn-default">Edit</a></td> - </tr> - {% endfor %} - - </tbody> - </table> - {% else %} - <p>No subjects found.</p> - {% endif %} + <table id="table" class="table table-bordered table-striped"> + <thead> + <tr> + <th> + <div name="string_filter">ND</div> + ND + + </th> + <th> + <div name="string_filter">Screening number</div> + Screening + </th> + <th> + <div name="string_filter">Name</div> + First name + + </th> + <th> + <div name="string_filter">Surname</div> + Last name + </th> + <th>Default location</th> + <th>Deceased</th> + <th>Resigned</th> + <th>Postponed</th> + <th>Edit</th> + </tr> + </thead> + + <tbody> + </tbody> + </table> </div> {% endblock maincontent %} {% block scripts %} {{ block.super }} - {% include "includes/tablesorter.js.html" %} + <script src="{% static 'AdminLTE/plugins/datatables/jquery.dataTables.min.js' %}"></script> + <script src="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.min.js' %}"></script> <script> + $('#table thead div[name="string_filter"]').each(function () { + var title = $(this).text(); + $(this).html('<input type="text" placeholder="Search ' + title + '" />'); + }); + $(function () { - $('#table').tablesorter({ - theme: "bootstrap", - widthFixed: true, - headerTemplate: '{content} {icon}', - widgets: ["uitheme", "filter", "columns", "zebra"], - widgetOptions: { - zebra: ["even", "odd"], - columns: ["primary", "secondary", "tertiary"], - filter_reset: ".reset", - filter_cssFilter: "form-control", - }, - headers: { - 0: {sorter: true}, - } - }).tablesorterPager({ - container: $(".ts-pager"), - cssGoto: ".pagenum", - // remove rows from the table to speed up the sort of large tables. - // setting this to false, only hides the non-visible rows; needed if you plan to add/remove rows with the pager enabled. - removeRows: false, - // output string - default is '{page}/{totalPages}'; - // possible variables: {page}, {totalPages}, {filteredPages}, {startRow}, {endRow}, {filteredRows} and {totalRows} - output: '{startRow} - {endRow} / {filteredRows} ({totalRows})' + var table = $('#table').DataTable({ + serverSide: true, + processing: 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"} + ] + }); + // Apply the search + table.columns().every(function () { + var that = this; + + $('input', this.header()).on('keyup change', function () { + if (that.search() !== this.value) { + that + .search(this.value) + .draw(); + } + }); }); }); </script> diff --git a/smash/web/views/subject.py b/smash/web/views/subject.py index 37732d54..85a305b8 100644 --- a/smash/web/views/subject.py +++ b/smash/web/views/subject.py @@ -7,11 +7,13 @@ from . import wrap_response from ..forms import SubjectAddForm, SubjectEditForm, VisitDetailForm, get_prefix_screening_number from ..models import Subject, Worker +SUBJECT_LIST_GENERIC = "GENERIC" def subjects(request): subjects_list = Subject.objects.order_by('-last_name') context = { - 'subjects_list': subjects_list + 'subjects_list': subjects_list, + 'list_type': SUBJECT_LIST_GENERIC, } return wrap_response(request, 'subjects/index.html', context) -- GitLab