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