diff --git a/central/templates/delegates_individual.html b/central/templates/delegates_individual.html index 2c9e529..f09cda8 100644 --- a/central/templates/delegates_individual.html +++ b/central/templates/delegates_individual.html @@ -14,7 +14,18 @@ {% endblock navbar %} {% block content %} -
+ +
Create Delegate @@ -47,20 +58,20 @@
- +
- +
+
+
+
+ + +
+ +
+
+
+
+ Report output +

+ + +      + + +

+
+

+
+ + +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/report_manager/tests.py b/report_manager/tests.py index b7252be..d4feb44 100644 --- a/report_manager/tests.py +++ b/report_manager/tests.py @@ -1,8 +1,116 @@ # -*- encoding: utf-8 -*- import unittest -from report_manager import central_report_gen +from report_manager import central_report_gen, owner_report_gen as owner_rg from map.models import Place + +class OwnerReportTests(unittest.TestCase): + + def test_us011_scenario1(self): + ''' Dado que soy un propietario + Cuando seleccione un rango de fechas en el que no hay registros de eventos ocurridos en alguno de mis inmuebles + Entonces no debe generar un reporte y debe informar que no hay registro disponibles según los datos ingresados''' + + self.start_date = "2013/01/01" + self.end_date = "2013/01/10" + self.place_pk = 5 + + # Validating if start_date is valid + self.assertTrue(owner_rg.is_valid_format(self.start_date)) + + # Validating if end_date is valid + self.assertTrue(owner_rg.is_valid_format(self.end_date)) + + # Getting the place based on its pk + place = owner_rg.get_place(self.place_pk) + + # Getting events in the place based on a date range + events = owner_rg.get_events_in_place(place, self.start_date, self.end_date) + + # No events found! + if not events or len(events) == 0: + self.assertTrue(True) + else: + self.assertTrue(False) + + def test_us011_scenario2(self): + ''' Dado que soy un propietario + Cuando seleccione un rango de fechas y hay eventos para el inmueble actual. + Entonces se genera un reporte con los eventos ocurridos en mi inmueble detallando principalmente el activo afectado, + cambio de estado y el momento en el que ocurrio el evento.''' + + self.start_date = "2013/01/01" + self.end_date = "2015/12/31" + self.place_pk = 5 + + # Validating if start_date is valid + self.assertTrue(owner_rg.is_valid_format(self.start_date)) + + # Validating if end_date is valid + self.assertTrue(owner_rg.is_valid_format(self.end_date)) + + # Getting the place based on its pk + place = owner_rg.get_place(self.place_pk) + + # Getting events in the place based on a date range + events = owner_rg.get_events_in_place(place, self.start_date, self.end_date) + + # Events do found! + if events and len(events) > 0: + self.assertTrue(True) + else: + self.assertTrue(False) + + def test_us011_scenario3(self): + ''' Dado que soy un propietario + Cuando seleccione unicamente la fecha inicial del rango de fechas y pulse la opción de generar reporte de eventos + Entonces no se genera el reporte y se informa al usuario que hace falta ingresar la fecha final y el inmueble para + la generacion.''' + + self.start_date = "2015/01/01" + self.end_date = "" + + # Validating if start_date is valid + self.assertTrue(owner_rg.is_valid_format(self.start_date)) + + # Validating if end_date is valid + self.assertFalse(owner_rg.is_valid_format(self.end_date)) + + def test_us011_scenario4(self): + ''' Dado que soy un propietario + Cuando seleccione unicamente la fecha final del rango de fechas y pulse la opción de generar reporte de eventos + Entonces no se genera el reporte y se informa al usuario que hace falta ingresar la fecha inicial y el inmueble + para la generacion.''' + + self.start_date = "" + self.end_date = "2015/01/20" + + # Validating if start_date is valid + self.assertFalse(owner_rg.is_valid_format(self.start_date)) + + # Validating if end_date is valid + self.assertTrue(owner_rg.is_valid_format(self.end_date)) + + def test_us011_scenario5(self): + ''' Dado que soy un propietario + Cuando ingreso el rango de fechas y el inmueble, pero la fecha final es menor que la fecha inicial + Entonces no se genera el reporte y se informa al usuario que la fecha final es menor que la fecha inicial y + por ende no se puede especificar el rango de fechas para realizar la generacion.''' + + self.start_date = "2015/01/01" + self.end_date = "2015/01/20" + + # Validating if start_date is valid + self.assertTrue(owner_rg.is_valid_format(self.start_date)) + + # Validating if end_date is valid + self.assertTrue(owner_rg.is_valid_format(self.end_date)) + + # Validating if end date is greater than start date + self.assertTrue(owner_rg.is_end_date_greater(self.start_date, self.end_date)) + + + class CentralReportTests(unittest.TestCase): "HU: Generar reporte mensual de incidencias. Narrativa: Como usuario de la central, quiero generar el reporte de incidencias ocurridas en el mes de los inmuebles que administro, organizados por inmueble y cantidad de incidencias generadas." diff --git a/report_manager/urls.py b/report_manager/urls.py new file mode 100644 index 0000000..780b0aa --- /dev/null +++ b/report_manager/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import patterns, include, url + + +urlpatterns = patterns('report_manager.views', + url(r'^(?P\d+)/$', 'home', name='report_home'), + url(r'^(?P\d+)/events/$', 'events_in_date_range', name='report_events_in_date_range'), +) diff --git a/report_manager/views.py b/report_manager/views.py index 91ea44a..cc1d43e 100644 --- a/report_manager/views.py +++ b/report_manager/views.py @@ -1,3 +1,128 @@ -from django.shortcuts import render +from django.shortcuts import render, redirect, get_object_or_404 +from django.contrib.messages import error +from xhtml2pdf import pisa +from django.http import HttpResponse +from django.template import RequestContext +from django.template.loader import render_to_string -# Create your views here. +from map.models import Place +from event_manager.models import Event + +import datetime, calendar +import cStringIO as StringIO +import cgi + +def home(request, place_pk): + # Getting the place + place = get_object_or_404(Place, pk=place_pk) + + context = {'place': place} + return render(request, 'form.html', context) + + +def generate_pdf(html): + # Generic function to generate a pdf file + result = StringIO.StringIO() + pdf = pisa.pisaDocument(StringIO.StringIO(html.encode("UTF-8")), result, link_callback=fetch_resources) + if not pdf.err: + return HttpResponse(result.getvalue(), content_type='application/pdf') + return HttpResponse('PDF document cannot be generated: %s' % cgi.escape(html)) + + +def fetch_resources(uri, rel): + import os.path + from django.conf import settings + path = os.path.join( + "index/static", + uri.replace(settings.STATIC_URL, "")) + + return path + + +def events_in_date_range(request, place_pk): + # Scenario 1 is validated displaying a message when have no results! + # Scenario 2 is validated displaying all events in a place between a date range! (Ideal scenario) + # Scenario 3 and 4 are validated via js, indicating dates are required fields! + + start_year = 0 + #math.fmod() + end_year = 0 + if request.method == 'POST': + # Validating if dates are in valid format! + try: + start_year = datetime.datetime.strptime(request.POST['start_date'], "%Y/%m/%d").year + except ValueError: + error(request, "The start date must be with valid format.") + return redirect('report_home', place_pk=place_pk) + + try: + end_year = datetime.datetime.strptime(request.POST['end_date'], "%Y/%m/%d").year + except ValueError: + error(request, "The end date must be with valid format.") + return redirect('report_home', place_pk=place_pk) + + start_date = request.POST['start_date'] + end_date = request.POST['end_date'] + as_pdf = request.POST['output'] != 'html' + + else: + return redirect('report_home', place_pk=place_pk) + + # Scenario 5 is validated redirecting to form page and indicating that + # end date must be greater than start date + if datetime.datetime.strptime(start_date, "%Y/%m/%d") > \ + datetime.datetime.strptime(end_date, "%Y/%m/%d"): + error(request, "The end date must be greater than the start date.") + return redirect('report_home', place_pk=place_pk) + + + if not start_year > 1900: + error(request, "The year in the start date must be greater than 1900.") + return redirect('report_home', place_pk=place_pk) + + if not end_year > 1900: + error(request, "The year in the end date must be greater than 1900.") + return redirect('report_home', place_pk=place_pk) + + + start_month = datetime.datetime.strptime(start_date, "%Y/%m/%d").month + end_month = datetime.datetime.strptime(end_date, "%Y/%m/%d").month + + if not start_month in range(1, 13): + error(request, "The month in the start date must be between 1 and 12.") + return redirect('report_home', place_pk=place_pk) + + if not end_month in range(1, 13): + error(request, "The month in the end date must be between 1 and 12.") + return redirect('report_home', place_pk=place_pk) + + + start_day = datetime.datetime.strptime(start_date, "%Y/%m/%d").day + end_day = datetime.datetime.strptime(end_date, "%Y/%m/%d").day + + if start_day <= 0 or start_day >= 30: + start_day = 1 + + if end_day <= 0 or end_day >= 30: + end_day = calendar.monthrange(end_year, end_month)[1] + + + # Getting the place to filter events + place = get_object_or_404(Place, pk=place_pk) + + # Filtering events in a place and a in a date range + events = Event.objects.filter(sensor__floor__place=place)\ + .filter(timestamp__gt=datetime.date(start_year, start_month, start_day), + timestamp__lt=datetime.date(end_year, end_month, end_day)) + + context = {'place': place, 'events': events, + 'start_date': start_date, 'end_date': end_date} + + # Generating pdf file + if as_pdf and events: + html = render_to_string('events_in_date_range_pdf.html', {'pagesize':'A4', 'place': place, 'events': events, + 'start_date': start_date, 'end_date': end_date}, context_instance=RequestContext(request)) + return generate_pdf(html) + + # Displaying report in HTML + return render(request, 'events_in_date_range.html', context) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 0cfc908..e7466f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,12 @@ django-registration==1.0 django-toolbelt==0.0.1 djangorestframework==3.0.5 gunicorn==19.2.1 +html5lib==0.999 nose==1.3.4 +pisa==3.0.33 psycopg2==2.6 +PyPDF2==1.24 static3==0.5.1 reportlab==3.1.44 +six==1.9.0 +xhtml2pdf==0.0.6