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 %}