Skip to content
This repository was archived by the owner on Aug 29, 2023. It is now read-only.

Commit d7e75fc

Browse files
committed
feat: add view for nonreporting centres
also, fix issues with centre edit view+template
1 parent 446c284 commit d7e75fc

6 files changed

Lines changed: 265 additions & 15 deletions

File tree

locations/forms.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import calendar
2+
13
from django import forms
2-
from .models import Location
4+
5+
from br.utils import get_report_year_range
6+
from locations.models import Location
37

48

59
def generate_edit_form(location, data=None):
6-
state_choices = Location.objects.filter(type__name='state').values_list(
10+
state_choices = Location.objects.filter(type__name='State').values_list(
711
'id', 'name')
812

913
def clean_location_field(form, field_name, location_type):
@@ -64,3 +68,22 @@ class CenterCreationForm(forms.Form):
6468
name = forms.CharField()
6569
lga = forms.ModelChoiceField(queryset=Location.objects.filter(
6670
type__name=u'LGA'))
71+
72+
73+
def _get_year_choices():
74+
choices = [('', '----- Select year -----')]
75+
year_range = get_report_year_range()
76+
choices.extend([(yr, yr) for yr in range(year_range[0], year_range[1] + 1)])
77+
return choices
78+
79+
80+
def _get_month_choices():
81+
choices = [('', '----- Select month -----')]
82+
choices.extend([(i, calendar.month_abbr[i]) for i in range(1, 13)])
83+
return choices
84+
85+
86+
class NonReportingCentresFilterForm(forms.Form):
87+
location = forms.ModelChoiceField(queryset=Location.objects.filter(type__name__in=['State', 'LGA']), required=False)
88+
year = forms.ChoiceField(choices=_get_year_choices, required=False)
89+
month = forms.ChoiceField(choices=_get_month_choices, required=False)

locations/templates/locations/center_edit.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% extends 'base/layout.html' %}
2-
{% load pipeline staticfiles %}
2+
{% load pipeline staticfiles widget_tweaks %}
33
{% block stylesheets %}
44
{% stylesheet 'centers' %}
55
{% endblock %}
@@ -21,7 +21,7 @@
2121
</div>
2222
<div class="form-group">
2323
<label for="{{ form.state.name }}">{{ form.state.label }}</label>
24-
{{ form.state }}
24+
{% render_field form.state class='form-control' %}
2525
</div>
2626
<div class="form-group">
2727
<label for="{{ form.lga.id_for_label }}">{{ form.lga.label|upper }}</label>
@@ -46,6 +46,7 @@
4646
</div>
4747
{% endblock %}
4848
{% block scripts %}
49+
<script src="{% static 'base/vendor/jquery/dist/jquery.min.js' %}"></script>
4950
{% javascript 'centers' %}
5051
<script type="text/javascript">
5152
$(document).ready(function() {
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
{% extends 'base/layout.html' %}{% load bootstrap_pagination location_tags static widget_tweaks %}
2+
{% block title %}Birth Registration Statistics &middot; {{ page_title }}{% endblock %}
3+
{% block usermenu %}
4+
{% include 'common/usermenu.html' %}
5+
{% endblock %}
6+
{% block stylesheets %}
7+
{{ block.super }}
8+
<link rel="stylesheet" type="text/css" href="{% static 'css/select2/select2.min.css' %}">
9+
<style type="text/css">
10+
.select2-container.form-control {
11+
display: inline-block;
12+
}
13+
</style>
14+
{% endblock %}
15+
{% block masthead %}
16+
<div class="row">
17+
<div class="col-lg-12 title">
18+
<ol class="breadcrumb">
19+
<li><a href="{% url "br:dashboard" %}">Birth Registration</a></li>
20+
<li class="active">{{ page_title }}</li>
21+
</ol>
22+
<h1 class="page-title">{{ page_title }}</h1>
23+
</div>
24+
</div>
25+
{% endblock %}
26+
{% block content %}
27+
<div class="row">
28+
<div class="col-lg-12">
29+
<section class="widget">
30+
<form class="form-horizontal form-inline" role="form" id="filter_form">
31+
<fieldset>
32+
<div class="form-group row">
33+
<div class="col-md-12">
34+
{% render_field filter_form.year class="form-control" %}
35+
{% render_field filter_form.month class="form-control" %}
36+
<button type="submit" class="btn btn-primary" id="filter_button">Filter</button>
37+
<a class="btn btn-inverse" href="{% url 'locations:non_reporting_center_list' %}">Clear</a>
38+
<a class="btn btn-primary-outline" href="{% url 'locations:non_reporting_center_list' %}?export=1&year={{ year }}&month={{ month }}">Export</a>
39+
</div>
40+
</div>
41+
</fieldset>
42+
</form>
43+
</section>
44+
</div>
45+
</div>
46+
<div class="row">
47+
<div class="col-lg-12">
48+
<section class="widget">
49+
<div class="widget-table">
50+
<table class="table table-striped table-bordered table-hover">
51+
<thead>
52+
<tr>
53+
<th>State</th>
54+
<th>LGA</th>
55+
<th>Name</th>
56+
<th>Code</th>
57+
<th>Active?</th>
58+
<th>Last report received</th>
59+
<th></th>
60+
</tr>
61+
</thead>
62+
<tbody>
63+
{% for centre in centres %}
64+
{% with ancestors=centre.get_ancestors %}
65+
<tr>
66+
<td>{{ ancestors|extract_ancestor_name:'State' }}</td>
67+
<td>{{ ancestors|extract_ancestor_name:'LGA' }}</td>
68+
<td>{{ centre.name }}</td>
69+
<td>{{ centre.code }}</td>
70+
{% if centre.active %}
71+
<td class="success"><i class="glyphicon glyphicon-ok"></i></td>
72+
{% else %}
73+
<td class="danger"><i class="glyphicon glyphicon-remove"></i></td>
74+
{% endif %}
75+
<td>{{ centre.latest_birth_report_time|date:'SHORT_DATE_FORMAT' }}</td>
76+
<td><a href="{% url 'locations:center_edit' pk=centre.pk %}">Edit</a></td>
77+
</tr>
78+
{% endwith %}
79+
{% empty %}
80+
<tr>
81+
<td class="table-warning text-xs-center" colspan="7">No non-reporting centers found</td>
82+
</tr>
83+
{% endfor %}
84+
</tbody>
85+
</table>
86+
</div>
87+
</section>
88+
</div>
89+
</div>
90+
<div class="row">
91+
<div class="col-lg-12">
92+
<div class="text-xs-center">
93+
{% bootstrap_paginate page_obj range=10 show_first_last="true" %}
94+
</div>
95+
</div>
96+
</div>
97+
{% endblock content %}
98+
{% block scripts %}
99+
{{ block.super }}
100+
<script src="{% static "js/select2/select2.min.js" %}"></script>
101+
<script>
102+
document.addEventListener('DOMContentLoaded', function () {
103+
var endpoint = '{% url "api:location_list_typed" %}?type=lga';
104+
$('#{{ filter_form.lga.id_for_label }}').select2({
105+
ajax: {
106+
url: endpoint,
107+
data: function(params) {
108+
return {q: params.term};
109+
},
110+
dataType: 'json',
111+
quietMillis: 250,
112+
processResults: function(data, params) {
113+
return {results: data.results};
114+
}
115+
},
116+
width: '30em',
117+
allowClear: true,
118+
minimumInputLength: 2,
119+
cache: true,
120+
templateResult: function(item) {
121+
return item.name + ' (' + item.type + ')';
122+
},
123+
templateSelection: function(item) {
124+
if (item.text)
125+
return item.text;
126+
else
127+
return item.name + ' (' + item.type + ')';
128+
}
129+
});
130+
});
131+
</script>
132+
{% endblock %}

locations/urls.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#!/usr/bin/env python
22
# vim: ai ts=4 sts=4 et sw=4
33
from django.conf.urls import url
4-
from locations.views import *
4+
from locations import views
55

66
urlpatterns = [
7-
url(r'^center/new/?$', CenterCreationView.as_view(), name='center_add'),
8-
url(r'^centers/?$', CenterListView.as_view(), name='center_list'),
9-
url(r'^center/(?P<pk>\d+)/?$', CenterUpdateView.as_view(), name='center_edit'),
7+
url(r'^center/new/?$', views.CenterCreationView.as_view(), name='center_add'),
8+
url(r'^centers/?$', views.CenterListView.as_view(), name='center_list'),
9+
url(r'^non-reporting-centers/?$', views.NonReportingCentresView.as_view(), name='non_reporting_center_list'),
10+
url(r'^center/(?P<pk>\d+)/?$', views.CenterUpdateView.as_view(), name='center_edit'),
1011
]

locations/views.py

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# vim: ai ts=4 sts=4 et sw=4
2+
import csv
23
import json
34

45
from django.contrib import messages
@@ -7,17 +8,23 @@
78
from django.core.exceptions import ObjectDoesNotExist
89
from django.core.urlresolvers import reverse, reverse_lazy
910
from django.db import connection
11+
from django.db.models import (
12+
Case, Count, IntegerField, OuterRef, Subquery, When)
1013
from django.forms.formsets import formset_factory
11-
from django.http import HttpResponse, HttpResponseRedirect
14+
from django.http import (
15+
HttpResponse, HttpResponseRedirect, StreamingHttpResponse)
1216
from django.shortcuts import get_object_or_404
17+
from django.utils.timezone import now
1318
from django.views.generic import ListView, FormView, TemplateView
1419
from drf_yasg.utils import swagger_auto_schema
1520
import pandas as pd
1621

1722
from django.conf import settings
1823

24+
from br.models import BirthRegistration
1925
from locations import raw_queries
20-
from locations.forms import generate_edit_form, CenterCreationForm
26+
from locations.forms import (
27+
generate_edit_form, CenterCreationForm, NonReportingCentresFilterForm)
2128
from locations.filters import CenterFilterSet
2229
from locations.models import Facility, Location, LocationType
2330

@@ -57,11 +64,14 @@ def get_queryset(self):
5764

5865
class CenterUpdateView(
5966
LoginRequiredMixin, PermissionRequiredMixin, FormView):
67+
permission_required = 'locations.edit_location'
6068
template_name = 'locations/center_edit.html'
6169

6270
def get_context_data(self, **kwargs):
6371
context = super(CenterUpdateView, self).get_context_data(**kwargs)
6472

73+
self.object = self.get_object()
74+
6575
context['page_title'] = 'Edit center: {}'.format(self.object.name)
6676
context[u'location'] = self.object
6777

@@ -77,8 +87,7 @@ def form_valid(self, form):
7787
center = Location.objects.get(pk=form.cleaned_data['id'])
7888
center.name = form.cleaned_data['name']
7989
center.code = form.cleaned_data['code']
80-
center.parent = Location.objects.get_object_or_404(
81-
pk=form.cleaned_data['lga'])
90+
center.parent = get_object_or_404(Location, pk=form.cleaned_data['lga'])
8291
center.active = form.cleaned_data['active']
8392
center.save()
8493
center.facilities.update(
@@ -87,14 +96,14 @@ def form_valid(self, form):
8796

8897
return HttpResponseRedirect(self.get_success_url())
8998

90-
def get_form(self, form_class):
91-
return generate_edit_form(self.object)
99+
def get_form(self, form_class=None):
100+
return generate_edit_form(self.get_object())
92101

93102
def get_queryset(self):
94103
return Location.objects.filter(type__name='RC')
95104

96105
def get_success_url(self):
97-
return reverse('center_list')
106+
return reverse('locations:center_list')
98107

99108
def post(self, request, *args, **kwargs):
100109
form = generate_edit_form(self.get_object(), request.POST)
@@ -195,3 +204,86 @@ def facilities(request):
195204
facility_dataframe.to_csv(response, encoding='UTF-8', index=False)
196205

197206
return response
207+
208+
209+
210+
class CSVBuffer(object):
211+
def write(self, value):
212+
return value
213+
214+
215+
def _get_rows(centres):
216+
yield ['Centre', 'Code', 'LGA', 'State', 'Active?', 'Last report date']
217+
218+
for centre in centres:
219+
last_report_time = centre.latest_birth_report_time()
220+
yield [
221+
centre.name,
222+
centre.code,
223+
centre.parent.name,
224+
centre.parent.parent.name,
225+
'Yes' if centre.active else 'No',
226+
last_report_time.strftime('%d-%m-%Y') if last_report_time else 'N/A'
227+
]
228+
229+
230+
class NonReportingCentresView(LoginRequiredMixin, ListView):
231+
context_object_name = 'centres'
232+
page_title = 'Non-reporting centres'
233+
paginate_by = settings.PAGE_SIZE
234+
template_name = 'locations/non_reporting_centre_list.html'
235+
236+
def get(self, request, *args, **kwargs):
237+
centres = self.get_queryset()
238+
239+
year = request.GET.get('year') or None
240+
month = request.GET.get('month') or None
241+
242+
if request.GET.get('export'):
243+
writer = csv.writer(CSVBuffer())
244+
response = StreamingHttpResponse((writer.writerow(row) for row in _get_rows(centres)), content_type='text/csv')
245+
filename = 'non-reporting-centres-{}-{}.csv'.format(
246+
year,
247+
str(month).zfill(2)
248+
)
249+
response['Content-Disposition'] = 'attachment; filename={}'.format(
250+
filename)
251+
252+
return response
253+
254+
return super(NonReportingCentresView, self).get(request, *args, **kwargs)
255+
256+
def get_context_data(self, **kwargs):
257+
context = super(NonReportingCentresView, self).get_context_data(**kwargs)
258+
259+
context['page_title'] = self.page_title
260+
context['filter_form'] = self.filter_form
261+
context['year'] = self.request.GET.get('year') or now().year
262+
context['month'] = self.request.GET.get('month') or ''
263+
264+
return context
265+
266+
def get_queryset(self):
267+
self.filter_form = NonReportingCentresFilterForm(self.request.GET)
268+
if self.filter_form.is_valid():
269+
filter_data = self.filter_form.cleaned_data
270+
else:
271+
filter_data = {}
272+
273+
filter_kwargs = {}
274+
year = filter_data.get('year') or now().year
275+
filter_kwargs['birthregistration_records__time__year'] = int(year)
276+
month = filter_data.get('month')
277+
if month:
278+
filter_kwargs['birthregistration_records__time__month'] = int(month)
279+
280+
if len(filter_kwargs) == 0:
281+
centres = Location.objects.none()
282+
else:
283+
centres = Location.objects.filter(
284+
type__name='RC'
285+
).annotate(
286+
cnt=Count(Case(When(then=1, **filter_kwargs)))
287+
).filter(cnt=0)
288+
289+
return centres

unicefng/templates/common/usermenu.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{% if user.is_authenticated %}
33
{% if perms.br.change_birthregistration %}<li><a class="dropdown-item" href="{% url 'br:reports_list' %}">BR reports</a></li>{% endif %}
44
{% if perms.br.change_birthregistration %}<li><a class="dropdown-item" href="{% url 'locations:center_list' %}">BR centers</a></li>{% endif %}
5+
{% if perms.br.change_birthregistration %}<li><a class="dropdown-item" href="{% url 'locations:non_reporting_center_list' %}">Non-reporting BR centers</a></li>{% endif %}
56
<li><a class="dropdown-item" href="{% url 'br:help' %}">BR help</a></li>
67
<div class="dropdown-divider"></div>
78
{% if perms.dr.change_deathreport %}<li><a class="dropdown-item" href="{% url 'dr:dr_report_list' %}">DR reports</a></li>{% endif %}

0 commit comments

Comments
 (0)