Skip to content
Snippets Groups Projects
Commit 44b7a137 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

Merge branch '84-performance-of-subject-list' into 'master'

Performance of subject list

Closes #84

See merge request !34
parents be6747df 86303696
No related branches found
No related tags found
1 merge request!34Performance of subject list
Pipeline #
...@@ -16,8 +16,8 @@ appointment-import/tmp.sql ...@@ -16,8 +16,8 @@ appointment-import/tmp.sql
*.iml *.iml
out out
.idea .idea
<<<<<<< HEAD
.coverage .coverage
smash/htmlcov/ smash/htmlcov/
run-coverage.bat
...@@ -22,6 +22,8 @@ urlpatterns = [ ...@@ -22,6 +22,8 @@ urlpatterns = [
url(r'^countries$', api_views.countries, name='web.api.countries'), url(r'^countries$', api_views.countries, name='web.api.countries'),
url(r'^specializations$', api_views.specializations, name='web.api.specializations'), url(r'^specializations$', api_views.specializations, name='web.api.specializations'),
url(r'^units$', api_views.units, name='web.api.units'), url(r'^units$', api_views.units, name='web.api.units'),
url(r'^locations$', api_views.locations, name='web.api.locations'),
url(r'^referrals$', api_views.referrals, name='web.api.referrals'), 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'^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'),
] ]
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import JsonResponse from django.http import JsonResponse
from models import Subject, Worker, AppointmentType from models import Subject, Worker, AppointmentType, Location
from views import e500_error
from views.subject import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT
from views.notifications import get_subjects_with_no_visit, get_subjects_with_reminder
@login_required @login_required
...@@ -12,6 +15,17 @@ def cities(request): ...@@ -12,6 +15,17 @@ def cities(request):
}) })
@login_required
def locations(request):
locations = Location.objects.all()
data = []
for location in locations:
data.append({"id": location.id, "name": location.name})
return JsonResponse({
"locations": data
})
@login_required @login_required
def countries(request): def countries(request):
X = Subject.objects.filter(country__isnull=False).values_list('country').distinct() X = Subject.objects.filter(country__isnull=False).values_list('country').distinct()
...@@ -44,6 +58,144 @@ def units(request): ...@@ -44,6 +58,144 @@ def units(request):
}) })
@login_required
def get_subjects(request, type):
if type == SUBJECT_LIST_GENERIC:
return Subject.objects.all()
elif type == SUBJECT_LIST_NO_VISIT:
return get_subjects_with_no_visit(request.user)
elif type == SUBJECT_LIST_REQUIRE_CONTACT:
return get_subjects_with_reminder(request.user)
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)
elif column == "dead":
result = result.filter(dead=(value == "true"))
elif column == "resigned":
result = result.filter(resigned=(value == "true"))
elif column == "postponed":
result = result.filter(postponed=(value == "true"))
elif column == "default_location":
result = result.filter(default_location=value)
elif column == "":
pass
else:
print "UNKNOWN filter: "
print row
return result
@login_required
def subjects(request, type):
try:
# 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()
data = []
for subject in subjects:
data.append(serialize_subject(subject))
return JsonResponse({
"draw": draw,
"recordsTotal": count,
"recordsFiltered": count_filtered,
"data": data,
})
except:
return e500_error(request)
def get_yes_no(val):
if val:
return "YES"
else:
return "NO"
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": get_yes_no(subject.dead),
"resigned": get_yes_no(subject.resigned),
"postponed": get_yes_no(subject.postponed),
"id": subject.id,
}
return result
@login_required @login_required
def appointment_types(request): def appointment_types(request):
appointments = AppointmentType.objects.filter().all() appointments = AppointmentType.objects.filter().all()
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
{% block styles %} {% block styles %}
{{ block.super }} {{ block.super }}
{% include "includes/tablesorter.css.html" %} <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}">
{% endblock styles %} {% endblock styles %}
{% block ui_active_tab %}'subjects'{% endblock ui_active_tab %} {% block ui_active_tab %}'subjects'{% endblock ui_active_tab %}
...@@ -26,79 +27,131 @@ ...@@ -26,79 +27,131 @@
</div> </div>
<div class="box-body"> <div class="box-body">
{% if subjects_list %} <table id="table" class="table table-bordered table-striped">
<table id="table" class="table table-bordered table-striped tablesorter"> <thead>
<thead> <tr>
<tr> <th>ND</th>
<th>ND</th> <th>Screening</th>
<th>Screening</th> <th>First name
<th>First name</th> </th>
<th>Last name</th> <th>Last name</th>
<th class="filter-select filter-exact" data-placeholder="Select location">Default location</th> <th>Default location</th>
<th>Deceased</th> <th>Deceased</th>
<th>Resigned</th> <th>Resigned</th>
<th>Postponed</th> <th>Postponed</th>
<th>Edit</th> <th>Edit</th>
</tr> </tr>
</thead> </thead>
<tfoot style="display: table-header-group;">
{% include "includes/tablesorter.tfoot.html" %} <tr>
<th>
<tbody> <div name="string_filter">ND</div>
{% for subject in subjects_list %} </th>
<tr> <th>
<td>{{ subject.nd_number }}</td> <div name="string_filter">Screening number</div>
<td>{{ subject.screening_number }}</td> </th>
<td>{{ subject.first_name }}</td> <th>
<td>{{ subject.last_name }}</td> <div name="string_filter">Name</div>
<td>{{ subject.default_location }}</td> </th>
<td>{% if subject.dead %} YES {% else %} NO {% endif %} </td> <th>
<td>{% if subject.resigned %} YES {% else %} NO {% endif %} </td> <div name="string_filter">Surname</div>
<td>{% if subject.postponed %} YES {% else %} NO {% endif %} </td> </th>
<td><a href="{% url 'web.views.subject_edit' subject.id %}" type="button" <th>
class="btn btn-block btn-default">Edit</a></td> <div name="location_filter">---</div>
</tr> </th>
{% endfor %} <th>
<div name="yes_no_filter">---</div>
</tbody> </th>
</table> <th>
{% else %} <div name="yes_no_filter">---</div>
<p>No subjects found.</p> </th>
{% endif %} <th>
<div name="yes_no_filter">---</div>
</th>
</tr>
</tfoot>
<tbody>
</tbody>
</table>
</div> </div>
{% endblock maincontent %} {% endblock maincontent %}
{% block scripts %} {% block scripts %}
{{ block.super }} {{ 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> <script>
$('#table tfoot div[name="string_filter"]').each(function () {
var title = $(this).text();
$(this).html('<input type="text" style="width:80px" placeholder="' + title + '" />');
});
$('#table 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 tfoot div[name="location_filter"]').each(function () {
var obj = $(this)
obj.html('<select style="width:80px"><option value selected="selected">---</option></select>');
$.get("{% url 'web.api.locations' %}", function (data) {
$.each(data.locations, function (index, location) {
$('select', obj).append('<option value="' + location.id + '">' + location.name + '</option>');
});
});
});
$(function () { $(function () {
$('#table').tablesorter({ var table = $('#table').DataTable({
theme: "bootstrap", serverSide: true,
widthFixed: true, processing: true,
headerTemplate: '{content} {icon}', ajax: "{% url 'web.api.subjects' list_type %}",
widgets: ["uitheme", "filter", "columns", "zebra"], columns: [
widgetOptions: { {"data": "nd_number"},
zebra: ["even", "odd"], {"data": "screening_number"},
columns: ["primary", "secondary", "tertiary"], {"data": "first_name"},
filter_reset: ".reset", {"data": "last_name"},
filter_cssFilter: "form-control", {"data": "default_location"},
}, {"data": "dead"},
headers: { {"data": "resigned"},
0: {sorter: true}, {"data": "postponed"},
} {"data": null},
}).tablesorterPager({ ],
container: $(".ts-pager"), columnDefs: [{
cssGoto: ".pagenum", "targets": 8,
// remove rows from the table to speed up the sort of large tables. "data": "id",
// setting this to false, only hides the non-visible rows; needed if you plan to add/remove rows with the pager enabled. "defaultContent": '<a href="#" type="button" class="btn btn-block btn-default">Edit</a>'
removeRows: false, }]
// output string - default is '{page}/{totalPages}'; });
// possible variables: {page}, {totalPages}, {filteredPages}, {startRow}, {endRow}, {filteredRows} and {totalRows}
output: '{startRow} - {endRow} / {filteredRows} ({totalRows})' $('#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");
}); });
</script> </script>
......
...@@ -7,6 +7,13 @@ from web.models.constants import SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL ...@@ -7,6 +7,13 @@ from web.models.constants import SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL
from web.views.notifications import get_today_midnight_date from web.views.notifications import get_today_midnight_date
def create_get_suffix(params):
result = "?"
for key in params:
result += key + "=" + str(params[key]) + "&"
return result
def create_location(name="test"): def create_location(name="test"):
return Location.objects.create(name=name) return Location.objects.create(name=name)
...@@ -27,14 +34,14 @@ def create_appointment_type(): ...@@ -27,14 +34,14 @@ def create_appointment_type():
) )
def create_subject(): def create_subject(id=1):
return Subject.objects.create( return Subject.objects.create(
first_name="Piotr", first_name="Piotr",
last_name="Gawron", last_name="Gawron",
default_location=get_test_location(), default_location=get_test_location(),
sex=SEX_CHOICES_MALE, sex=SEX_CHOICES_MALE,
type=SUBJECT_TYPE_CHOICES_CONTROL, type=SUBJECT_TYPE_CHOICES_CONTROL,
screening_number="piotr's number", screening_number="piotr's number" + str(id),
country="france") country="france")
......
...@@ -3,11 +3,14 @@ import json ...@@ -3,11 +3,14 @@ import json
from django.urls import reverse from django.urls import reverse
from web.api_views import get_subjects_order, get_subjects_filtered, serialize_subject
from web.models import Subject
from web.tests.functions import create_location, \
create_get_suffix
from web.tests.functions import create_subject, create_appointment_type from web.tests.functions import create_subject, create_appointment_type
from web.views.subject import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT
from . import LoggedInWithWorkerTestCase from . import LoggedInWithWorkerTestCase
__author__ = 'Piotr Gawron'
class TestApi(LoggedInWithWorkerTestCase): class TestApi(LoggedInWithWorkerTestCase):
def setUp(self): def setUp(self):
...@@ -32,6 +35,24 @@ class TestApi(LoggedInWithWorkerTestCase): ...@@ -32,6 +35,24 @@ class TestApi(LoggedInWithWorkerTestCase):
self.assertTrue(city_name in cities) self.assertTrue(city_name in cities)
def test_locations(self):
location_name = "some location"
response = self.client.get(reverse('web.api.locations'))
self.assertEqual(response.status_code, 200)
location = create_location(location_name)
response = self.client.get(reverse('web.api.locations'))
appointment_types = json.loads(response.content)['locations']
found = False
for type in appointment_types:
if type['name'] == location_name:
found = True
self.assertTrue(found)
def test_countries(self): def test_countries(self):
country_name = "some country" country_name = "some country"
...@@ -123,3 +144,216 @@ class TestApi(LoggedInWithWorkerTestCase): ...@@ -123,3 +144,216 @@ class TestApi(LoggedInWithWorkerTestCase):
found = True found = True
self.assertTrue(found) self.assertTrue(found)
def test_subjects_general(self):
response = self.client.get(reverse('web.api.subjects', kwargs={'type': SUBJECT_LIST_GENERIC}))
self.assertEqual(response.status_code, 200)
def test_subjects_no_visit(self):
response = self.client.get(reverse('web.api.subjects', kwargs={'type': SUBJECT_LIST_NO_VISIT}))
self.assertEqual(response.status_code, 200)
def test_subjects_require_contact(self):
response = self.client.get(reverse('web.api.subjects', kwargs={'type': SUBJECT_LIST_REQUIRE_CONTACT}))
self.assertEqual(response.status_code, 200)
def test_subjects_invalid(self):
response = self.client.get(reverse('web.api.subjects', kwargs={'type': "bla"}))
self.assertEqual(response.status_code, 500)
def test_subjects_general_search(self):
name = "Piotrek"
self.subject.first_name = name
self.subject.save()
params = {
"columns[0][search][value]": "another_name",
"columns[0][data]": "first_name"
}
url = ("%s" + create_get_suffix(params)) % reverse('web.api.subjects', kwargs={'type': SUBJECT_LIST_GENERIC})
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertFalse(name in response.content)
params["columns[0][search][value]"] = name
url = ("%s" + create_get_suffix(params)) % reverse('web.api.subjects', kwargs={'type': SUBJECT_LIST_GENERIC})
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertTrue(name in response.content)
def check_subject_filtered(self, filters, result):
subjects = get_subjects_filtered(Subject.objects.all(), filters)
self.assertEqual(len(result), subjects.count())
for index in range(len(result)):
self.assertEqual(result[index], subjects[index])
def check_subject_ordered(self, order, result):
subjects = get_subjects_order(Subject.objects.all(), order, "asc")
self.assertEqual(len(result), subjects.count())
for index in range(len(result)):
self.assertEqual(result[index], subjects[index])
subjects = get_subjects_order(Subject.objects.all(), order, "desc")
length = len(result)
self.assertEqual(length, subjects.count())
for index in range(length):
self.assertEqual(result[length - index - 1], subjects[index])
def test_subjects_sort_nd_number(self):
subject = self.subject
subject.nd_number = "PPP"
subject.save()
subject2 = create_subject(2)
subject2.nd_number = "QQQ"
subject2.save()
self.check_subject_ordered("nd_number", [subject, subject2])
def test_subjects_sort_default_location(self):
subject = self.subject
self.check_subject_ordered("default_location", [subject])
def test_subjects_sort_screening_number(self):
subject = self.subject
subject.screening_number = "PPP"
subject.save()
subject2 = create_subject(2)
subject2.screening_number = "QQQ"
subject2.save()
self.check_subject_ordered("screening_number", [subject, subject2])
def test_subjects_sort_last_name(self):
subject = self.subject
subject.last_name = "XXX"
subject.save()
subject2 = create_subject(2)
subject2.last_name = "YYY"
subject2.save()
self.check_subject_ordered("last_name", [subject, subject2])
def test_subjects_sort_dead(self):
subject = self.subject
subject.dead = True
subject.save()
subject2 = create_subject(2)
subject2.dead = False
subject2.save()
self.check_subject_ordered("dead", [subject2, subject])
def test_subjects_sort_resigned(self):
subject = self.subject
subject.resigned = True
subject.save()
subject2 = create_subject(2)
subject2.resigned = False
subject2.save()
self.check_subject_ordered("resigned", [subject2, subject])
def test_subjects_sort_postponed(self):
subject = self.subject
subject.postponed = True
subject.save()
subject2 = create_subject(2)
subject2.postponed = False
subject2.save()
self.check_subject_ordered("postponed", [subject2, subject])
def test_subjects_filter_dead(self):
subject = self.subject
subject.dead = True
subject.save()
subject2 = create_subject(2)
subject2.dead = False
subject2.save()
self.check_subject_filtered([["dead", "true"]], [subject])
self.check_subject_filtered([["dead", "false"]], [subject2])
def test_subjects_filter_nd_number(self):
subject = self.subject
subject.nd_number = "PPP"
subject.save()
subject2 = create_subject(2)
subject2.nd_number = "QQQ"
subject2.save()
self.check_subject_filtered([["nd_number", "P"]], [subject])
def test_subjects_filter_screening_number(self):
subject = self.subject
subject.screening_number = "PPP"
subject.save()
subject2 = create_subject(2)
subject2.screening_number = "QQQ"
subject2.save()
self.check_subject_filtered([["screening_number", "Q"]], [subject2])
def test_subjects_filter_last_name(self):
subject = self.subject
subject.last_name = "XXX"
subject.save()
subject2 = create_subject(2)
subject2.last_name = "YYY"
subject2.save()
self.check_subject_filtered([["last_name", "Q"]], [])
def test_subjects_filter_resigned(self):
subject = self.subject
subject.resigned = True
subject.save()
subject2 = create_subject(2)
subject2.resigned = False
subject2.save()
self.check_subject_filtered([["resigned", "true"]], [subject])
self.check_subject_filtered([["resigned", "false"]], [subject2])
def test_subjects_filter_postponed(self):
subject = self.subject
subject.postponed = True
subject.save()
subject2 = create_subject(2)
subject2.postponed = False
subject2.save()
self.check_subject_filtered([["postponed", "true"]], [subject])
self.check_subject_filtered([["postponed", "false"]], [subject2])
def test_subjects_filter_default_location(self):
subject = self.subject
self.check_subject_filtered([["default_location", str(subject.default_location.id)]], [subject])
self.check_subject_filtered([["default_location", "-1"]], [])
def test_subjects_filter_unknown(self):
subject = self.subject
self.check_subject_filtered([["some_unknown", "zxczxc"]], [subject])
self.check_subject_filtered([["", ""]], [subject])
def test_serialize_subject(self):
subject = self.subject
subject.dead = True
subject_json = serialize_subject(subject);
self.assertEqual("YES", subject_json["dead"]);
import datetime 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_appointment, create_location, create_worker, create_appointment_type
from functions import create_subject from functions import create_subject
from functions import create_visit from functions import create_visit
...@@ -10,6 +12,7 @@ from web.views.notifications import \ ...@@ -10,6 +12,7 @@ from web.views.notifications import \
get_approaching_visits_without_appointments, \ get_approaching_visits_without_appointments, \
get_approaching_visits_without_appointments_count, \ get_approaching_visits_without_appointments_count, \
get_exceeded_visit_notifications_count, \ get_exceeded_visit_notifications_count, \
get_filter_locations, \
get_notifications, \ get_notifications, \
get_subject_with_no_visit_notifications_count, \ get_subject_with_no_visit_notifications_count, \
get_subjects_with_reminder_count, \ get_subjects_with_reminder_count, \
...@@ -418,3 +421,14 @@ class NotificationViewTests(LoggedInTestCase): ...@@ -418,3 +421,14 @@ class NotificationViewTests(LoggedInTestCase):
notification = get_subject_with_no_visit_notifications_count(self.user) notification = get_subject_with_no_visit_notifications_count(self.user)
self.assertEquals(original_without_visit_notification.count, notification.count) self.assertEquals(original_without_visit_notification.count, notification.count)
def test_get_filter_locations_for_invalid_user(self):
locations = get_filter_locations(AnonymousUser())
self.assertEqual(0, locations.count())
def test_get_filter_locations_for_invalid_user_2(self):
try:
locations = get_filter_locations("invalid class")
self.fail("Exception expected")
except TypeError:
pass
...@@ -7,13 +7,14 @@ from . import wrap_response ...@@ -7,13 +7,14 @@ from . import wrap_response
from ..forms import SubjectAddForm, SubjectEditForm, VisitDetailForm, get_prefix_screening_number from ..forms import SubjectAddForm, SubjectEditForm, VisitDetailForm, get_prefix_screening_number
from ..models import Subject, Worker from ..models import Subject, Worker
SUBJECT_LIST_GENERIC = "GENERIC"
SUBJECT_LIST_NO_VISIT = "NO_VISIT"
SUBJECT_LIST_REQUIRE_CONTACT = "REQUIRE_CONTACT"
def subjects(request): def subjects(request):
subjects_list = Subject.objects.order_by('-last_name')
context = { context = {
'subjects_list': subjects_list 'list_type': SUBJECT_LIST_GENERIC,
} }
return wrap_response(request, 'subjects/index.html', context) return wrap_response(request, 'subjects/index.html', context)
...@@ -32,20 +33,16 @@ def subject_add(request): ...@@ -32,20 +33,16 @@ def subject_add(request):
def subject_no_visits(request): def subject_no_visits(request):
subjects_list = get_subjects_with_no_visit(request.user).order_by('-last_name')
context = { context = {
'subjects_list': subjects_list 'list_type': SUBJECT_LIST_NO_VISIT,
} }
return wrap_response(request, 'subjects/index.html', context) return wrap_response(request, 'subjects/index.html', context)
def subject_require_contact(request): def subject_require_contact(request):
subjects_list = get_subjects_with_reminder(request.user).order_by('-last_name')
context = { context = {
'subjects_list': subjects_list 'list_type': SUBJECT_LIST_REQUIRE_CONTACT,
} }
return wrap_response(request, 'subjects/index.html', context) return wrap_response(request, 'subjects/index.html', context)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment