diff --git a/locations/api.py b/locations/api.py
index d95a3f6..718c9de 100644
--- a/locations/api.py
+++ b/locations/api.py
@@ -1,5 +1,7 @@
+from django.db.models import Q
from rest_framework import generics
-from locations.models import Location
+
+from locations.models import Location, LocationType
from locations.serializers import LocationSerializer
@@ -41,8 +43,13 @@ def get_queryset(self):
if not type_names:
return queryset.none()
- types = type_names.split(u',')
- queryset = queryset.filter(type__name__in=types)
+ types = [t.lower() for t in type_names.split(u',')]
+ terms = Q()
+ for typ in types:
+ terms |= Q(name__iexact=typ)
+
+ loc_types = LocationType.objects.filter(terms)
+ queryset = queryset.filter(type__in=loc_types)
# filter on parent id
parent_id = self.request.query_params.get(u'parent')
diff --git a/locations/forms.py b/locations/forms.py
index da0fdee..c15bc67 100644
--- a/locations/forms.py
+++ b/locations/forms.py
@@ -1,9 +1,13 @@
+import calendar
+
from django import forms
-from .models import Location
+
+from br.utils import get_report_year_range
+from locations.models import Location
def generate_edit_form(location, data=None):
- state_choices = Location.objects.filter(type__name='state').values_list(
+ state_choices = Location.objects.filter(type__name='State').values_list(
'id', 'name')
def clean_location_field(form, field_name, location_type):
@@ -64,3 +68,22 @@ class CenterCreationForm(forms.Form):
name = forms.CharField()
lga = forms.ModelChoiceField(queryset=Location.objects.filter(
type__name=u'LGA'))
+
+
+def _get_year_choices():
+ choices = [('', '----- Select year -----')]
+ year_range = get_report_year_range()
+ choices.extend([(yr, yr) for yr in range(year_range[0], year_range[1] + 1)])
+ return choices
+
+
+def _get_month_choices():
+ choices = [('', '----- Select month -----')]
+ choices.extend([(i, calendar.month_abbr[i]) for i in range(1, 13)])
+ return choices
+
+
+class NonReportingCentresFilterForm(forms.Form):
+ location = forms.ModelChoiceField(queryset=Location.objects.filter(type__name__in=['State', 'LGA']), required=False)
+ year = forms.ChoiceField(choices=_get_year_choices, required=False)
+ month = forms.ChoiceField(choices=_get_month_choices, required=False)
diff --git a/locations/models.py b/locations/models.py
index cf91ee8..6dc0ee2 100644
--- a/locations/models.py
+++ b/locations/models.py
@@ -195,6 +195,17 @@ def get_stock(self):
stock = None
return stock
+
+ def latest_birth_report_time(self):
+ if self.type.name != 'RC':
+ return None
+
+ try:
+ report = self.birthregistration_records.latest('time')
+
+ return report.time
+ except Exception:
+ return None
def get_locations_graph(reverse=False):
diff --git a/locations/templates/locations/center_edit.html b/locations/templates/locations/center_edit.html
index c6eb2f1..4ab84f0 100644
--- a/locations/templates/locations/center_edit.html
+++ b/locations/templates/locations/center_edit.html
@@ -1,5 +1,5 @@
{% extends 'base/layout.html' %}
-{% load pipeline staticfiles %}
+{% load pipeline staticfiles widget_tweaks %}
{% block stylesheets %}
{% stylesheet 'centers' %}
{% endblock %}
@@ -13,15 +13,15 @@
{{ form.id }}
-
+ {% render_field form.name class='form-control' %}
-
+ {% render_field form.code class='form-control' %}
- {{ form.state }}
+ {% render_field form.state class='form-control' %}
@@ -46,6 +46,7 @@
{% endblock %}
{% block scripts %}
+{{ block.super }}
{% javascript 'centers' %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/locations/urls.py b/locations/urls.py
index 121b708..17bc610 100755
--- a/locations/urls.py
+++ b/locations/urls.py
@@ -1,10 +1,11 @@
#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4
from django.conf.urls import url
-from locations.views import *
+from locations import views
urlpatterns = [
- url(r'^center/new/?$', CenterCreationView.as_view(), name='center_add'),
- url(r'^centers/?$', CenterListView.as_view(), name='center_list'),
- url(r'^center/(?P\d+)/?$', CenterUpdateView.as_view(), name='center_edit'),
+ url(r'^center/new/?$', views.CenterCreationView.as_view(), name='center_add'),
+ url(r'^centers/?$', views.CenterListView.as_view(), name='center_list'),
+ url(r'^non-reporting-centers/?$', views.NonReportingCentresView.as_view(), name='non_reporting_center_list'),
+ url(r'^center/(?P\d+)/?$', views.CenterUpdateView.as_view(), name='center_edit'),
]
diff --git a/locations/views.py b/locations/views.py
index 8127c6f..77ebff5 100644
--- a/locations/views.py
+++ b/locations/views.py
@@ -1,4 +1,6 @@
# vim: ai ts=4 sts=4 et sw=4
+import calendar
+import csv
import json
from django.contrib import messages
@@ -7,17 +9,23 @@
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse, reverse_lazy
from django.db import connection
+from django.db.models import (
+ Case, Count, IntegerField, OuterRef, Subquery, When)
from django.forms.formsets import formset_factory
-from django.http import HttpResponse, HttpResponseRedirect
+from django.http import (
+ HttpResponse, HttpResponseRedirect, StreamingHttpResponse)
from django.shortcuts import get_object_or_404
+from django.utils.timezone import now
from django.views.generic import ListView, FormView, TemplateView
from drf_yasg.utils import swagger_auto_schema
import pandas as pd
from django.conf import settings
+from br.models import BirthRegistration
from locations import raw_queries
-from locations.forms import generate_edit_form, CenterCreationForm
+from locations.forms import (
+ generate_edit_form, CenterCreationForm, NonReportingCentresFilterForm)
from locations.filters import CenterFilterSet
from locations.models import Facility, Location, LocationType
@@ -57,11 +65,14 @@ def get_queryset(self):
class CenterUpdateView(
LoginRequiredMixin, PermissionRequiredMixin, FormView):
+ permission_required = 'locations.edit_location'
template_name = 'locations/center_edit.html'
def get_context_data(self, **kwargs):
context = super(CenterUpdateView, self).get_context_data(**kwargs)
+ self.object = self.get_object()
+
context['page_title'] = 'Edit center: {}'.format(self.object.name)
context[u'location'] = self.object
@@ -77,8 +88,7 @@ def form_valid(self, form):
center = Location.objects.get(pk=form.cleaned_data['id'])
center.name = form.cleaned_data['name']
center.code = form.cleaned_data['code']
- center.parent = Location.objects.get_object_or_404(
- pk=form.cleaned_data['lga'])
+ center.parent = get_object_or_404(Location, pk=form.cleaned_data['lga'])
center.active = form.cleaned_data['active']
center.save()
center.facilities.update(
@@ -87,14 +97,14 @@ def form_valid(self, form):
return HttpResponseRedirect(self.get_success_url())
- def get_form(self, form_class):
- return generate_edit_form(self.object)
+ def get_form(self, form_class=None):
+ return generate_edit_form(self.get_object())
def get_queryset(self):
return Location.objects.filter(type__name='RC')
def get_success_url(self):
- return reverse('center_list')
+ return reverse('locations:center_list')
def post(self, request, *args, **kwargs):
form = generate_edit_form(self.get_object(), request.POST)
@@ -195,3 +205,115 @@ def facilities(request):
facility_dataframe.to_csv(response, encoding='UTF-8', index=False)
return response
+
+
+class CSVBuffer(object):
+ def write(self, value):
+ return value
+
+
+def _get_rows(centres):
+ yield ['Centre', 'Code', 'LGA', 'State', 'Active?', 'Last report date']
+
+ for centre in centres:
+ last_report_time = centre.latest_birth_report_time()
+ yield [
+ centre.name,
+ centre.code,
+ centre.parent.name,
+ centre.parent.parent.name,
+ 'Yes' if centre.active else 'No',
+ last_report_time.strftime('%d-%m-%Y') if last_report_time else 'N/A'
+ ]
+
+
+class NonReportingCentresView(LoginRequiredMixin, ListView):
+ context_object_name = 'centres'
+ page_title = 'Non-reporting centres'
+ paginate_by = settings.PAGE_SIZE
+ template_name = 'locations/non_reporting_centre_list.html'
+
+ def get(self, request, *args, **kwargs):
+ centres = self.get_queryset()
+
+ year = request.GET.get('year') or None
+ month = request.GET.get('month') or None
+
+ if request.GET.get('export'):
+ writer = csv.writer(CSVBuffer())
+ response = StreamingHttpResponse((writer.writerow(row) for row in _get_rows(centres)), content_type='text/csv')
+ filename = 'non-reporting-centres-{}-{}.csv'.format(
+ year,
+ str(month).zfill(2)
+ )
+ response['Content-Disposition'] = 'attachment; filename={}'.format(
+ filename)
+
+ return response
+
+ return super(NonReportingCentresView, self).get(request, *args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = super(NonReportingCentresView, self).get_context_data(**kwargs)
+
+ context['page_title'] = self.page_title
+ context['filter_form'] = self.filter_form
+ context['year'] = self.selected_year
+ context['month'] = self.selected_month or ''
+ context['location'] = self.filter_location.pk if self.filter_location else ''
+ context['caption'] = self.get_header_title()
+
+ return context
+
+ def get_header_title(self):
+ if self.selected_month and self.filter_location:
+ return 'Non-reporting centres for {}, {} {}'.format(
+ self.filter_location.name,
+ calendar.month_name[int(self.selected_month)],
+ self.selected_year
+ )
+ elif self.selected_month:
+ return 'Non-reporting centres for {} {}'.format(
+ calendar.month_name[int(self.selected_month)],
+ self.selected_year
+ )
+ elif self.filter_location:
+ return 'Non-reporting centres for {}, {}'.format(
+ self.filter_location.name,
+ self.selected_year
+ )
+ else:
+ return 'Non-reporting centres for {}'.format(
+ self.selected_year
+ )
+
+ def get_queryset(self):
+ self.filter_form = NonReportingCentresFilterForm(self.request.GET)
+ if self.filter_form.is_valid():
+ filter_data = self.filter_form.cleaned_data
+ else:
+ filter_data = {}
+
+ filter_kwargs = {}
+ alt_filter_kwargs = {'type__name': 'RC'}
+ self.selected_year = filter_data.get('year') or now().year
+ filter_kwargs['birthregistration_records__time__year'] = int(self.selected_year)
+ self.selected_month = filter_data.get('month')
+ if self.selected_month:
+ filter_kwargs['birthregistration_records__time__month'] = int(self.selected_month)
+
+ self.filter_location = filter_data.get('location')
+ if self.filter_location:
+ alt_filter_kwargs['lft__gt'] = self.filter_location.lft
+ alt_filter_kwargs['rgt__lt'] = self.filter_location.rgt
+
+ if len(filter_kwargs) == 0:
+ centres = Location.objects.none()
+ else:
+ centres = Location.objects.filter(
+ **alt_filter_kwargs
+ ).annotate(
+ cnt=Count(Case(When(then=1, **filter_kwargs)))
+ ).filter(cnt=0)
+
+ return centres
diff --git a/requirements/base.txt b/requirements/base.txt
index 95dc304..71f092c 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -14,6 +14,7 @@ django-redis==4.7.0
django-selectable==1.2.1
django-sendfile==0.3.11
django-subdomains==2.1.0
+django-widget-tweaks==1.4.8
djangorestframework==3.9.4
drf-yasg==1.17.1
fuzzywuzzy==0.12.0
diff --git a/unicefng/settings.py b/unicefng/settings.py
index 0a73781..49ef6f8 100644
--- a/unicefng/settings.py
+++ b/unicefng/settings.py
@@ -219,6 +219,7 @@
"pipeline",
"bootstrap_pagination",
"drf_yasg",
+ "widget_tweaks",
# RapidSMS
"rapidsms",
"rapidsms.backends.database",
diff --git a/unicefng/templates/common/usermenu.html b/unicefng/templates/common/usermenu.html
index d76c22c..95c7614 100644
--- a/unicefng/templates/common/usermenu.html
+++ b/unicefng/templates/common/usermenu.html
@@ -2,6 +2,7 @@
{% if user.is_authenticated %}
{% if perms.br.change_birthregistration %}BR reports{% endif %}
{% if perms.br.change_birthregistration %}BR centers{% endif %}
+{% if perms.br.change_birthregistration %}Non-reporting BR centers{% endif %}
BR help
{% if perms.dr.change_deathreport %}DR reports{% endif %}