From e9bab3610ef2c064cea47b92f23b9f4ab7d88a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ses=C3=B3stris=20Vieira?= Date: Mon, 4 Apr 2022 09:50:17 -0300 Subject: [PATCH] =?UTF-8?q?Finalizando=20a=20migra=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sigi/apps/casas/templates/casas/painel.html | 5 +- .../casas/resumo_carteira_snippet.html | 67 +- sigi/apps/casas/urls.py | 5 +- sigi/apps/casas/views.py | 464 +++++----- .../templates/convenios/tabela_regiao.html | 178 +--- sigi/apps/convenios/urls.py | 25 +- sigi/apps/convenios/views.py | 225 ++--- .../eventos/templates/eventos/calendario.html | 58 +- sigi/apps/eventos/urls.py | 42 +- sigi/apps/eventos/views.py | 831 +++++++++--------- sigi/apps/home/static/home/css/openmap.css | 44 + .../home/dashboard/gerentes_snippet.html | 35 + .../home/dashboard/resumo_convenios.html | 57 ++ .../templates/home/dashboard/resumo_seit.html | 41 + .../apps/home/templates/home/lista_casas.html | 60 ++ sigi/apps/home/templates/home/mapfilter.html | 173 ++++ sigi/apps/home/templates/home/openmap.html | 375 ++++++++ .../home/templates/home/openmapdetail.html | 23 + .../home/templates/home/sem_convenio.html | 95 +- .../home/templatetags/smart_pagination.py | 34 +- sigi/apps/home/urls.py | 43 +- sigi/apps/home/views.py | 494 +++++++---- sigi/apps/ocorrencias/admin.py | 52 +- sigi/apps/ocorrencias/filters.py | 14 +- sigi/apps/ocorrencias/forms.py | 49 +- ...ve_comentario_encaminhar_setor_and_more.py | 21 + ...0008_remove_categoria_setor_responsavel.py | 17 + sigi/apps/ocorrencias/models.py | 22 - .../templates/ocorrencias/painel-old.html | 198 +++++ .../templates/ocorrencias/painel.html | 399 +++++---- sigi/apps/ocorrencias/urls.py | 49 +- sigi/apps/ocorrencias/views.py | 143 +-- .../parlamentar/change_form.html | 1 - sigi/apps/servidores/models.py | 7 + sigi/apps/utils/__init__.py | 3 +- sigi/settings/base.py | 7 + sigi/settings/menu_conf.yaml | 2 +- sigi/static/css/dashboard.css | 37 + sigi/static/img/ilsombra.png | Bin 0 -> 2121 bytes sigi/static/js/dashboard.js | 72 ++ sigi/templates/admin/base_site.html | 29 + sigi/templates/admin/change_list.html | 152 ++-- sigi/templates/material/admin/index.html | 21 +- sigi/templates/material/admin/side_nav.html | 44 +- .../material/admin/user_picture.html | 21 + sigi/templates/pdf/base_report.html | 11 +- sigi/templates/sigi/snippets/base_card.html | 27 + .../sigi/snippets/base_card_chart.html | 18 + .../sigi/snippets/base_card_text.html | 8 + sigi/templates/sigi/snippets/dashboard.html | 57 +- sigi/urls.py | 7 +- sigiStatic/js/dashboard.js | 63 -- templates/snippets/modules/resumo_seit.html | 38 - 53 files changed, 3138 insertions(+), 1825 deletions(-) create mode 100644 sigi/apps/home/static/home/css/openmap.css create mode 100644 sigi/apps/home/templates/home/dashboard/gerentes_snippet.html create mode 100644 sigi/apps/home/templates/home/dashboard/resumo_convenios.html create mode 100644 sigi/apps/home/templates/home/dashboard/resumo_seit.html create mode 100644 sigi/apps/home/templates/home/lista_casas.html create mode 100644 sigi/apps/home/templates/home/mapfilter.html create mode 100644 sigi/apps/home/templates/home/openmap.html create mode 100644 sigi/apps/home/templates/home/openmapdetail.html create mode 100644 sigi/apps/ocorrencias/migrations/0007_remove_comentario_encaminhar_setor_and_more.py create mode 100644 sigi/apps/ocorrencias/migrations/0008_remove_categoria_setor_responsavel.py create mode 100644 sigi/apps/ocorrencias/templates/ocorrencias/painel-old.html delete mode 100644 sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/change_form.html create mode 100644 sigi/static/css/dashboard.css create mode 100644 sigi/static/img/ilsombra.png create mode 100644 sigi/static/js/dashboard.js create mode 100644 sigi/templates/admin/base_site.html create mode 100644 sigi/templates/material/admin/user_picture.html create mode 100644 sigi/templates/sigi/snippets/base_card.html create mode 100644 sigi/templates/sigi/snippets/base_card_chart.html create mode 100644 sigi/templates/sigi/snippets/base_card_text.html delete mode 100644 sigiStatic/js/dashboard.js delete mode 100644 templates/snippets/modules/resumo_seit.html diff --git a/sigi/apps/casas/templates/casas/painel.html b/sigi/apps/casas/templates/casas/painel.html index 4b9441c..cefb501 100644 --- a/sigi/apps/casas/templates/casas/painel.html +++ b/sigi/apps/casas/templates/casas/painel.html @@ -22,19 +22,18 @@ {% block extrahead %}{{ block.super }} +{% endblock %} + +{% comment %} {% block extrastyle %} {{ block.super }} {% endblock %} @@ -41,3 +88,4 @@ {% block content %} {% include "eventos/calendario_snippet.html" %} {% endblock %} +{% endcomment %} \ No newline at end of file diff --git a/sigi/apps/eventos/urls.py b/sigi/apps/eventos/urls.py index 2d0a8f3..5e4494d 100644 --- a/sigi/apps/eventos/urls.py +++ b/sigi/apps/eventos/urls.py @@ -1,22 +1,28 @@ -# coding: utf-8 -from django.conf.urls import patterns, url +from django.urls import path, include +from sigi.apps.eventos import views +urlpatterns = [ + path('calendario/', views.calendario, name='eventos-calendario'), +] -urlpatterns = patterns( - 'sigi.apps.eventos.views', - # Painel de ocorrencias - url(r'^calendario/$', 'calendario', name='eventos-calendario'), - url(r'^alocacaoequipe/$', 'alocacao_equipe', name='eventos-alocacaoequipe'), - # Carrinho - url(r'^evento/carrinho/$', 'visualizar_carrinho', - name='visualizar-carrinho-evento'), - url(r'^evento/carrinho/excluir_carrinho/$', 'excluir_carrinho', - name='excluir-carrinho-evento'), # Error - url(r'^evento/carrinho/deleta_itens_carrinho$', 'deleta_itens_carrinho', - name='deleta-itens-carrinho-evento'), # Error - url(r'^evento/csv/$', 'export_csv', name='evento-export-csv'), # Error - url(r'^evento/(?P\w+)/declaracao/$', 'declaracao', - name='evento-declaracao'), +# from django.conf.urls import patterns, url -) +# urlpatterns = patterns( +# 'sigi.apps.eventos.views', +# # Painel de ocorrencias +# url(r'^calendario/$', 'calendario', name='eventos-calendario'), +# url(r'^alocacaoequipe/$', 'alocacao_equipe', name='eventos-alocacaoequipe'), +# # Carrinho +# url(r'^evento/carrinho/$', 'visualizar_carrinho', +# name='visualizar-carrinho-evento'), +# url(r'^evento/carrinho/excluir_carrinho/$', 'excluir_carrinho', +# name='excluir-carrinho-evento'), # Error +# url(r'^evento/carrinho/deleta_itens_carrinho$', 'deleta_itens_carrinho', +# name='deleta-itens-carrinho-evento'), # Error +# url(r'^evento/csv/$', 'export_csv', name='evento-export-csv'), # Error +# url(r'^evento/(?P\w+)/declaracao/$', 'declaracao', +# name='evento-declaracao'), + + +# ) diff --git a/sigi/apps/eventos/views.py b/sigi/apps/eventos/views.py index d22cf90..9701d00 100644 --- a/sigi/apps/eventos/views.py +++ b/sigi/apps/eventos/views.py @@ -1,41 +1,12 @@ -# -*- coding: utf-8 -*- -# -# sigi.apps.eventos.views -# -# Copyright (C) 2015 Interlegis -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - import calendar import datetime import locale -import csv -from django import template -from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden -from django.core.paginator import Paginator, InvalidPage, EmptyPage -from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.shortcuts import get_object_or_404, render -from django.utils import translation -from django.utils.translation import ungettext, gettext as _ -from django.http.response import JsonResponse, HttpResponse -from django.template import Template, Context +from django.shortcuts import render +from django.utils.translation import to_locale, get_language, gettext as _ from sigi.apps.eventos.models import Evento, Equipe, Convite, Modulo from sigi.apps.eventos.forms import SelecionaModeloForm from sigi.apps.servidores.models import Servidor -from sigi.shortcuts import render_to_pdf, pdf_renderer @login_required def calendario(request): @@ -43,388 +14,422 @@ def calendario(request): ano_pesquisa = int(request.GET.get('ano', datetime.date.today().year)) formato = request.GET.get('fmt', 'html') - dia1 = datetime.date(ano_pesquisa, mes_pesquisa, 1) - mes_anterior = dia1 - datetime.timedelta(days=1) - mes_seguinte = dia1.replace(day=28) + datetime.timedelta(days=4) # Ugly hack - mes_seguinte = mes_seguinte.replace(day=1) - - data = {'mes_pesquisa': mes_pesquisa, 'ano_pesquisa': ano_pesquisa} - - if Evento.objects.filter(data_inicio__year=mes_anterior.year, - data_inicio__month=mes_anterior.month).exists(): - data['prev_button'] = {'mes': mes_anterior.month, 'ano': mes_anterior.year } - - if Evento.objects.filter(data_inicio__year=mes_seguinte.year, - data_inicio__month=mes_seguinte.month).exists(): - data['next_button'] = {'mes': mes_seguinte.month, 'ano': mes_seguinte.year } - - c = calendar.Calendar(6) - dates = reduce(lambda x,y: x+y, c.monthdatescalendar(ano_pesquisa, mes_pesquisa)) - - eventos = [] - - for evento in Evento.objects.filter(data_inicio__year=ano_pesquisa, - data_inicio__month=mes_pesquisa).order_by('data_inicio'): - start = dates.index(evento.data_inicio.date()) - if not evento.data_termino.date() in dates: - lastday = dates[-1] - while lastday < evento.data_termino.date(): - lastday = lastday + datetime.timedelta(days=1) - dates.append(lastday) - eventos.append({'evento': evento, 'start': start}) - - # Calcula a distância dos eventos para as bordas do calendário - for evento in eventos: - end = dates.index(evento['evento'].data_termino.date()) - evento['duration'] = end-evento['start']+1 - evento['close'] = len(dates)-end-1 - - # Agrupa os eventos em linhas para melhorar a visualização - linhas = [] - - for evento in eventos: - encaixado = False - for linha in linhas: - sobrepoe = False - for e in linha: - if (((evento['evento'].data_inicio.date() >= e['evento'].data_inicio.date()) and - (evento['evento'].data_inicio.date() <= e['evento'].data_termino.date())) or - ((evento['evento'].data_termino.date() >= e['evento'].data_inicio.date()) and - (evento['evento'].data_termino.date() <= e['evento'].data_termino.date()))): - sobrepoe = True - break - if not sobrepoe: - # Adiona o evento em uma linha que ele não sobrepoe nenhum outro - linha.append(evento) - encaixado = True - break - if not encaixado: - # Adiciona uma nova linha porque este evento não se encaixa em nenhuma existente - linhas.append([evento]) - - # Recalcula as distâncias dos eventos por linha para encaixar no calendário - for linha in linhas: - anterior = None - for evento in linha: - if anterior is None: - anterior = evento - continue - anterior['close'] = (evento['evento'].data_inicio.date() - anterior['evento'].data_termino.date()).days-1 - evento['start'] = 0 - anterior = evento - - data['dates'] = dates - data['eventos'] = eventos - data['linhas'] = linhas - - if formato == 'pdf': - return render_to_pdf('eventos/calendario_pdf.html', data ) - - return render(request, 'eventos/calendario.html', data) - -@login_required -def alocacao_equipe(request): - ano_pesquisa = int(request.GET.get('ano', datetime.date.today().year)) - formato = request.GET.get('fmt', 'html') - - data = {'ano_pesquisa': ano_pesquisa} - - if Evento.objects.filter(data_inicio__year=ano_pesquisa-1).exists(): - data['prev_button'] = {'ano': ano_pesquisa - 1 } - - if Evento.objects.filter(data_inicio__year=ano_pesquisa+1).exists(): - data['next_button'] = {'ano': ano_pesquisa + 1 } - - dados = [] - - for evento in Evento.objects.filter(data_inicio__year=ano_pesquisa).exclude(status='C').prefetch_related('equipe_set'): - for p in evento.equipe_set.all(): - registro = None - for r in dados: - if r[0] == p.membro.pk: - registro = r - break - if not registro: - registro = [p.membro.pk, p.membro.nome_completo, [{'dias': 0, 'eventos': 0} for x in range(1,13)]] - dados.append(registro) - - registro[2][evento.data_inicio.month-1]['dias'] += (evento.data_termino - evento.data_inicio).days + 1 - registro[2][evento.data_inicio.month-1]['eventos'] += 1 - - dados.sort(lambda x, y: cmp(x[1], y[1])) - - lang = (translation.to_locale(translation.get_language())+'.utf8').encode() + meses = {} + lang = to_locale(get_language())+'.UTF-8' locale.setlocale(locale.LC_ALL, lang) - meses = [calendar.month_name[m] for m in range(1,13)] - - linhas = [[_("Servidor")] + meses + ['total']] - - for r in dados: - r[2].append(reduce(lambda x,y:{'dias': x['dias'] + y['dias'], - 'eventos': x['eventos'] + y['eventos']}, r[2])) - linhas.append([r[1]] + - [_(ungettext("%(dias)s dia", "%(dias)s dias", d['dias']) + " em " + - ungettext("%(eventos)s evento", "%(eventos)s eventos", d['eventos']) - ) % d if d['dias'] > 0 or d['eventos'] > 0 else '' for d in r[2]]) - -# for registro in Servidor.objects.filter(equipe_evento__evento__data_inicio__year=ano_pesquisa).exclude(equipe_evento__evento__status='C').distinct(): -# dados = [{'dias': 0, 'eventos': 0} for x in range(1,13)] -# for part in registro.equipe_evento.filter(evento__data_inicio__year=ano_pesquisa).exclude(evento__status='C'): -# dados[part.evento.data_inicio.month-1]['dias'] += (part.evento.data_termino - -# part.evento.data_inicio).days + 1 -# dados[part.evento.data_inicio.month-1]['eventos'] += 1 -# dados.append([registro.nome_completo] + [_(ungettext("%(dias)s dia", "%(dias)s dias", d['dias']) + " em " + ungettext("%(eventos)s evento", "%(eventos)s eventos", d['eventos'])) % d if d['dias'] > 0 or d['eventos'] > 0 else '' for d in dados]) - - data['linhas'] = linhas - - if formato == 'pdf': - return render_to_pdf('eventos/alocacao_equipe_pdf.html', data) - elif formato == 'csv': - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="alocacao_equipe_%s.csv"' % (ano_pesquisa,) - writer = csv.writer(response) - asc_list = [[s.encode('utf-8') if isinstance(s, unicode) else s for s in l] for l in linhas] - writer.writerows(asc_list) - return response - elif formato == 'json': - result = {'ano': ano_pesquisa, - 'equipe': [{'pk': d[0], - 'nome_completo': d[1], - 'meses': {m[0]: m[1] for m in zip(meses+['total'], d[2])} - } for d in dados]} - return JsonResponse(result) - - return render(request, 'eventos/alocacao_equipe.html', data) - -# Views e functions para carrinho de exportação - -def query_ordena(qs, o): - from sigi.apps.eventos.admin import EventoAdmin - list_display = EventoAdmin.list_display - order_fields = [] - - for order_number in o.split('.'): - order_number = int(order_number) - order = '' - if order_number != abs(order_number): - order_number = abs(order_number) - order = '-' - order_fields.append(order + list_display[order_number - 1]) - qs = qs.order_by(*order_fields) - return qs - -def get_for_qs(get, qs): - kwargs = {} - for k, v in get.iteritems(): - if str(k) not in ('page', 'pop', 'q', '_popup', 'o', 'ot'): - kwargs[str(k)] = v - qs = qs.filter(**kwargs) - if 'o' in get: - qs = query_ordena(qs, get['o']) - return qs - -def carrinhoOrGet_for_qs(request): - if 'carrinho_eventos' in request.session: - ids = request.session['carrinho_eventos'] - qs = Evento.objects.filter(pk__in=ids) - else: - qs = Evento.objects.all() - if request.GET: - qs = get_for_qs(request.GET, qs) - return qs - -def adicionar_eventos_carrinho(request, queryset=None, id=None): - if request.method == 'POST': - ids_selecionados = request.POST.getlist('_selected_action') - if 'carrinho_eventos' not in request.session: - request.session['carrinho_eventos'] = ids_selecionados - else: - lista = request.session['carrinho_eventos'] - # Verifica se id já não está adicionado - for id in ids_selecionados: - if id not in lista: - lista.append(id) - request.session['carrinho_eventos'] = lista - -@login_required -def visualizar_carrinho(request): - qs = carrinhoOrGet_for_qs(request) - paginator = Paginator(qs, 100) - - try: - page = int(request.GET.get('page', '1')) - except ValueError: - page = 1 - - try: - paginas = paginator.page(page) - except (EmptyPage, InvalidPage): - paginas = paginator.page(paginator.num_pages) - - carrinhoIsEmpty = not('carrinho_eventos' in request.session) - - return render( - request, - 'eventos/carrinho.html', - { - 'carIsEmpty': carrinhoIsEmpty, - 'paginas': paginas, - 'query_str': '?' + request.META['QUERY_STRING'] - } - ) - -@login_required -def excluir_carrinho(request): - if 'carrinho_eventos' in request.session: - del request.session['carrinho_eventos'] - messages.info(request, 'O carrinho foi esvaziado') - return HttpResponseRedirect('../../') -@login_required -def deleta_itens_carrinho(request): - if request.method == 'POST': - ids_selecionados = request.POST.getlist('_selected_action') - removed = 0 - if 'carrinho_eventos' in request.session: - lista = request.session['carrinho_eventos'] - for item in ids_selecionados: - lista.remove(item) - removed += 1 - if lista: - request.session['carrinho_eventos'] = lista - else: - del lista - del request.session['carrinho_eventos'] - messages.info(request, "{0} itens removidos do carrinho".format(removed)) - return HttpResponseRedirect('.') - -@login_required -def export_csv(request): - def rm_rows(lista,reg): - for a in lista: - if a in lista: - reg.pop(a,None) - else: - pass - - def serialize(r, field): - value = (getattr(r, 'get_{0}_display'.format(field.name), None) or - getattr(r, field.name, "")) - if callable(value): - value = value() - if value is None: - value = "" - return unicode(value).encode('utf8') - - eventos = carrinhoOrGet_for_qs(request) - eventos.select_related('equipe', 'convite') - - if not eventos: - messages.info(request, _("Nenhum evento a exportar")) - return HttpResponseRedirect('../') - - max_equipe = max([e.equipe_set.count() for e in eventos]) - - mun_casa = 'Município da Casa Anfitriã'.encode('utf8') - uf_casa = 'UF da Casa Anfitriã'.encode('utf8') - reg_casa = 'Região da Casa Anfitriã'.encode('utf8') - - head = [f.verbose_name.encode('utf8') for f in Evento._meta.fields] - head.extend([mun_casa, uf_casa, reg_casa]) - head.extend([f.verbose_name.encode('utf8')+"_{0}".format(i+1) - for i in range(max_equipe) for f in Equipe._meta.fields - if f.name not in ('id', 'evento')]) - head.extend([f.verbose_name.encode('utf8') for f in Convite._meta.fields - if f.name not in ('id', 'evento')]) - head.extend([f.verbose_name.encode('utf8') for f in Modulo._meta.fields - if f.name not in ('id', 'evento')]) - - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename=eventos.csv' - rm_list = ['Descrição do evento', 'Local do evento', 'Público alvo', 'Motivo do cancelamento', 'Descrição do módulo'] - - for a in head: - if 'Observações_' in a: - rm_list.append(a) - - for a in rm_list: - if a in head: - head.remove(a) + for ano, mes in Evento.objects.values_list( + 'data_inicio__year', 'data_inicio__month').order_by( + 'data_inicio__year', 'data_inicio__month').distinct( + 'data_inicio__year', 'data_inicio__month'): + if ano in meses: + meses[ano][mes] = calendar.month_name[mes] else: - pass - writer = csv.DictWriter(response, fieldnames=head) - writer.writeheader() - - for evento in eventos: - reg = {f.verbose_name.encode('utf8'): serialize(evento, f) - for f in Evento._meta.fields} - if evento.casa_anfitria is None: - reg[mun_casa] = "" - reg[uf_casa] = "" - reg[reg_casa] = "" - else: - reg[mun_casa] = evento.casa_anfitria.municipio.nome.encode('utf8') - reg[uf_casa] = evento.casa_anfitria.municipio.uf.sigla.\ - encode('utf8') - reg[reg_casa] = evento.casa_anfitria.municipio.uf.\ - get_regiao_display().encode('utf8') - - idx = 1 - for membro in evento.equipe_set.all(): - reg.update( - { - "{0}_{1}".format(f.verbose_name.encode('utf8'), idx): - serialize(membro, f) for f in Equipe._meta.fields - if f.name not in ('id', 'evento') - } - ) - idx += 1 - for convite in evento.convite_set.all(): - reg.update( - {f.verbose_name.encode('utf8'): serialize(convite, f) - for f in Convite._meta.fields - if f.name not in ('id', 'evento')} - ) - rm_rows(rm_list,reg) - writer.writerow(reg) - - if evento.convite_set.count() == 0: - rm_rows(rm_list,reg) - - writer.writerow(reg) - - return response - -@login_required -def declaracao(request, id): - if request.method == 'POST': - form = SelecionaModeloForm(request.POST) - if form.is_valid(): - evento = get_object_or_404(Evento, id=id) - modelo = form.cleaned_data['modelo'] - template_string = ( - """ - {% extends "eventos/declaracao_pdf.html" %} - {% block text_body %}""" + - modelo.texto + """ - {% endblock %} - """ - ) - context = Context( - {'pagesize': modelo.formato, - 'pagemargin': modelo.margem, - 'evento': evento, - 'data': datetime.date.today(), - } - ) - template = Template(template_string) - # return HttpResponse(template.render(context)) - return pdf_renderer(template, context, 'declaracao.pdf') - else: - form = SelecionaModeloForm() - - return render( - request, - 'eventos/seleciona_modelo.html', - {'form': form, 'evento_id': id} - ) \ No newline at end of file + meses[ano] = {mes: calendar.month_name[mes]} + + context = { + 'ano_pesquisa': ano_pesquisa, + 'mes_pesquisa': mes_pesquisa, + 'meses': meses, + 'eventos': Evento.objects.filter( + data_inicio__year=ano_pesquisa, + data_inicio__month=mes_pesquisa + ) + } + + return render(request, 'eventos/calendario.html', context) + + + + +# @login_required +# def calendario(request): +# mes_pesquisa = int(request.GET.get('mes', datetime.date.today().month)) +# ano_pesquisa = int(request.GET.get('ano', datetime.date.today().year)) +# formato = request.GET.get('fmt', 'html') + +# dia1 = datetime.date(ano_pesquisa, mes_pesquisa, 1) +# mes_anterior = dia1 - datetime.timedelta(days=1) +# mes_seguinte = dia1.replace(day=28) + datetime.timedelta(days=4) # Ugly hack +# mes_seguinte = mes_seguinte.replace(day=1) + +# data = {'mes_pesquisa': mes_pesquisa, 'ano_pesquisa': ano_pesquisa} + +# if Evento.objects.filter(data_inicio__year=mes_anterior.year, +# data_inicio__month=mes_anterior.month).exists(): +# data['prev_button'] = {'mes': mes_anterior.month, 'ano': mes_anterior.year } + +# if Evento.objects.filter(data_inicio__year=mes_seguinte.year, +# data_inicio__month=mes_seguinte.month).exists(): +# data['next_button'] = {'mes': mes_seguinte.month, 'ano': mes_seguinte.year } + +# c = calendar.Calendar(6) +# dates = reduce(lambda x,y: x+y, c.monthdatescalendar(ano_pesquisa, mes_pesquisa)) + +# eventos = [] + +# for evento in Evento.objects.filter(data_inicio__year=ano_pesquisa, +# data_inicio__month=mes_pesquisa).order_by('data_inicio'): +# start = dates.index(evento.data_inicio.date()) +# if not evento.data_termino.date() in dates: +# lastday = dates[-1] +# while lastday < evento.data_termino.date(): +# lastday = lastday + datetime.timedelta(days=1) +# dates.append(lastday) +# eventos.append({'evento': evento, 'start': start}) + +# # Calcula a distância dos eventos para as bordas do calendário +# for evento in eventos: +# end = dates.index(evento['evento'].data_termino.date()) +# evento['duration'] = end-evento['start']+1 +# evento['close'] = len(dates)-end-1 + +# # Agrupa os eventos em linhas para melhorar a visualização +# linhas = [] + +# for evento in eventos: +# encaixado = False +# for linha in linhas: +# sobrepoe = False +# for e in linha: +# if (((evento['evento'].data_inicio.date() >= e['evento'].data_inicio.date()) and +# (evento['evento'].data_inicio.date() <= e['evento'].data_termino.date())) or +# ((evento['evento'].data_termino.date() >= e['evento'].data_inicio.date()) and +# (evento['evento'].data_termino.date() <= e['evento'].data_termino.date()))): +# sobrepoe = True +# break +# if not sobrepoe: +# # Adiona o evento em uma linha que ele não sobrepoe nenhum outro +# linha.append(evento) +# encaixado = True +# break +# if not encaixado: +# # Adiciona uma nova linha porque este evento não se encaixa em nenhuma existente +# linhas.append([evento]) + +# # Recalcula as distâncias dos eventos por linha para encaixar no calendário +# for linha in linhas: +# anterior = None +# for evento in linha: +# if anterior is None: +# anterior = evento +# continue +# anterior['close'] = (evento['evento'].data_inicio.date() - anterior['evento'].data_termino.date()).days-1 +# evento['start'] = 0 +# anterior = evento + +# data['dates'] = dates +# data['eventos'] = eventos +# data['linhas'] = linhas + +# if formato == 'pdf': +# return render_to_pdf('eventos/calendario_pdf.html', data ) + +# return render(request, 'eventos/calendario.html', data) + +# @login_required +# def alocacao_equipe(request): +# ano_pesquisa = int(request.GET.get('ano', datetime.date.today().year)) +# formato = request.GET.get('fmt', 'html') + +# data = {'ano_pesquisa': ano_pesquisa} + +# if Evento.objects.filter(data_inicio__year=ano_pesquisa-1).exists(): +# data['prev_button'] = {'ano': ano_pesquisa - 1 } + +# if Evento.objects.filter(data_inicio__year=ano_pesquisa+1).exists(): +# data['next_button'] = {'ano': ano_pesquisa + 1 } + +# dados = [] + +# for evento in Evento.objects.filter(data_inicio__year=ano_pesquisa).exclude(status='C').prefetch_related('equipe_set'): +# for p in evento.equipe_set.all(): +# registro = None +# for r in dados: +# if r[0] == p.membro.pk: +# registro = r +# break +# if not registro: +# registro = [p.membro.pk, p.membro.nome_completo, [{'dias': 0, 'eventos': 0} for x in range(1,13)]] +# dados.append(registro) + +# registro[2][evento.data_inicio.month-1]['dias'] += (evento.data_termino - evento.data_inicio).days + 1 +# registro[2][evento.data_inicio.month-1]['eventos'] += 1 + +# dados.sort(lambda x, y: cmp(x[1], y[1])) + +# lang = (translation.to_locale(translation.get_language())+'.utf8').encode() +# locale.setlocale(locale.LC_ALL, lang) +# meses = [calendar.month_name[m] for m in range(1,13)] + +# linhas = [[_("Servidor")] + meses + ['total']] + +# for r in dados: +# r[2].append(reduce(lambda x,y:{'dias': x['dias'] + y['dias'], +# 'eventos': x['eventos'] + y['eventos']}, r[2])) +# linhas.append([r[1]] + +# [_(ungettext("%(dias)s dia", "%(dias)s dias", d['dias']) + " em " + +# ungettext("%(eventos)s evento", "%(eventos)s eventos", d['eventos']) +# ) % d if d['dias'] > 0 or d['eventos'] > 0 else '' for d in r[2]]) + +# # for registro in Servidor.objects.filter(equipe_evento__evento__data_inicio__year=ano_pesquisa).exclude(equipe_evento__evento__status='C').distinct(): +# # dados = [{'dias': 0, 'eventos': 0} for x in range(1,13)] +# # for part in registro.equipe_evento.filter(evento__data_inicio__year=ano_pesquisa).exclude(evento__status='C'): +# # dados[part.evento.data_inicio.month-1]['dias'] += (part.evento.data_termino - +# # part.evento.data_inicio).days + 1 +# # dados[part.evento.data_inicio.month-1]['eventos'] += 1 +# # dados.append([registro.nome_completo] + [_(ungettext("%(dias)s dia", "%(dias)s dias", d['dias']) + " em " + ungettext("%(eventos)s evento", "%(eventos)s eventos", d['eventos'])) % d if d['dias'] > 0 or d['eventos'] > 0 else '' for d in dados]) + +# data['linhas'] = linhas + +# if formato == 'pdf': +# return render_to_pdf('eventos/alocacao_equipe_pdf.html', data) +# elif formato == 'csv': +# response = HttpResponse(content_type='text/csv') +# response['Content-Disposition'] = 'attachment; filename="alocacao_equipe_%s.csv"' % (ano_pesquisa,) +# writer = csv.writer(response) +# asc_list = [[s.encode('utf-8') if isinstance(s, unicode) else s for s in l] for l in linhas] +# writer.writerows(asc_list) +# return response +# elif formato == 'json': +# result = {'ano': ano_pesquisa, +# 'equipe': [{'pk': d[0], +# 'nome_completo': d[1], +# 'meses': {m[0]: m[1] for m in zip(meses+['total'], d[2])} +# } for d in dados]} +# return JsonResponse(result) + +# return render(request, 'eventos/alocacao_equipe.html', data) + +# # Views e functions para carrinho de exportação + +# def query_ordena(qs, o): +# from sigi.apps.eventos.admin import EventoAdmin +# list_display = EventoAdmin.list_display +# order_fields = [] + +# for order_number in o.split('.'): +# order_number = int(order_number) +# order = '' +# if order_number != abs(order_number): +# order_number = abs(order_number) +# order = '-' +# order_fields.append(order + list_display[order_number - 1]) +# qs = qs.order_by(*order_fields) +# return qs + +# def get_for_qs(get, qs): +# kwargs = {} +# for k, v in get.iteritems(): +# if str(k) not in ('page', 'pop', 'q', '_popup', 'o', 'ot'): +# kwargs[str(k)] = v +# qs = qs.filter(**kwargs) +# if 'o' in get: +# qs = query_ordena(qs, get['o']) +# return qs + +# def carrinhoOrGet_for_qs(request): +# if 'carrinho_eventos' in request.session: +# ids = request.session['carrinho_eventos'] +# qs = Evento.objects.filter(pk__in=ids) +# else: +# qs = Evento.objects.all() +# if request.GET: +# qs = get_for_qs(request.GET, qs) +# return qs + +# def adicionar_eventos_carrinho(request, queryset=None, id=None): +# if request.method == 'POST': +# ids_selecionados = request.POST.getlist('_selected_action') +# if 'carrinho_eventos' not in request.session: +# request.session['carrinho_eventos'] = ids_selecionados +# else: +# lista = request.session['carrinho_eventos'] +# # Verifica se id já não está adicionado +# for id in ids_selecionados: +# if id not in lista: +# lista.append(id) +# request.session['carrinho_eventos'] = lista + +# @login_required +# def visualizar_carrinho(request): +# qs = carrinhoOrGet_for_qs(request) +# paginator = Paginator(qs, 100) + +# try: +# page = int(request.GET.get('page', '1')) +# except ValueError: +# page = 1 + +# try: +# paginas = paginator.page(page) +# except (EmptyPage, InvalidPage): +# paginas = paginator.page(paginator.num_pages) + +# carrinhoIsEmpty = not('carrinho_eventos' in request.session) + +# return render( +# request, +# 'eventos/carrinho.html', +# { +# 'carIsEmpty': carrinhoIsEmpty, +# 'paginas': paginas, +# 'query_str': '?' + request.META['QUERY_STRING'] +# } +# ) + +# @login_required +# def excluir_carrinho(request): +# if 'carrinho_eventos' in request.session: +# del request.session['carrinho_eventos'] +# messages.info(request, 'O carrinho foi esvaziado') +# return HttpResponseRedirect('../../') + +# @login_required +# def deleta_itens_carrinho(request): +# if request.method == 'POST': +# ids_selecionados = request.POST.getlist('_selected_action') +# removed = 0 +# if 'carrinho_eventos' in request.session: +# lista = request.session['carrinho_eventos'] +# for item in ids_selecionados: +# lista.remove(item) +# removed += 1 +# if lista: +# request.session['carrinho_eventos'] = lista +# else: +# del lista +# del request.session['carrinho_eventos'] +# messages.info(request, "{0} itens removidos do carrinho".format(removed)) +# return HttpResponseRedirect('.') + +# @login_required +# def export_csv(request): +# def rm_rows(lista,reg): +# for a in lista: +# if a in lista: +# reg.pop(a,None) +# else: +# pass + +# def serialize(r, field): +# value = (getattr(r, 'get_{0}_display'.format(field.name), None) or +# getattr(r, field.name, "")) +# if callable(value): +# value = value() +# if value is None: +# value = "" +# return unicode(value).encode('utf8') + +# eventos = carrinhoOrGet_for_qs(request) +# eventos.select_related('equipe', 'convite') + +# if not eventos: +# messages.info(request, _("Nenhum evento a exportar")) +# return HttpResponseRedirect('../') + +# max_equipe = max([e.equipe_set.count() for e in eventos]) + +# mun_casa = 'Município da Casa Anfitriã'.encode('utf8') +# uf_casa = 'UF da Casa Anfitriã'.encode('utf8') +# reg_casa = 'Região da Casa Anfitriã'.encode('utf8') + +# head = [f.verbose_name.encode('utf8') for f in Evento._meta.fields] +# head.extend([mun_casa, uf_casa, reg_casa]) +# head.extend([f.verbose_name.encode('utf8')+"_{0}".format(i+1) +# for i in range(max_equipe) for f in Equipe._meta.fields +# if f.name not in ('id', 'evento')]) +# head.extend([f.verbose_name.encode('utf8') for f in Convite._meta.fields +# if f.name not in ('id', 'evento')]) +# head.extend([f.verbose_name.encode('utf8') for f in Modulo._meta.fields +# if f.name not in ('id', 'evento')]) + +# response = HttpResponse(content_type='text/csv') +# response['Content-Disposition'] = 'attachment; filename=eventos.csv' +# rm_list = ['Descrição do evento', 'Local do evento', 'Público alvo', 'Motivo do cancelamento', 'Descrição do módulo'] + +# for a in head: +# if 'Observações_' in a: +# rm_list.append(a) + +# for a in rm_list: +# if a in head: +# head.remove(a) +# else: +# pass +# writer = csv.DictWriter(response, fieldnames=head) +# writer.writeheader() + +# for evento in eventos: +# reg = {f.verbose_name.encode('utf8'): serialize(evento, f) +# for f in Evento._meta.fields} +# if evento.casa_anfitria is None: +# reg[mun_casa] = "" +# reg[uf_casa] = "" +# reg[reg_casa] = "" +# else: +# reg[mun_casa] = evento.casa_anfitria.municipio.nome.encode('utf8') +# reg[uf_casa] = evento.casa_anfitria.municipio.uf.sigla.\ +# encode('utf8') +# reg[reg_casa] = evento.casa_anfitria.municipio.uf.\ +# get_regiao_display().encode('utf8') + +# idx = 1 +# for membro in evento.equipe_set.all(): +# reg.update( +# { +# "{0}_{1}".format(f.verbose_name.encode('utf8'), idx): +# serialize(membro, f) for f in Equipe._meta.fields +# if f.name not in ('id', 'evento') +# } +# ) +# idx += 1 +# for convite in evento.convite_set.all(): +# reg.update( +# {f.verbose_name.encode('utf8'): serialize(convite, f) +# for f in Convite._meta.fields +# if f.name not in ('id', 'evento')} +# ) +# rm_rows(rm_list,reg) +# writer.writerow(reg) + +# if evento.convite_set.count() == 0: +# rm_rows(rm_list,reg) + +# writer.writerow(reg) + +# return response + +# @login_required +# def declaracao(request, id): +# if request.method == 'POST': +# form = SelecionaModeloForm(request.POST) +# if form.is_valid(): +# evento = get_object_or_404(Evento, id=id) +# modelo = form.cleaned_data['modelo'] +# template_string = ( +# """ +# {% extends "eventos/declaracao_pdf.html" %} +# {% block text_body %}""" + +# modelo.texto + """ +# {% endblock %} +# """ +# ) +# context = Context( +# {'pagesize': modelo.formato, +# 'pagemargin': modelo.margem, +# 'evento': evento, +# 'data': datetime.date.today(), +# } +# ) +# template = Template(template_string) +# # return HttpResponse(template.render(context)) +# return pdf_renderer(template, context, 'declaracao.pdf') +# else: +# form = SelecionaModeloForm() + +# return render( +# request, +# 'eventos/seleciona_modelo.html', +# {'form': form, 'evento_id': id} +# ) \ No newline at end of file diff --git a/sigi/apps/home/static/home/css/openmap.css b/sigi/apps/home/static/home/css/openmap.css new file mode 100644 index 0000000..2dec4a0 --- /dev/null +++ b/sigi/apps/home/static/home/css/openmap.css @@ -0,0 +1,44 @@ +body, html, .mapbox, #map { + height: 100%; + width: 100%; +} +#content { + height: 91%; +} +.filterwrap { + background-color: rgba(255,255,255,0.5); + position: absolute; + top: 0; + left: 0; + z-index: 314159; + height: 100%; + max-height: 100%; + overflow-y: auto; +} +.region-ufs { + margin-left: 15px; +} +.autocomplete-content { + max-width: 300px; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05); + box-shadow: 0 1px 2px rgba(0,0,0,.05); + border-color: #ddd; + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + padding: 15px; + z-index: 99999 !important; +} +.dropdown-content li>span { + font-size: 13px; + line-height: 13px; + padding: 3px 3px; +} + +.dropdown-content li { + font-size: 13px; + line-height: 13px; + height: auto; + min-height: unset; +} \ No newline at end of file diff --git a/sigi/apps/home/templates/home/dashboard/gerentes_snippet.html b/sigi/apps/home/templates/home/dashboard/gerentes_snippet.html new file mode 100644 index 0000000..b775a86 --- /dev/null +++ b/sigi/apps/home/templates/home/dashboard/gerentes_snippet.html @@ -0,0 +1,35 @@ +{% load static i18n %} + + \ No newline at end of file diff --git a/sigi/apps/home/templates/home/dashboard/resumo_convenios.html b/sigi/apps/home/templates/home/dashboard/resumo_convenios.html new file mode 100644 index 0000000..c7538cb --- /dev/null +++ b/sigi/apps/home/templates/home/dashboard/resumo_convenios.html @@ -0,0 +1,57 @@ +{% load i18n %} + +Centro Oeste +Nordeste +Norte +Sudeste +Sul + + + + {% for item in tabela_resumo_camara.cabecalho_topo %} + + {% endfor %} + + {% for cabecalho,lista in tabela_resumo_camara.lista_zip %} + + + {% for item in lista %} + + {% endfor %} + + {% endfor %} +
{{item}}
{{cabecalho}}{{item}}
+ + + + + + + + + + + + + + + + + + + + + + + + +
{% trans 'Total de câmaras' %}{{ tabela_resumo_camara.total_camaras }}
{% trans 'Câmaras sem processo' %}{{ tabela_resumo_camara.camaras_sem_processo }}
{% trans 'Casas sem convenio que utilizam algum serviço de hospedagem' %} + list + file_download + {{ tabela_resumo_camara.sem_convenio.hospedagem|length }}
{% trans 'Casas sem convenio que utilizam somente serviço de registro' %} + list + file_download + {{ tabela_resumo_camara.sem_convenio.registro|length }}
{% trans 'Casas sem convenio que utilizam algum serviço de registro e/ou hospedagem' %} + list + file_download + {{ tabela_resumo_camara.sem_convenio.total|length }}
\ No newline at end of file diff --git a/sigi/apps/home/templates/home/dashboard/resumo_seit.html b/sigi/apps/home/templates/home/dashboard/resumo_seit.html new file mode 100644 index 0000000..208ff97 --- /dev/null +++ b/sigi/apps/home/templates/home/dashboard/resumo_seit.html @@ -0,0 +1,41 @@ +{% load static i18n %} + + + + + + {% for s in tabela_resumo_seit.titulos %} + + {% endfor %} + + {% for servico in tabela_resumo_seit.servicos %} + + + + + + + {% endfor %} +
{{ s }}
+ {{ servico.nome }} + + {{ servico.total }}{{ servico.novos_mes_anterior }}{{ servico.novos_mes_atual }}
diff --git a/sigi/apps/home/templates/home/lista_casas.html b/sigi/apps/home/templates/home/lista_casas.html new file mode 100644 index 0000000..2573def --- /dev/null +++ b/sigi/apps/home/templates/home/lista_casas.html @@ -0,0 +1,60 @@ +{% extends "pdf/base_report.html" %} +{% load i18n %} + +{% block page_size %}A4 landscape{% endblock page_size %} +{% block title %}{% trans 'Lista de Casas atendidas' %}{% endblock title %} +{% block report_name %}{% trans 'Lista de Casas atendidas'|upper %}{% endblock report_name %} + +{% block main_content %} + + + {% if tipos_orgao %} + + {% endif %} + {% if tipos_servico %} + + {% endif %} + {% if tipos_convenio %} + + {% endif %} + {% if gerentes %} + + {% endif %} + {% if ufs %} + + {% endif %} +
{% trans "Filtros aplicados" %}
{% trans 'Tipos de órgão' %}{% for t in tipos_orgao %}{{ t.nome }}{% if not forloop.last %}, {% endif %}{% endfor %}
{% trans 'Tipos de serviço' %}{% for s in tipos_servico %}{{ s.nome }}{% if not forloop.last %}, {% endif %}{% endfor %}
{% trans 'Tipos de convênio' %}{% for c in tipos_convenio %}{{ c.nome }}{% if not forloop.last %}, {% endif %}{% endfor %}
{% trans 'Gerentes Interlegis' %}{% for g in gerentes %}{{ g.nome_completo }}{% if not forloop.last %}, {% endif %}{% endfor %}
{% trans 'Estados' %}{% for uf in ufs %}{{ uf.nome }}{% if not forloop.last %}, {% endif %}{% endfor %}
+
+ + + + + + + + + + + + {% for casa in casas %} + + + + + + + + + {% endfor %} +
{% trans 'Casa Legislativa' %}{% trans 'Estado' %}{% trans 'Região' %}{% trans 'Serviços' %}{% trans 'Convênios' %}{% trans 'Gerente(s)' %}
{{ casa.nome }} {{ casa.municipio.uf.nome }}{{ casa.municipio.uf.get_regiao_display }}
    {% for s in casa.servico_set.all %} + {% if s.data_desativacao == None %} +
  • {{ s }}
  • + {% endif %} + {% endfor %}
    {% for c in casa.convenio_set.all %} +
  • {{ c }}
  • + {% endfor %} +
    {% for g in casa.gerentes_interlegis.all %} +
  • {{ g }}
  • + {% endfor %}
+
+{% endblock %} diff --git a/sigi/apps/home/templates/home/mapfilter.html b/sigi/apps/home/templates/home/mapfilter.html new file mode 100644 index 0000000..2dcb659 --- /dev/null +++ b/sigi/apps/home/templates/home/mapfilter.html @@ -0,0 +1,173 @@ +{% load i18n material %} + +
+ {% if not mobile %} + + {% endif %} +
+ {{ csrftoken }} +
+
    +
  • +
    +
    + {% if 'profile/user_picture.html'|template_exists %} + {% include 'profile/user_picture.html' %} + {% else %} + {% include 'material/admin/user_picture.html' %} + {% endif %} +
    +
    +
  • +
  • +
    +
    +
    +
    + search + + +
    +
    +
    + Total de Órgãos selecionados: - +
  • +
  • +
    {% trans "Por Tipo de órgão" %}
    +
    + {% for o in tipos_orgao %} +

    + +

    + {% endfor %} +
    +
  • +
  • +
    {% trans "Por Tipo de serviço" %}
    +
    +

    + +

    +

    + +

    + {% for s in tipos_servico %} +

    + +

    + {% endfor %} +
    +
  • + +
  • +
    {% trans "Por convênio" %}
    +
    +

    + +

    +

    + +

    + {% for c in tipos_convenio %} +

    + +

    + {% endfor %} +
    +
  • +
  • +
    {% trans "Por região/estado" %}
    +
    + {% for s, n, ufs in regioes %} +

    + +

    +
    + {% for uf in ufs %} +

    + +

    + {% endfor %} +
    + {% endfor %} +
    +
  • + +
  • +
    {% trans "Por gerente Interlegis" %}
    +
    +

    + +

    +

    + +

    + {% for g in gerentes %} +

    + +

    + {% endfor %} +
    +
  • +
  • +
    {% trans "Exportação de dados" %}
    +
    + + + + + +
    +
  • + + + +
+
\ No newline at end of file diff --git a/sigi/apps/home/templates/home/openmap.html b/sigi/apps/home/templates/home/openmap.html new file mode 100644 index 0000000..5cd8c74 --- /dev/null +++ b/sigi/apps/home/templates/home/openmap.html @@ -0,0 +1,375 @@ +{% extends "admin/index.html" %} +{% load static %} +{% load i18n %} + +{% block extrastyle %} + {{ block.super }} + + + + +{% endblock %} + +{% block extrahead %} + {{ block.super }} + + + + + + + + +{% endblock %} + +{% block usertools %} + +{% endblock %} + +{% block side_nav %} + {% if not nav_bar_minimized %} + + {% endif %} +
+ {% include 'home/mapfilter.html' with mobile=True %} +
+{% endblock %} + +{% block content %} +
+ +
+{% endblock %} + +{% block sidebar %}{% endblock %} + +{% block footer %} +{{ block.super }} + +{% endblock %} + +{% comment %} + + + + + {% trans 'SIGI' %} + + + +
+ +
+ +
+ +
+ + + + +{% endcomment %} + diff --git a/sigi/apps/home/templates/home/openmapdetail.html b/sigi/apps/home/templates/home/openmapdetail.html new file mode 100644 index 0000000..4945662 --- /dev/null +++ b/sigi/apps/home/templates/home/openmapdetail.html @@ -0,0 +1,23 @@ +
+
+ {{ orgao.nome }} +
+
+ + + {% if orgao.data_instalacao %}{% endif %} + + {% if orgao.telefones.all %}{% endif %} + {% if orgao.email %}{% endif %} + {% if orgao.convenio_set.all %} + + {% endif %} + {% if orgao.servico_set.all %} + + {% endif %} + {% if orgao.gerentes_interlegis.all %} + + {% endif %} +
CNPJ{{ orgao.cnpj }}
Data de instalação{{ orgao.data_instalacao }}
Endereço
{{ orgao.logradouro }}, {{ orgao.bairro }}, {{ orgao.municipio.nome }}, {{ orgao.municipio.uf.sigla }}, CEP: {{ orgao.cep }}
Telefones{% for telefone in orgao.telefones.all %}{{ telefone.numero }}{% if not forloop.last %}, {% endif %}{% endfor %}
E-mail{{ orgao.email }}
Convênios{% for c in orgao.convenio_set.all %}{{ c }}{% if not forloop.last %}, {% endif %}{% endfor %}
Serviços{% for s in orgao.servico_set.all %}{% if s.url %}{{ s }}{% else %}{{ s }}{% endif %}{% if not forloop.last %}, {% endif %}{% endfor %}
Gerentes{% for g in orgao.gerentes_interlegis.all %}{{ g.nome_completo }}{% if not forloop.last %}, {% endif %} {% endfor %}
+
+
\ No newline at end of file diff --git a/sigi/apps/home/templates/home/sem_convenio.html b/sigi/apps/home/templates/home/sem_convenio.html index 4e977ef..43386d1 100644 --- a/sigi/apps/home/templates/home/sem_convenio.html +++ b/sigi/apps/home/templates/home/sem_convenio.html @@ -1,83 +1,23 @@ -{% load static from staticfiles %} -{% load i18n %} - - - - - {{ titulo }} - - - - +{% block page_size %}A4 portrait{% endblock %} +{% block main_content %} - - - - - - + + + + + + + + {% for casa in casas %} - + {% endfor %}
{% trans "Nome da Casa" %}{% trans "UF" %}{% trans "Gerente de contas" %}{% trans "Serviços" %}
{% trans "Nome da Casa" %}{% trans "UF" %}{% trans "Gerente de contas" %}{% trans "Serviços" %}
{{ casa.nome }} {{ casa.municipio.uf.sigla }}{{ casa.lista_gerentes }}{{ casa.lista_gerentes|safe }} {% for s in casa.servico_set.all %} {% if s.data_desativacao == None %} @@ -88,11 +28,4 @@
- - - - +{% endblock main_content %} diff --git a/sigi/apps/home/templatetags/smart_pagination.py b/sigi/apps/home/templatetags/smart_pagination.py index 4dcbca2..e4d5142 100644 --- a/sigi/apps/home/templatetags/smart_pagination.py +++ b/sigi/apps/home/templatetags/smart_pagination.py @@ -1,23 +1,3 @@ -# -*- coding: utf-8 -*- -# -# sigi.apps.home.templatetags.smart_pagination -# -# Copyright (C) 2015 Interlegis -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - from django import template register = template.Library() @@ -28,17 +8,17 @@ def smart_paginator(page_obj, querystring=None): querystring = '?' + querystring + '&' else: querystring = '?' - + page_range = page_obj.paginator.page_range - mid = len(page_range) / 2 - + mid = len(page_range) // 2 + range = list(set(page_range[:3]) | set(page_range[mid-2:mid+1]) | set(page_range[-3:]) | set(page_range[page_obj.number-2:page_obj.number+1])) range.sort() - + last = range[0]-1 page_range = [] - + for p in range: if p-1 != last: if p-2 == last: @@ -46,8 +26,8 @@ def smart_paginator(page_obj, querystring=None): else: page_range.append(None) page_range.append(p) - last = p - + last = p + return dict(page_obj=page_obj, querystring=querystring, page_range=page_range) @register.inclusion_tag('menus/menu_item.html') diff --git a/sigi/apps/home/urls.py b/sigi/apps/home/urls.py index 33e1e5b..234371a 100644 --- a/sigi/apps/home/urls.py +++ b/sigi/apps/home/urls.py @@ -1,15 +1,28 @@ -# coding: utf-8 -from django.conf.urls import patterns, url - - -urlpatterns = patterns('sigi.apps.home.views', - url(r'^$', 'index', name='sigi_index'), - url(r'^home/resumoconvenios/$', 'resumo_convenios', name="home_resumoconvenios"), - url(r'^home/resumoseit/$', 'resumo_seit', name="home_resumoseit"), - url(r'^home/chartseit/$', 'chart_seit', name="home_chartseit"), - url(r'^home/chartconvenios/$', 'chart_convenios', name="home_chartconvenios"), - url(r'^home/chartcarteira/$', 'chart_carteira', name="home_chartcarteira"), - url(r'^home/chartperformance/$', 'chart_performance', name="home_chartperformance"), - url(r'^home/report/semconvenio/$', 'report_sem_convenio', name="home_reportsemconvenio"), - -) +from django.urls import path +from sigi.apps.home import views + +urlpatterns = [ + path('',views.openmap, name='openmap'), + path('openmapdata/', views.openmapdata, name='openmapdata'), + path('openmapdetail//', views.openmapdetail, name='openmapdetail'), + path('openmapsearch/', views.openmapsearch, name='openmapsearch'), + path('home/resumoseit/', views.resumo_seit, name="home_resumoseit"), + path('home/chartseit/', views.chart_seit, name="home_chartseit"), + path('home/chartperformance/', views.chart_performance, name="home_chartperformance"), + path('home/chartcarteira/', views.chart_carteira, name="home_chartcarteira"), + path('home/resumoconvenios/', views.resumo_convenios, name="home_resumoconvenios"), + path('home/report/semconvenio/', views.report_sem_convenio, name="home_reportsemconvenio"), +] + +# from django.conf.urls import patterns, url + + +# urlpatterns = patterns('sigi.apps.home.views', +# url(r'^$', 'index', name='sigi_index'), +# url(r'^home/chartseit/$', 'chart_seit', name="home_chartseit"), +# url(r'^home/chartconvenios/$', 'chart_convenios', name="home_chartconvenios"), +# url(r'^home/chartcarteira/$', 'chart_carteira', name="home_chartcarteira"), +# url(r'^home/chartperformance/$', 'chart_performance', name="home_chartperformance"), +# url(r'^home/report/semconvenio/$', 'report_sem_convenio', name="home_reportsemconvenio"), + +# ) diff --git a/sigi/apps/home/views.py b/sigi/apps/home/views.py index 90f3d3f..dc2a878 100644 --- a/sigi/apps/home/views.py +++ b/sigi/apps/home/views.py @@ -1,57 +1,202 @@ -# -*- coding: utf-8 -*- -# -# sigi.apps.home.views -# -# Copyright (c) 2016 by Interlegis -# -# GNU General Public License (GPL) -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301, USA. -# - -import datetime import calendar +import csv +import datetime +from itertools import cycle +from django.contrib.admin.sites import site +from django.contrib.auth.decorators import login_required +from django.db.models import Q, Count +from django.http import HttpResponse, JsonResponse from django.shortcuts import render, get_object_or_404 +from django.template.loader import render_to_string from django.utils.translation import gettext as _ -from itertools import cycle -from sigi.apps.casas.models import Orgao +from django.views.decorators.cache import never_cache +from django_weasyprint.views import WeasyTemplateResponse +from sigi.apps.casas.models import TipoOrgao, Orgao +from sigi.apps.contatos.models import UnidadeFederativa from sigi.apps.convenios.models import Convenio, Projeto -from sigi.apps.diagnosticos.models import Diagnostico -from sigi.apps.metas.models import Meta from sigi.apps.servicos.models import TipoServico from sigi.apps.servidores.models import Servidor -from django.views.decorators.cache import never_cache -from django.contrib.auth.decorators import login_required -from django.http.response import JsonResponse, HttpResponse -from django.core.urlresolvers import reverse -from django.db.models import Q, Count -from sigi.shortcuts import render_to_pdf -import csv +from sigi.apps.utils import to_ascii + +# from django.shortcuts import render, get_object_or_404 +# from sigi.apps.casas.models import Orgao +# from sigi.apps.diagnosticos.models import Diagnostico +# from sigi.apps.metas.models import Meta +# from sigi.apps.servicos.models import TipoServico +# from sigi.apps.servidores.models import Servidor +# from django.http.response import JsonResponse, HttpResponse +from django.urls import reverse +# from sigi.shortcuts import render_to_pdf +# import csv + +def openmap(request): + reptype = request.GET.get('reptype', None) + context = site.each_context(request) + + if reptype is None: + context['tipos_orgao'] = TipoOrgao.objects.filter(legislativo=True) + context['tipos_servico'] = TipoServico.objects.all() + context['tipos_convenio'] = Projeto.objects.all() + context['gerentes'] = Servidor.objects.exclude(casas_que_gerencia=None) + context['regioes'] = [(s, n, UnidadeFederativa.objects.filter(regiao=s)) + for s, n in UnidadeFederativa.REGIAO_CHOICES] + return render(request, 'home/openmap.html', context) + else: + tipos_orgao = request.GET.getlist('tipo_orgao', []) + tipos_servico = request.GET.getlist('tipo_servico', []) + tipos_convenio = request.GET.getlist('tipo_convenio', []) + gerentes = request.GET.getlist('gerente', []) + ufs = request.GET.getlist('uf', []) + casas = openmapdata(request) + + context['tipos_orgao'] = TipoOrgao.objects.filter(legislativo=True, + sigla__in=tipos_orgao) + context['tipos_servico'] = TipoServico.objects.filter( + sigla__in=tipos_servico + ) + context['tipos_convenio'] = Projeto.objects.filter( + sigla__in=tipos_convenio) + context['gerentes'] = Servidor.objects.exclude( + casas_que_gerencia=None).filter(id__in=gerentes) + context['ufs'] = UnidadeFederativa.objects.filter(sigla__in=ufs) + context['casas'] = casas + + if reptype == "lista": + return WeasyTemplateResponse( + filename='Lista de Casas atendidas.pdf', + request=request, + template='home/lista_casas.html', + context=context, + content_type='application/pdf', + ) + else: + fields = ['cnpj', 'nome', 'municipio__uf__nome', + 'municipio__uf__regiao', 'logradouro', 'bairro', + 'cep', 'ult_alt_endereco', 'email'] + if reptype in ('exporta_servico', 'exporta'): + fields.extend(['servico__tipo_servico__nome', 'servico__url', + 'servico__data_ativacao', + 'servico__data_desativacao']) + if reptype in ('exporta_convenio', 'exporta'): + fields.extend([ + 'convenio__num_convenio', + 'convenio__num_processo_sf', + 'convenio__projeto__sigla', + 'convenio__data_adesao', + 'convenio__data_termino_vigencia', + 'convenio__data_retorno_assinatura' + ]) + if reptype in ('exporta_contato', 'exporta'): + fields.extend(['funcionario__nome', 'funcionario__setor', + 'funcionario__email', 'funcionario__nota', + 'funcionario__redes_sociais', + 'funcionario__desativado', + 'funcionario__ult_alteracao']) + + dados = casas.distinct().values(*fields) + response = HttpResponse(content_type='text/csv') + writer = csv.DictWriter(response, fieldnames=fields) + writer.writeheader() + writer.writerows(dados) + return response + +def openmapdata(request): + tipos_orgao = request.GET.getlist('tipo_orgao', None) + tipos_servico = request.GET.getlist('tipo_servico', None) + tipos_convenio = request.GET.getlist('tipo_convenio', None) + ufs = request.GET.getlist('uf', None) + gerentes = request.GET.getlist('gerente', None) + reptype = request.GET.get('reptype', None) + + dados = Orgao.objects.all() + + if tipos_orgao: + dados = dados.filter(tipo__sigla__in=tipos_orgao) + else: + dados = dados.filter(tipo__legislativo=True) + + if tipos_servico: + if "none" in tipos_servico: + dados = dados.filter(servico=None) + else: + dados = dados.filter(servico__tipo_servico__sigla__in=tipos_servico, + servico__data_desativacao=None) + + if tipos_convenio: + if "none" in tipos_convenio: + dados = dados.filter(convenio=None) + else: + dados = dados.filter(convenio__projeto__sigla__in=tipos_convenio) + + if ufs: + dados = dados.filter(municipio__uf__sigla__in=ufs) + + if gerentes: + if "none" in gerentes: + dados = dados.filter(gerentes_interlegis=None) + else: + dados = dados.filter(gerentes_interlegis__id__in=gerentes) + + + if not reptype: + dados = dados.order_by('nome', 'id').distinct('nome', 'id') + dados = dados.values_list("id", "nome", "municipio__latitude", + "municipio__longitude") + return JsonResponse(list(dados), safe=False) + else: + dados = dados.order_by( + 'municipio__uf__regiao', + 'municipio__uf__nome', + 'nome', + 'id' + ).distinct( + 'municipio__uf__regiao', + 'municipio__uf__nome', + 'nome', + 'id' + ).prefetch_related( + 'servico_set', + 'convenio_set', + 'municipio__uf', + 'gerentes_interlegis' + ) + return dados + +def openmapdetail(request, orgao_id): + orgao = get_object_or_404(Orgao, id=orgao_id) + return render(request, "home/openmapdetail.html", {'orgao': orgao}) + +def openmapsearch(request): + q = request.GET.get('q', '') + if len(q) < 3: + return JsonResponse({'result': 'unsearchable'}) + + dados = Orgao.objects.filter( + tipo__legislativo=True, + search_text__icontains=to_ascii(q) + )[:10] + dados = dados.values("id", "nome", "municipio__latitude", + "municipio__longitude") + dados = [{'id': d['id'], + 'label': d['nome'], + 'lat': d['municipio__latitude'], + 'lng': d['municipio__longitude']} for d in dados] + return JsonResponse(list(dados), safe=False) -@never_cache -@login_required -def index(request): - context = {'gerentes': Servidor.objects.exclude(casas_que_gerencia=None)} - return render(request, 'index.html', context) + + + +# @never_cache +# @login_required +# def index(request): +# context = {'gerentes': Servidor.objects.exclude(casas_que_gerencia=None)} +# return render(request, 'index.html', context) @never_cache @login_required def resumo_convenios(request): context = {'tabela_resumo_camara': busca_informacoes_camara() } - return render(request, 'snippets/modules/resumo_convenios.html', context) + return render(request, 'home/dashboard/resumo_convenios.html', context) @never_cache @login_required @@ -66,36 +211,40 @@ def resumo_seit(request): tabela_resumo_seit = busca_informacoes_seit() context = {'tabela_resumo_seit': tabela_resumo_seit} - return render(request, 'snippets/modules/resumo_seit.html', context) + return render(request, 'home/dashboard/resumo_seit.html', context) @never_cache @login_required def chart_seit(request): - mes = request.GET.get('mes', None) - ano = request.GET.get('ano', None) + hoje = datetime.date.today() + mes = request.GET.get('mes', hoje.month) + ano = request.GET.get('ano', hoje.year) - try: - mes = datetime.date(year=int(ano), month=int(mes), day=1) - tabela_resumo_seit = busca_informacoes_seit(mes) - except: - tabela_resumo_seit = busca_informacoes_seit() + mes = datetime.date(year=int(ano), month=int(mes), day=1) + tabela_resumo_seit = busca_informacoes_seit(mes) data = { 'type': 'line', - 'prevlink': reverse('home_chartseit') + ('?ano=%s&mes=%s' % - (tabela_resumo_seit['mes_anterior'].year, - tabela_resumo_seit['mes_anterior'].month)), - 'nextlink': reverse('home_chartseit') + ('?ano=%s&mes=%s' % - (tabela_resumo_seit['proximo_mes'].year, - tabela_resumo_seit['proximo_mes'].month)), - 'options': {'bezierCurve': False, 'datasetFill': False, 'pointDot': False, 'responsive': True}, + 'prevlink': reverse('home_chartseit') + ( + f"?ano={tabela_resumo_seit['mes_anterior'].year}" + f"&mes={tabela_resumo_seit['mes_anterior'].month}" + ), + 'nextlink': reverse('home_chartseit') + ( + f"?ano={tabela_resumo_seit['proximo_mes'].year}" + f"&mes={tabela_resumo_seit['proximo_mes'].month}" + ), + 'options': {'bezierCurve': False, 'datasetFill': False, + 'pointDot': False, 'responsive': True}, 'data': { - 'labels': ['%02d/%s' % (mes.month, mes.year) for mes in reversed(tabela_resumo_seit['meses'])], + 'labels': [f'{mes: %m/%Y}' + for mes in reversed(tabela_resumo_seit['meses'])], 'datasets': [ { 'label': servico['nome'], - 'strokeColor': servico['cor'], - 'data': [mes['total'] for mes in reversed(servico['novos_por_mes'])] + 'borderColor': servico['cor'], + 'backgroundColor': servico['cor'], + 'data': [mes['total'] + for mes in reversed(servico['novos_por_mes'])] } for servico in tabela_resumo_seit['servicos']], } @@ -103,37 +252,38 @@ def chart_seit(request): return JsonResponse(data) -@never_cache -@login_required -def chart_convenios(request): - q = request.GET.get('q', 'all') - convenios = Convenio.objects.all() - if q == 'assinados': - convenios = convenios.exclude(data_retorno_assinatura=None) - data = { - 'type': 'pie', - 'options': {'responsive': False, 'maintainAspectRatio': False}, - 'data': grafico_convenio_projeto(convenios), - } - return JsonResponse(data) +# @never_cache +# @login_required +# def chart_convenios(request): +# q = request.GET.get('q', 'all') +# convenios = Convenio.objects.all() +# if q == 'assinados': +# convenios = convenios.exclude(data_retorno_assinatura=None) +# data = { +# 'type': 'pie', +# 'options': {'responsive': False, 'maintainAspectRatio': False}, +# 'data': grafico_convenio_projeto(convenios), +# } +# return JsonResponse(data) @never_cache @login_required def chart_carteira(request): colors, highlights = color_palete() - data = {'type': 'pie', - 'options': {'responsive': True}, - 'data': [{'value': r['total_casas'], - 'color': colors.next(), - 'highlight': highlights.next(), - 'label': r['gerentes_interlegis__nome_completo'] - } - for r in Orgao.objects.exclude( - gerentes_interlegis=None).values( - 'gerentes_interlegis__nome_completo').annotate( - total_casas=Count('pk')).order_by( - 'gerentes_interlegis__nome_completo') - ] + gerentes = Servidor.objects.exclude(casas_que_gerencia=None).annotate( + total_casas=Count('casas_que_gerencia')) + + data = { + 'type': 'doughnut', + 'data': { + 'labels': [g.get_apelido() for g in gerentes], + 'datasets': [ + { + 'data': [g.total_casas for g in gerentes], + 'backgroundColor': [next(highlights) for g in gerentes] + } + ], + }, } return JsonResponse(data) @@ -142,24 +292,42 @@ def chart_carteira(request): @login_required def chart_performance(request): servidor = request.GET.get('servidor', None) + gerentes = Servidor.objects.exclude(casas_que_gerencia=None) + gerente = None if servidor is None: + if (request.user.servidor and + request.user.servidor.casas_que_gerencia.exists()): + gerente = request.user.servidor + else: + servidor = '_all' + + if servidor is not None and servidor != '_all': + gerente = get_object_or_404(Servidor, pk=servidor) + + if gerente is None: casas = Orgao.objects.exclude(gerentes_interlegis=None) else: - gerente = get_object_or_404(Servidor, pk=servidor) casas = gerente.casas_que_gerencia data = { - 'type': 'pie', - 'options': {'responsive': True}, - 'data': [ - {'label': _("Utilizam serviços"), - 'value': casas.exclude(servico=None).count(), - 'color': '#91e8e1'}, - {'label': _("Não utilizam serviços"), - 'value': casas.filter(servico=None).count(), - 'color': '#f7a35c'}, - ] + 'type': 'doughnut', + 'data': { + 'labels': [_("Utilizam serviços"), _("Não utilizam serviços")], + 'datasets': [ + { + 'label': 'SeiLaQueIsso', + 'data': [casas.exclude(servico=None).count(), + casas.filter(servico=None).count()], + 'backgroundColor': ['#91e8e1', '#f7a35c'] + } + ] + }, + 'actionblock': render_to_string( + 'home/dashboard/gerentes_snippet.html', + context={'gerentes': gerentes, 'gerente': gerente}, + request=request + ) } return JsonResponse(data) @@ -187,20 +355,19 @@ def report_sem_convenio(request): if fmt == 'csv': response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename=casas.csv' + response['Content-Disposition'] = f'attachment; filename={ titulo }.csv' writer = csv.writer(response) - writer.writerow([titulo.encode('utf8')]) + writer.writerow([titulo]) writer.writerow(['']) - writer.writerow(['casa', 'uf', 'gerentes', - 'serviços'.encode('utf8')]) + writer.writerow(['casa', 'uf', 'gerentes', 'serviços']) for casa in casas: writer.writerow([ - casa.nome.encode('utf8'), - casa.municipio.uf.sigla.encode('utf8'), - casa.lista_gerentes(fmt='lista').encode('utf8'), + casa.nome, + casa.municipio.uf.sigla, + casa.lista_gerentes(fmt='lista'), (', '.join(casa.servico_set.filter( data_desativacao__isnull=True).values_list( - 'tipo_servico__nome', flat=True))).encode('utf8'), + 'tipo_servico__nome', flat=True))), ]) return response elif fmt == 'json': @@ -219,15 +386,17 @@ def report_sem_convenio(request): } return JsonResponse(data, safe=False) else: - context = {'casas': casas, 'titulo': titulo} - return render_to_pdf('home/sem_convenio.html', context) + context = {'casas': casas, 'title': titulo} + return WeasyTemplateResponse( + filename=f'{ titulo }.pdf', + request=request, + template='home/sem_convenio.html', + context=context, + content_type='application/pdf', + ) def busca_informacoes_camara(): - """ - Busca informacoes no banco para montar tabela de resumo de camaras por projeto - Retorna um dicionario de listas - """ camaras = Orgao.objects.filter(tipo__sigla='CM') convenios = Convenio.objects.filter(casa_legislativa__tipo__sigla='CM') projetos = Projeto.objects.all() @@ -261,15 +430,21 @@ def busca_informacoes_camara(): cabecalho_topo.append(projeto.sigla) lista_total.append(camaras.filter(convenio__projeto=projeto).count()) - lista_nao_aderidas.append(camaras.filter(convenio__in=conv_sem_adesao_proj).count()) - lista_aderidas.append(camaras.filter(convenio__in=conv_com_adesao_proj).count()) - lista_convenios_assinados.append(camaras.filter(convenio__in=conv_assinados_proj).count()) - lista_convenios_em_andamento.append(camaras.filter(convenio__in=conv_em_andamento_proj).count()) - lista_camaras_equipadas.append(camaras.filter(convenio__in=conv_equipadas_proj).count()) - - # Monta linhas de diagnosticos - lista_diagnosticos_digitados = ['', '', Diagnostico.objects.count(), '', '', ''] - lista_diagnosticos_publicados = ['', '', Diagnostico.objects.filter(publicado=True).count(), '', '', ''] + lista_nao_aderidas.append( + camaras.filter(convenio__in=conv_sem_adesao_proj).count() + ) + lista_aderidas.append( + camaras.filter(convenio__in=conv_com_adesao_proj).count() + ) + lista_convenios_assinados.append( + camaras.filter(convenio__in=conv_assinados_proj).count() + ) + lista_convenios_em_andamento.append( + camaras.filter(convenio__in=conv_em_andamento_proj).count() + ) + lista_camaras_equipadas.append( + camaras.filter(convenio__in=conv_equipadas_proj).count() + ) # Cabecalho da esquerda na tabela cabecalho_esquerda = ( @@ -279,8 +454,6 @@ def busca_informacoes_camara(): _('Câmaras municipais com convênios assinados'), _('Câmaras municipais convênios em andamento'), _('Câmaras municipais equipadas'), - _('Diagnósticos digitados'), - _('Diagnósticos publicados') ) linhas = ( @@ -290,11 +463,9 @@ def busca_informacoes_camara(): lista_convenios_assinados, lista_convenios_em_andamento, lista_camaras_equipadas, - lista_diagnosticos_digitados, - lista_diagnosticos_publicados ) - # Unindo as duas listass para que o cabecalho da esquerda fique junto com sua + # Unindo as duas listas para que o cabecalho da esquerda fique junto com sua # respectiva linha lista_zip = zip(cabecalho_esquerda, linhas) @@ -307,33 +478,53 @@ def busca_informacoes_camara(): 'sem_convenio': sem_convenio(), } - def sem_convenio(): - total = Orgao.objects.exclude(servico=None).filter(servico__data_desativacao=None, convenio=None).order_by('municipio__uf__sigla', 'nome').distinct('municipio__uf__sigla', 'nome') - hospedagem = Orgao.objects.exclude(servico=None).filter(servico__data_desativacao=None, servico__tipo_servico__modo='H', convenio=None).order_by('municipio__uf__sigla', 'nome').distinct('municipio__uf__sigla', 'nome') - reg_keys = set(total.values_list('pk', flat=True)).difference(set(hospedagem.values_list('pk', flat=True))) - registro = Orgao.objects.filter(pk__in=reg_keys).order_by('municipio__uf__sigla', 'nome') + total = Orgao.objects.exclude( + servico=None + ).filter( + servico__data_desativacao=None, convenio=None + ).order_by( + 'municipio__uf__sigla', 'nome' + ).distinct( + 'municipio__uf__sigla', 'nome' + ) + hospedagem = Orgao.objects.exclude( + servico=None + ).filter( + servico__data_desativacao=None, servico__tipo_servico__modo='H', + convenio=None + ).order_by( + 'municipio__uf__sigla', 'nome' + ).distinct( + 'municipio__uf__sigla', 'nome' + ) + reg_keys = set(total.values_list('pk', flat=True)).difference( + set(hospedagem.values_list('pk', flat=True)) + ) + registro = Orgao.objects.filter(pk__in=reg_keys).order_by( + 'municipio__uf__sigla', 'nome' + ) return { 'total': total, 'hospedagem': hospedagem, 'registro': registro, } -def grafico_convenio_projeto(convenios): - colors, highlights = color_palete() - projetos = Projeto.objects.all() - lista_projetos = [{'label': projeto.sigla, - 'value': convenios.filter(projeto=projeto).count(), - 'color': colors.next(), - 'highlight': highlights.next()} - for projeto in projetos] - # remove projetos sem convenio - lista_projetos = [x for x in lista_projetos if x['value'] > 0] +# def grafico_convenio_projeto(convenios): +# colors, highlights = color_palete() +# projetos = Projeto.objects.all() +# lista_projetos = [{'label': projeto.sigla, +# 'value': convenios.filter(projeto=projeto).count(), +# 'color': colors.next(), +# 'highlight': highlights.next()} +# for projeto in projetos] +# # remove projetos sem convenio +# lista_projetos = [x for x in lista_projetos if x['value'] > 0] - # print lista_projetos - # total_convenios = "Total: " + str(convenios.count()) - # lista_projetos.insert(0, total_convenios) - return lista_projetos +# # print lista_projetos +# # total_convenios = "Total: " + str(convenios.count()) +# # lista_projetos.insert(0, total_convenios) +# return lista_projetos def busca_informacoes_seit(mes_atual=None): @@ -341,7 +532,8 @@ def busca_informacoes_seit(mes_atual=None): if mes_atual is None: mes_atual = datetime.date.today().replace(day=1) mes_anterior = mes_atual - datetime.timedelta(days=1) - proximo_mes = mes_atual + datetime.timedelta(days=calendar.monthrange(mes_atual.year, mes_atual.month)[1]) + proximo_mes = mes_atual + datetime.timedelta(days=calendar.monthrange( + mes_atual.year, mes_atual.month)[1]) meses = [] mes = mes_atual @@ -374,18 +566,18 @@ def busca_informacoes_seit(mes_atual=None): 'novos_mes_anterior': tipo_servico.servico_set.filter(data_ativacao__year=mes_anterior.year, data_ativacao__month=mes_anterior.month).count(), 'novos_mes_atual': tipo_servico.servico_set.filter(data_ativacao__year=mes_atual.year, data_ativacao__month=mes_atual.month).count(), 'novos_por_mes': por_mes, - 'cor': colors.next(), + 'cor': next(colors), } ) return result -def busca_informacoes_diagnostico(): - return [ - {'title': _('Diagnósticos digitados'), 'count': Diagnostico.objects.count()}, - {'title': _('Diagnósticos publicados'), 'count': Diagnostico.objects.filter(publicado=True).count()}, - ] +# def busca_informacoes_diagnostico(): +# return [ +# {'title': _('Diagnósticos digitados'), 'count': Diagnostico.objects.count()}, +# {'title': _('Diagnósticos publicados'), 'count': Diagnostico.objects.filter(publicado=True).count()}, +# ] def color_palete(): diff --git a/sigi/apps/ocorrencias/admin.py b/sigi/apps/ocorrencias/admin.py index 9a407ee..b8cbdbf 100644 --- a/sigi/apps/ocorrencias/admin.py +++ b/sigi/apps/ocorrencias/admin.py @@ -3,7 +3,7 @@ from django.contrib.admin.views.main import ChangeList from django.utils.translation import gettext as _ from django.utils.safestring import mark_safe -from sigi.apps.ocorrencias.filters import OcorrenciaListFilter +from sigi.apps.ocorrencias.filters import ServidorRegistroFilter from sigi.apps.ocorrencias.models import (Ocorrencia, Comentario, Anexo, Categoria, TipoContato) from sigi.apps.servidores.models import Servidor @@ -18,18 +18,15 @@ class ComentarioViewInline(admin.TabularInline): can_delete = False verbose_name = _("Comentário anterior") verbose_name_plural = _("Comentários anteriores") - fields = ('usuario', 'data_criacao', 'novo_status', 'encaminhar_setor', - 'descricao', ) - readonly_fields = ('novo_status', 'encaminhar_setor', 'descricao', - 'data_criacao', 'usuario',) + fields = ('usuario', 'data_criacao', 'novo_status', 'descricao', ) + readonly_fields = fields class ComentarioInline(admin.StackedInline): model = Comentario extra = 1 verbose_name = _("Comentário novo") verbose_name_plural = _("Comentários novos") - fieldsets = ((None, {'fields': (('novo_status', 'encaminhar_setor',), - 'descricao', )}),) + fields = ('novo_status', 'descricao',) def get_queryset(self, queryset): return self.model.objects.none() @@ -39,32 +36,15 @@ class AnexosInline(admin.TabularInline): extra = 2 readonly_fields = ['data_pub', ] -class OcorrenciaChangeList(ChangeList): - def get_queryset(self, request): - tmp_params = self.params.copy() - grupo = None - if 'grupo' in self.params: - grupo = self.params['grupo'] - del self.params['grupo'] - qs = super().get_queryset(request) - self.params = tmp_params.copy() - if grupo: - servidor = Servidor.objects.get(user=request.user) - if grupo == 'S': # Apenas do meu setor - qs = qs.filter(setor_responsavel=servidor.servico) - elif grupo == 'M': # Apenas criados por mim - qs = qs.filter(servidor_registro=servidor) - return qs - @admin.register(Ocorrencia) -class OcorrenciaAdmin(BaseModelAdmin): +class OcorrenciaAdmin(admin.ModelAdmin): list_display = ('data_criacao', 'casa_legislativa', 'get_municipio', 'get_uf', 'assunto', 'prioridade', 'status', - 'data_modificacao', 'setor_responsavel',) + 'data_modificacao',) list_filter = ( - OcorrenciaListFilter, 'status', 'prioridade', 'categoria__nome', - 'setor_responsavel__nome', + 'status', 'prioridade', 'categoria__nome', ('casa_legislativa__gerentes_interlegis', GerentesInterlegisFilter), + ('servidor_registro', ServidorRegistroFilter), ) search_fields = ('casa_legislativa__search_text', 'assunto', 'servidor_registro__nome_completo', 'descricao', @@ -72,13 +52,10 @@ class OcorrenciaAdmin(BaseModelAdmin): date_hierarchy = 'data_criacao' fields = ('casa_legislativa', 'categoria', 'tipo_contato', 'assunto', 'status', 'prioridade', 'ticket', 'descricao', - 'servidor_registro', 'setor_responsavel', 'resolucao', ) - readonly_fields = ('servidor_registro', 'setor_responsavel', ) + 'servidor_registro', 'resolucao', ) + readonly_fields = ('servidor_registro', ) inlines = (ComentarioViewInline, ComentarioInline, AnexosInline, ) - raw_id_fields = ('casa_legislativa', ) - - def get_changelist(self, request, **kwargs): - return OcorrenciaChangeList + autocomplete_fields = ('casa_legislativa', ) def get_fieldsets(self, request, obj=None): if obj is None: @@ -88,23 +65,20 @@ class OcorrenciaAdmin(BaseModelAdmin): else: self.fields = ('casa_legislativa', 'categoria', 'tipo_contato', 'assunto', 'status', 'prioridade', 'ticket', - 'descricao', 'servidor_registro', - 'setor_responsavel', 'resolucao', ) + 'descricao', 'servidor_registro', 'resolucao', ) return super().get_fieldsets(request, obj) def save_model(self, request, obj, form, change): if not change: obj.servidor_registro = Servidor.objects.get(user=request.user) - obj.setor_responsavel = obj.categoria.setor_responsavel obj.save() def save_formset(self, request, form, formset, change): - servidor = Servidor.objects.get(user=request.user) instances = formset.save(commit=False) for instance in instances: if isinstance(instance, Comentario): - instance.usuario = servidor + instance.usuario = request.user.servidor instance.save() super(OcorrenciaAdmin, self).save_formset(request, form, formset, change) diff --git a/sigi/apps/ocorrencias/filters.py b/sigi/apps/ocorrencias/filters.py index e855359..7d34429 100644 --- a/sigi/apps/ocorrencias/filters.py +++ b/sigi/apps/ocorrencias/filters.py @@ -1,9 +1,16 @@ from django.contrib import admin from django.utils.translation import gettext as _ - from sigi.apps.servidores.models import Servidor +class ServidorRegistroFilter(admin.filters.RelatedFieldListFilter): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + servidores = Servidor.objects.exclude(ocorrencia=None).order_by( + 'nome_completo') + self.lookup_choices = [(x.id, x) for x in servidores] + + class OcorrenciaListFilter(admin.SimpleListFilter): title = _('Relacionadas a Mim') parameter_name = 'minhas' @@ -12,16 +19,13 @@ class OcorrenciaListFilter(admin.SimpleListFilter): if request.user.servidor is None: return None return ( - ('S', _('Atribuídos ao meu setor')), ('M', _('Registrados por mim')), ('G', _('Sobre casas que gerencio')), ) def queryset(self, request, queryset): servidor = request.user.servidor - if self.value() == 'S': - return queryset.filter(setor_responsavel=servidor.servico) - elif self.value() == 'M': + if self.value() == 'M': return queryset.filter(servidor_registro=servidor) elif self.value() == 'G': return queryset.filter( diff --git a/sigi/apps/ocorrencias/forms.py b/sigi/apps/ocorrencias/forms.py index 1b67c25..6a6c603 100644 --- a/sigi/apps/ocorrencias/forms.py +++ b/sigi/apps/ocorrencias/forms.py @@ -1,13 +1,16 @@ -from django.forms import ModelForm, ModelChoiceField, HiddenInput, TextInput +from django import forms from sigi.apps.ocorrencias.models import Ocorrencia, Comentario, Anexo from sigi.apps.servidores.models import Servico -from django.utils.encoding import force_text +from django.utils.encoding import force_str from django.utils.html import format_html from django.forms.utils import flatatt from django.urls import reverse_lazy from django.utils.safestring import mark_safe +from material.admin.widgets import MaterialAdminTextareaWidget +from django.contrib.admin.widgets import AutocompleteSelect +from django.contrib import admin -class AjaxSelect(TextInput): +class AjaxSelect(forms.TextInput): url = "" def __init__(self, url, attrs=None): super(AjaxSelect, self).__init__(attrs) @@ -20,7 +23,7 @@ class AjaxSelect(TextInput): code_attrs = self.build_attrs(type='hidden', name=name, id='hidden_'+name) if value != '': - final_attrs['value'] = force_text(self._format_value(value)) + final_attrs['value'] = force_str(self._format_value(value)) result = format_html('', flatatt(final_attrs)) + "\n" result = result + format_html('', flatatt(code_attrs)) js = """ @@ -38,31 +41,35 @@ class AjaxSelect(TextInput): result = result + mark_safe(js) return result -class AnexoForm(ModelForm): +class AnexoForm(forms.ModelForm): class Meta: model = Anexo fields = ['ocorrencia', 'descricao', 'arquivo',] - widgets = {'ocorrencia': HiddenInput()} - -class ComentarioForm(ModelForm): - encaminhar_setor = ModelChoiceField( - queryset=Servico.objects.all(), - cache_choices=True - ) + widgets = {'ocorrencia': forms.HiddenInput()} +class ComentarioForm(forms.ModelForm): class Meta: model = Comentario - fields = ['ocorrencia', 'descricao', 'novo_status', 'encaminhar_setor'] - widgets = {'ocorrencia': HiddenInput(),} + fields = ['ocorrencia', 'descricao', 'novo_status',] + widgets = { + 'ocorrencia': forms.HiddenInput(), + 'descricao': MaterialAdminTextareaWidget(), + } -class OcorrenciaForm(ModelForm): +class OcorrenciaForm(forms.ModelForm): class Meta: model = Ocorrencia fields = ['casa_legislativa', 'categoria', 'tipo_contato', 'assunto', - 'prioridade', 'ticket', 'descricao', 'setor_responsavel',] + 'prioridade', 'ticket', 'descricao',] widgets = { - 'casa_legislativa': AjaxSelect( - url=reverse_lazy('painel-buscacasa'), - attrs={'size':100} - ), - } \ No newline at end of file + 'casa_legislativa': AutocompleteSelect( + Ocorrencia.casa_legislativa.field, + admin.site + ) + } + # widgets = { + # 'casa_legislativa': AjaxSelect( + # url=reverse_lazy('painel-buscacasa'), + # attrs={'size':100} + # ), + # } \ No newline at end of file diff --git a/sigi/apps/ocorrencias/migrations/0007_remove_comentario_encaminhar_setor_and_more.py b/sigi/apps/ocorrencias/migrations/0007_remove_comentario_encaminhar_setor_and_more.py new file mode 100644 index 0000000..317718a --- /dev/null +++ b/sigi/apps/ocorrencias/migrations/0007_remove_comentario_encaminhar_setor_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0.1 on 2022-02-13 13:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ocorrencias', '0006_alter_anexo_arquivo_alter_anexo_descricao_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='comentario', + name='encaminhar_setor', + ), + migrations.RemoveField( + model_name='ocorrencia', + name='setor_responsavel', + ), + ] diff --git a/sigi/apps/ocorrencias/migrations/0008_remove_categoria_setor_responsavel.py b/sigi/apps/ocorrencias/migrations/0008_remove_categoria_setor_responsavel.py new file mode 100644 index 0000000..61fba3e --- /dev/null +++ b/sigi/apps/ocorrencias/migrations/0008_remove_categoria_setor_responsavel.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0.1 on 2022-02-13 13:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ocorrencias', '0007_remove_comentario_encaminhar_setor_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='categoria', + name='setor_responsavel', + ), + ] diff --git a/sigi/apps/ocorrencias/models.py b/sigi/apps/ocorrencias/models.py index 040c8ea..ea1e641 100644 --- a/sigi/apps/ocorrencias/models.py +++ b/sigi/apps/ocorrencias/models.py @@ -8,11 +8,6 @@ from django.utils.safestring import mark_safe class Categoria(models.Model): nome = models.CharField(_("Categoria"), max_length=50) descricao = models.TextField(_('descrição'), blank=True, null=True) - setor_responsavel = models.ForeignKey( - 'servidores.Servico', - on_delete=models.PROTECT, - verbose_name=_("Setor responsável") - ) class Meta: verbose_name = _('Categoria') @@ -100,11 +95,6 @@ class Ocorrencia(models.Model): on_delete=models.PROTECT, verbose_name=_("Servidor que registrou a ocorrência") ) - setor_responsavel = models.ForeignKey( - 'servidores.Servico', - on_delete=models.PROTECT, - verbose_name=_("Setor responsável") - ) ticket = models.PositiveIntegerField( _('Número do ticket'), blank=True, @@ -157,20 +147,8 @@ class Comentario(models.Model): blank=True, null=True ) - encaminhar_setor = models.ForeignKey( - 'servidores.Servico', - on_delete=models.PROTECT, - verbose_name=_('Encaminhar para setor'), - blank=True, - null=True - ) def save(self, *args, **kwargs): - if (self.encaminhar_setor - and (self.encaminhar_setor != self.ocorrencia.setor_responsavel) - ): - self.ocorrencia.setor_responsavel = self.encaminhar_setor - self.ocorrencia.save() if self.novo_status and (self.novo_status != self.ocorrencia.status): self.ocorrencia.status = self.novo_status self.ocorrencia.save() diff --git a/sigi/apps/ocorrencias/templates/ocorrencias/painel-old.html b/sigi/apps/ocorrencias/templates/ocorrencias/painel-old.html new file mode 100644 index 0000000..4e964bf --- /dev/null +++ b/sigi/apps/ocorrencias/templates/ocorrencias/painel-old.html @@ -0,0 +1,198 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_static %} +{% load static from staticfiles %} +{% load thumbnail %} + +{% block extrastyle %} + {{ block.super }} + +{% endblock %} + +{% block extrahead %} + {{ block.super }} + + + + +{% endblock %} + +{% block coltype %}colMS{% endblock %} + +{% block content_title %}

{{ panel_title }}

{% endblock %} + +{% block content %} + {% url 'painel-ocorrencias' as url_painel %} +
+
+
+ + + + +
+
+ + + +
+ {% include 'ocorrencias/ocorrencia_form.html' %} +
+ + {% if paineis %} + + {% endif %} + +
+
+ {% for ocorrencia in ocorrencias %} + {% include 'ocorrencias/ocorrencia_snippet.html' %} + {% empty %} +
+
+

{% trans 'Nenhuma ocorrência encontrada.' %}

+
+
+ {% endfor %} +
+
+
+ + + +{% endblock %} diff --git a/sigi/apps/ocorrencias/templates/ocorrencias/painel.html b/sigi/apps/ocorrencias/templates/ocorrencias/painel.html index 4e964bf..5b3c041 100644 --- a/sigi/apps/ocorrencias/templates/ocorrencias/painel.html +++ b/sigi/apps/ocorrencias/templates/ocorrencias/painel.html @@ -1,198 +1,211 @@ {% extends "admin/base_site.html" %} -{% load i18n admin_static %} -{% load static from staticfiles %} -{% load thumbnail %} - +{% load i18n static admin_urls %} {% block extrastyle %} - {{ block.super }} - -{% endblock %} - -{% block extrahead %} - {{ block.super }} - - - - + {{ block.super }} + {% endblock %} - -{% block coltype %}colMS{% endblock %} - -{% block content_title %}

{{ panel_title }}

{% endblock %} - +{% block breadcrumbs %}{% endblock %} {% block content %} - {% url 'painel-ocorrencias' as url_painel %} -
-
-
- - - - -
-
- - - -
- {% include 'ocorrencias/ocorrencia_form.html' %} -
- - {% if paineis %} - - {% endif %} - -
-
- {% for ocorrencia in ocorrencias %} - {% include 'ocorrencias/ocorrencia_snippet.html' %} - {% empty %} -
-
-

{% trans 'Nenhuma ocorrência encontrada.' %}

-
-
- {% endfor %} -
-
-
- - - +
+

{{ panel_title }}

+
+
+
+ {% for id, text in paineis.items %} + {{ text }} + {% endfor %} +
+
+
+
+ +
+
+ +{% for ocorrencia in ocorrencias %} +
+
+
+
+ + edit + {{ ocorrencia.casa_legislativa.nome }}, {{ ocorrencia.casa_legislativa.municipio.uf.sigla }} +

+ {% blocktrans with data_criacao=ocorrencia.data_criacao tipo_contato=ocorrencia.tipo_contato categoria=ocorrencia.categoria status=ocorrencia.get_status_display %} + Criado em {{ data_criacao }} via {{ tipo_contato }} solicitando {{ categoria }}, com status {{ status }} + {% endblocktrans %} +

+

+ {% trans "Gerentes" %}: + {% for gerente in ocorrencia.casa_legislativa.gerentes_interlegis.all %} +

+ {% if gerente.foto %} + + {% endif %} + {{ gerente.get_apelido }} +
+ {% endfor %} +

+
+
+ +
+

{{ ocorrencia.descricao }}

+
+ {% trans "Prioridade" %}: {{ ocorrencia.get_prioridade_display }} + + {% blocktranslate count counter=ocorrencia.anexo_set.count %}Um anexo{% plural %}{{ counter }} anexos{% endblocktranslate %} + +
+
+
+
+
+
    +
  • + {% if usuario.foto %} + + {% else %} + account_circle + {% endif %} +
    + {% csrf_token %} +

    {% trans "Comentar" %}:

    + {% for field in comentario_form %} + {% if field.name == 'ocorrencia' %} + + {% elif field.name == 'novo_status' %} +
    + +
    + {% else %} +
    + {{ field }} +
    + {% endif %} + {% endfor %} + +
    +
  • + {% for comentario in ocorrencia.comentarios.all %} +
  • + {% if comentario.usuario.foto %} + + {% else %} + account_circle + {% endif %} +

    {% blocktranslate with data=comentario.data_criacao nome=comentario.usuario.get_apelido %} + Em {{ data }}, {{ nome }} disse: + {% endblocktranslate %}

    + {{ comentario.descricao }} + {% if comentario.novo_status %} +

    {% blocktranslate with status=comentario.get_novo_status_display|default:"-" %} + Status: {{ status }} + {% endblocktranslate %}

    + {% endif %} +
  • + {% endfor %} +
+
+
+
+
+{% endfor %} {% endblock %} + +{% block footer %} + {{ block.super }} + +{% endblock %} \ No newline at end of file diff --git a/sigi/apps/ocorrencias/urls.py b/sigi/apps/ocorrencias/urls.py index f98cb08..6398686 100644 --- a/sigi/apps/ocorrencias/urls.py +++ b/sigi/apps/ocorrencias/urls.py @@ -1,19 +1,30 @@ -# coding: utf-8 -from django.conf.urls import patterns, url - - -urlpatterns = patterns( - 'sigi.apps.ocorrencias.views', - # Painel de ocorrencias - url(r'^painel/$', 'painel_ocorrencias', name='painel-ocorrencias'), - url(r'^painel/buscanominal/$', 'busca_nominal', {"origin": "tudo"}, name='painel-buscanominal'), - url(r'^painel/buscanominal/casa/$', 'busca_nominal', {"origin": "casa"}, name='painel-buscacasa'), - url(r'^painel/buscanominal/servidor/$', 'busca_nominal', {"origin": "servidor"}, name='painel-buscaservidor'), - url(r'^painel/buscanominal/servico/$', 'busca_nominal', {"origin": "servico"}, name='painel-buscaservico'), - url(r'^mudaprioridade/$', 'muda_prioridade', name='ocorrencia-mudaprioridade'), - url(r'^excluianexo/$', 'exclui_anexo', name='ocorrencia-excluianexo'), - url(r'^incluianexo/$', 'inclui_anexo', name='ocorrencia-incluianexo'), - url(r'^anexosnippet/$', 'anexo_snippet', name='ocorrencia-anexosnippet'), - url(r'^incluicomentario/$', 'inclui_comentario', name='ocorrencia-incluicomentario'), - url(r'^incluiocorrencia/$', 'inclui_ocorrencia', name='ocorrencia-incluiocorrencia'), -) +from django.urls import path +from sigi.apps.ocorrencias.views import painel_ocorrencias + +urlpatterns = [ + path('painel/', painel_ocorrencias, name='painel-ocorrencias'), +] + + + + + +# # coding: utf-8 +# from django.conf.urls import patterns, url + + +# urlpatterns = patterns( +# 'sigi.apps.ocorrencias.views', +# # Painel de ocorrencias +# url(r'^painel/$', 'painel_ocorrencias', name='painel-ocorrencias'), +# url(r'^painel/buscanominal/$', 'busca_nominal', {"origin": "tudo"}, name='painel-buscanominal'), +# url(r'^painel/buscanominal/casa/$', 'busca_nominal', {"origin": "casa"}, name='painel-buscacasa'), +# url(r'^painel/buscanominal/servidor/$', 'busca_nominal', {"origin": "servidor"}, name='painel-buscaservidor'), +# url(r'^painel/buscanominal/servico/$', 'busca_nominal', {"origin": "servico"}, name='painel-buscaservico'), +# url(r'^mudaprioridade/$', 'muda_prioridade', name='ocorrencia-mudaprioridade'), +# url(r'^excluianexo/$', 'exclui_anexo', name='ocorrencia-excluianexo'), +# url(r'^incluianexo/$', 'inclui_anexo', name='ocorrencia-incluianexo'), +# url(r'^anexosnippet/$', 'anexo_snippet', name='ocorrencia-anexosnippet'), +# url(r'^incluicomentario/$', 'inclui_comentario', name='ocorrencia-incluicomentario'), +# url(r'^incluiocorrencia/$', 'inclui_ocorrencia', name='ocorrencia-incluiocorrencia'), +# ) diff --git a/sigi/apps/ocorrencias/views.py b/sigi/apps/ocorrencias/views.py index 93b910c..81db03a 100644 --- a/sigi/apps/ocorrencias/views.py +++ b/sigi/apps/ocorrencias/views.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from django.http import JsonResponse, Http404 from django.db.models import Q, Count -from django.utils.translation import ungettext, gettext as _ +from django.utils.translation import ngettext, gettext as _ from django.shortcuts import get_object_or_404, render, HttpResponse from django.contrib.auth.decorators import login_required from django.views.decorators.http import require_POST @@ -17,87 +17,92 @@ from django.utils.html import escape @login_required def painel_ocorrencias(request): - tipo = request.GET.get('type', None) - id = request.GET.get('id', None) painel = request.GET.get('painel', None) + id_servidor = request.GET.get('servidor', None) + id_casa = request.GET.get('casa', None) + page = int(request.GET.get('page', '0')) + + paineis = { + 'gerente': _("Casas que gerencio"), + 'registro': _("Ocorrências registrados por mim"), + 'tudo': _("Todas as ocorrências") + } + + if id_servidor is None: + servidor = request.user.servidor + else: + servidor = get_object_or_404(Servidor, id=id_servidor) - data = {} - - if tipo is None or tipo == 'error': - tipo = 'servidor' - u = get_object_or_404(Servidor, user=request.user) - id = u.pk - - if id is None: - raise Http404("id não definido") + if id_casa is not None: + casa = get_object_or_404(Orgao, id=id_casa) + painel = 'tudo' + panel_title = _(f"Ocorrências da {casa.nome}, {casa.municipio.uf.nome}") + else: + casa = None + if servidor: + is_gerente = servidor.casas_que_gerencia.exists() + is_registrador = (servidor.ocorrencia_set.exists() or + servidor.comentario_set.exists()) + panel_title = servidor.nome_completo + else: + is_gerente = False + is_registrador = False + panel_title = _('Todas as ocorrências') + + if (servidor is None) or (not is_gerente and not is_registrador): + painel = 'tudo' + elif not is_gerente and is_registrador: + painel = 'registro' + elif is_gerente: + if painel is None: + painel = 'gerente' - if tipo == 'casa': - casa = get_object_or_404(Orgao, pk=id) - ocorrencias = casa.ocorrencia_set.all() - panel_title = "{casa}, {uf}".format( - casa=casa.nome, - uf=casa.municipio.uf.sigla + if painel == 'gerente': + ocorrencias = Ocorrencia.objects.filter( + casa_legislativa__gerentes_interlegis=servidor) + elif painel == 'registro': + ocorrencias = ( + Ocorrencia.objects.filter(servidor_registro=servidor) | + Ocorrencia.objects.filter(comentarios__usuario=servidor) ) - elif tipo == 'servidor': - servidor = get_object_or_404(Servidor, pk=id) - panel_title = servidor.nome_completo - - paineis = {'gerente': "Minhas casas", 'servico': "Meu setor", - 'timeline': "Comentados por mim"} - - if painel is None: - if Orgao.objects.filter( - gerentes_interlegis=servidor).count() > 0: - painel = 'gerente' - elif Ocorrencia.objects.filter( - setor_responsavel=servidor.servico).count() > 0: - painel = 'servico' - else: - painel = 'timeline' - - data.update({'paineis': paineis, 'painel': painel, - 'servidor': servidor}) - - if painel == 'gerente': - ocorrencias = Ocorrencia.objects.filter( - casa_legislativa__gerentes_interlegis=servidor) - elif painel == 'servico': - ocorrencias = Ocorrencia.objects.filter( - setor_responsavel_id=servidor.servico_id) - else: - ocorrencias = ( - Ocorrencia.objects.filter(servidor_registro=servidor) | - Ocorrencia.objects.filter(comentarios__usuario=servidor) - ) - elif tipo == 'servico': - servico = get_object_or_404(Servico, pk=id) - ocorrencias = servico.ocorrencia_set.all() - panel_title = _("{sigla} - {nome}").format( - sigla=servico.sigla, nome=servico.nome) + else: # Tudo... + if casa is None: # ...de todas as Casas... + ocorrencias = Ocorrencia.objects.all() + else: # ... ou da Casa escolhida + ocorrencias = casa.ocorrencia_set.all() ocorrencias = ocorrencias.filter(status__in=[1, 2]) ocorrencias = ocorrencias.order_by('prioridade', '-data_modificacao') ocorrencias = ocorrencias.select_related( - 'casa_legislativa', 'categoria', 'tipo_contato', 'servidor_registro', - 'setor_responsavel', 'casa_legislativa__gerentes_interlegis' + 'casa_legislativa', 'casa_legislativa__municipio', + 'casa_legislativa__municipio__uf', 'categoria', 'tipo_contato', + 'servidor_registro', ) ocorrencias = ocorrencias.prefetch_related( - 'comentarios', 'comentarios__usuario', 'comentarios__encaminhar_setor', - 'casa_legislativa__municipio', 'casa_legislativa__municipio__uf', - 'anexo_set' + 'comentarios', 'comentarios__usuario', 'anexo_set', + 'casa_legislativa__gerentes_interlegis' ) ocorrencias = ocorrencias.annotate(total_anexos=Count('anexo')) - data.update( - {'ocorrencias': ocorrencias, - 'panel_title': panel_title, - 'comentario_form': ComentarioForm(), - 'ocorrencia_form': OcorrenciaForm(), - 'PRIORITY_CHOICES': Ocorrencia.PRIORITY_CHOICES - } - ) + if page * 100 > ocorrencias.count(): + ocorrencias = ocorrencias[-100] + else: + ocorrencias = ocorrencias[page * 100:page * 100 + 100] + + + context = { + 'paineis': paineis, + 'painel': painel, + 'servidor': servidor, + 'casa': casa, + 'ocorrencias': ocorrencias, + 'panel_title': panel_title, + 'comentario_form': ComentarioForm(), + 'ocorrencia_form': OcorrenciaForm(), + 'PRIORITY_CHOICES': Ocorrencia.PRIORITY_CHOICES + } - return render(request, 'ocorrencias/painel.html', data) + return render(request, 'ocorrencias/painel.html', context) @login_required def busca_nominal(request, origin="tudo"): @@ -161,7 +166,7 @@ def exclui_anexo(request): ocorrencia = anexo.ocorrencia anexo.delete() - link_label = (ungettext('%s arquivo anexo', '%s arquivos anexos', ocorrencia.anexo_set.count()) % + link_label = (ngettext('%s arquivo anexo', '%s arquivos anexos', ocorrencia.anexo_set.count()) % (ocorrencia.anexo_set.count(),)) painel = render_to_string('ocorrencias/anexos_snippet.html', {'ocorrencia': ocorrencia}, diff --git a/sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/change_form.html b/sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/change_form.html deleted file mode 100644 index 30bf990..0000000 --- a/sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/change_form.html +++ /dev/null @@ -1 +0,0 @@ -{% extends "change_form_with_report_and_labels.html" %} diff --git a/sigi/apps/servidores/models.py b/sigi/apps/servidores/models.py index 1c599fa..21d4970 100644 --- a/sigi/apps/servidores/models.py +++ b/sigi/apps/servidores/models.py @@ -71,6 +71,13 @@ class Servidor(models.Model): Servidor.objects.filter(user=self.user).update(user=None) return super(Servidor, self).save(*args, **kwargs) + def get_apelido(self): + if self.apelido: + return self.apelido + else: + nomes = self.nome_completo.split(' ') + return nomes[0] + # Soluçao alternativa para extender o usuário do django # Acessa do servidor de um objeto user criando um profile # baseado nos dados do LDAP diff --git a/sigi/apps/utils/__init__.py b/sigi/apps/utils/__init__.py index 473bbc2..6a1b8f7 100644 --- a/sigi/apps/utils/__init__.py +++ b/sigi/apps/utils/__init__.py @@ -32,7 +32,8 @@ def to_ascii(txt, codif='utf-8'): txt = force_str(txt) if isinstance(txt, str): txt = txt.encode('utf-8') - return normalize('NFKD', txt.decode(codif)).encode('ASCII', 'ignore') + return normalize('NFKD', txt.decode(codif)).encode( + 'ASCII', 'ignore').decode(codif) def queryset_ascii(self, request): diff --git a/sigi/settings/base.py b/sigi/settings/base.py index 4c71e81..e4831de 100644 --- a/sigi/settings/base.py +++ b/sigi/settings/base.py @@ -69,6 +69,7 @@ TEMPLATES = [ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + 'django.template.context_processors.media', ], }, }, @@ -102,6 +103,12 @@ STATIC_URL = 'static/' STATICFILES_DIRS = [BASE_DIR / "static",] STATIC_ROOT = '/var/www/sigi/static/' +# Media files +# https://docs.djangoproject.com/en/4.0/topics/files/#managing-files + +MEDIA_ROOT = BASE_DIR / '../media' +MEDIA_URL = "media/" + # Default primary key field type # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field diff --git a/sigi/settings/menu_conf.yaml b/sigi/settings/menu_conf.yaml index 18f2642..4a2b1e4 100644 --- a/sigi/settings/menu_conf.yaml +++ b/sigi/settings/menu_conf.yaml @@ -48,7 +48,7 @@ main_menu: icon: comment children: - title: Painel de ocorrências - view_name: + view_name: painel-ocorrencias - title: Registro de ocorrências view_name: admin:ocorrencias_ocorrencia_changelist querystr: minhas=S&status__in=1,2 diff --git a/sigi/static/css/dashboard.css b/sigi/static/css/dashboard.css new file mode 100644 index 0000000..b418f52 --- /dev/null +++ b/sigi/static/css/dashboard.css @@ -0,0 +1,37 @@ +table.servicos { + width: 100%; +} + +table.numeros>tbody>tr>td { + text-align: right; +} +.app>.card>.card-content>.card-title { + font-size: 16px; + font-weight: bolder; +} + +.full-preloader { + width: 100%; + height: 100%; + background-color: rgb(255,255,255,0.8); + position: absolute; + z-index: 2; + display: flex; + align-items: center; + justify-content: space-around; +} + +.user_thumb { + width: 24px; + height: 24px; + margin-right: 1rem; +} + +.gerente_selector { + width: 100%; + border: none; +} + +.card-links { + padding: 0 24px; +} \ No newline at end of file diff --git a/sigi/static/img/ilsombra.png b/sigi/static/img/ilsombra.png new file mode 100644 index 0000000000000000000000000000000000000000..309c1e7061a27692ec4716e2a599dc78e9a5d8bf GIT binary patch literal 2121 zcmV-P2)6f$P)004Lh0ssI2`oL~D00009a7bBm000XU z000XU0RWnu7ytkO2XskIMF-yq4h=dW=?qm$0000PbVXQnLvL+uWo~o;Lvm$dbY)~9 zcWHEJAV*0}P*;Ht7XSbU!%0LzRCwC$oymUMNDzm+TdfVT$}+}4aBO^i`9W7|4?B2|SJRC`!;K zNd5)fH>gfAJeShQi`XUz5JF(KB^Lk&z#sq&0>B^u33XBt1i&ZJaxZHdJFt}&{Two<& z!R6+Q05Bw302q=i01Qdi%MTg=pU`{-NU{ha0QfW+R+2CQY?w$A06rC95pIK03V_QD z7+f@1@M-?9I3#@E55w?z*ov+qT!MRS*RK`<1TO+MTv( zn#VjH5dce^GA#ap=kxjO=_!umU;ftfT+efD+wS-KjYb0i7byla#vUIZr<2J`biXSM zf=?em&gb(Y1E!S&Jb9cf7mM_thUsLoTr3Lz{1Nv4y`7bb^7z*zvx6#~3kE^{}rI?m2!s%axF~&WMmMaDL_;)K&DSc@=j$`h-{W8A{ix1!*|YE6I9Ruh+|wdKgCCytv4^?5?u;asw>O^37-@Wm*2Knl`!_ zWo_-?4p>#yDgs6bX}W%Yf3K$dfH#}X@pzo0FCxrX_Bug^#o2;+w(Q8VJRXnd_T09= zzEqr&6{Xwlw%hG|ZCN4j7_3zpFhYnBa@oDSur9XS?RK--?}IQ5Fvha1Xqwh+H4Vea z-Eqeli@3UIjBwQff5+TvwOXy#>%MFhMSRZ{Dw@i%-Hvk%K7+UiU?WS>4FDG}0M}{; z04}u!*8y;OEm)RizBMc%lB~mtF2=bZQ?TFj^v{MN!l#@w;2;P-ynp}u1pAM7@AAF4 z&lj)&85VyReXutpNs@d^9qx-@LktTpKn%`zG{N_Mj*DPLQMi{liebUI0S8=DXfBh3 zMFgCt44Dtrp6h_f&I~vVLdNM>FvhqJfPaiw(jjM?dBL*}IQr)X_bo!v03WEP zu_VdrZ(Y~r^>$3t6e@!e0r25_a55elJ@SNVZppG-uh#+akAPG3Ryr9i(&YrW-RX#v zKZ^pGrifUslL2rThFlJPV}#r7wrGn?0kY^6fWt6IzkAOs0N0Ef_b%w7S};Orv)u{~ zShV!2Fj@4ocH5oEfK^p(zIIn=NdTuDG2}SAG^=68nD24eX0R?VMN6+l0i1HgP?98R z1}M3%%hM?Y)5TD|&;`IXGq64QKz0E{t~6IveQR*=z?2wm^#S6sllrlyssfycvxI&#lA*tm~Qne)jbAUn5?nD001)u6jKI zw*^aO!3d!+48KR>BuUoi0^I4C7a51K0pM?dvo<4soz0%p>GgVjp6|}lHT}AOjS3}E z0Aq}^o|ulJX!i7k5c1D*3`tehw|BRM5CEK0_3Q^~7mJ1O`<7)%GmqnNIVa z6ZBm7;o-qFP0OI`AoJLfDuBicDvqac%FC6_rtob zt7)35su<&M<6p&?FbIGVLP(M%(GJYRG6@8LK>!#8fI$El1b_iB2mpfsFbDvH05Avu zg8(o929-$PwhJdtM1ggQ6eT|b-UJCelv3&+C%OzI3cVqU00000NkvXXu0mjfabx7n literal 0 HcmV?d00001 diff --git a/sigi/static/js/dashboard.js b/sigi/static/js/dashboard.js new file mode 100644 index 0000000..fbc50fc --- /dev/null +++ b/sigi/static/js/dashboard.js @@ -0,0 +1,72 @@ +$(document).ready(function () { + Chart.defaults.plugins.legend.labels.usePointStyle = true; + setlinks(); + $("div[data-source]").each(function(index, container) { + var container = $(container); + var url = container.attr('data-source'); + get_content(container, url); + }); + $("canvas[data-source]").each(function(index, canvas) { + var canvas = $(canvas) + var url = canvas.attr("data-source"); + plot_chart(canvas, url); + }); +}); + +function setlinks() { + $('.modal').modal(); + $('.dropdown-trigger').dropdown(); + $('.collapsible').collapsible(); + $("a[data-target]").off('click').on('click', function(e) { + e.preventDefault(); + var $this = $(this); + var target = $("#"+$this.attr('data-target')); + var url = $this.attr('href'); + if (target.is("canvas")) { + plot_chart(target, url); + } else if (target.is("div")) { + get_content(target, url); + } + }); +} + +function get_content(container, url) { + container.closest('.card').find('.full-preloader').removeClass('hide'); + $.get(url, function(data) { + container.html(data); + container.closest('.card').find('.full-preloader').addClass('hide'); + setlinks(); + }).fail(function() { + container.closest('.card').find('.full-preloader').html("Ocorreu um erro. Tente recarregar a página"); + }); +} + +function plot_chart(canvas, url) { + canvas.closest('.card').find('.full-preloader').removeClass('hide'); + $.get(url, function(data) { + var chart_name = canvas.attr("data-chart-name"); + var has_action_links = canvas.attr("data-has-action-links"); + + var new_canvas = $(canvas.clone()).insertBefore(canvas); + canvas.remove(); + canvas = new_canvas; + canvas.removeClass("hide"); + + var ctx = canvas.get(0).getContext("2d"); + var myChart = new Chart(ctx, data); + + if (has_action_links) { + if (data.actionblock) { + $(`#${chart_name}-action-links`).html(data.actionblock).removeClass("hide"); + } else { + $(`#${chart_name}-previlink`).attr('href', data.prevlink); + $(`#${chart_name}-nextlink`).attr('href', data.nextlink); + $(`#${chart_name}-action-links`).removeClass("hide"); + } + } + setlinks(); + canvas.closest('.card').find('.full-preloader').addClass('hide'); + }).fail(function() { + canvas.closest('.card').find('.full-preloader').html("Ocorreu um erro. Tente recarregar a página"); + }); +} \ No newline at end of file diff --git a/sigi/templates/admin/base_site.html b/sigi/templates/admin/base_site.html new file mode 100644 index 0000000..cef458d --- /dev/null +++ b/sigi/templates/admin/base_site.html @@ -0,0 +1,29 @@ +{% extends "admin/base_site.html" %} +{% load static i18n %} + +{% block theme %} + + +{% endblock %} + +{% block userlinks %} + {% block welcome-msg %}{% endblock %} + {% if site_url %} + {% trans 'Mapa' %} + {% endif %} + {% if user.is_active and user.is_staff %} + {% url 'django-admindocs-docroot' as docsroot %} + {% if docsroot %} + {% trans 'Documentation' %} + {% endif %} + {% endif %} + {% if user.has_usable_password %} + + {% trans 'Change password' %} + + {% endif %} + + {% trans 'Log out' %} + + +{% endblock %} \ No newline at end of file diff --git a/sigi/templates/admin/change_list.html b/sigi/templates/admin/change_list.html index 3881db0..c7e446f 100644 --- a/sigi/templates/admin/change_list.html +++ b/sigi/templates/admin/change_list.html @@ -8,16 +8,71 @@ .side-wrapper { width: 100%; } - - - {% endblock extrastyle %} -{% block breadcrumbs %}{% endblock %} +{% block object-tools %} +
+ + build + +
    + {% block object-tools-items %} +
  • + + + +
  • + {% if has_add_permission %} +
  • + {% url cl.opts|admin_urlname:'add' as add_url %} + + + +
  • + {% endif %} + {% endblock %} +
+
+{% endblock %} + +{% block side_wrapper %} +
+
+
+ {% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %} +
+
+ {% block search %}{% search_form cl %}{% endblock %} +
+
+ {% block filters %} + {{ block.super }} + {% endblock %} +
+
+
+{% endblock %} {% block footer %} {{ block.super }} @@ -25,84 +80,17 @@ $(document).ready(function(){ $('.fixed-action-btn').floatingActionButton(); M.Tooltip.init($('.tooltipped'), {}); - }); - - - {% endblock footer %} -{% block content %} -
- {% block object-tools %} -
- - build - -
    -
  • - - - -
  • - {% block object-tools-items %} - {% if has_add_permission %} -
  • - {% url cl.opts|admin_urlname:'add' as add_url %} - - - -
  • - {% endif %} - {% endblock %} -
-
- {% endblock %} - {% if cl.formset.errors %} -

- {% if cl.formset.total_error_count == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %} -

- {{ cl.formset.non_form_errors }} - {% endif %} -
- - {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %} - -
{% csrf_token %} - {% if cl.formset %} -
{{ cl.formset.management_form }}
- {% endif %} - - {% block result_list %} - {% result_list cl %} - {% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %} - {% endblock %} - {% block pagination %}{% pagination cl %}{% endblock %} -
-
-
    -
    -
    - {% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %} -
    -
    - {% block search %}{% search_form cl %}{% endblock %} -
    -
    - {% block filters %} - {% if cl.has_filters %} -
    - - {% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %} -
    - {% endif %} - {% endblock %} -
    -
    -
-
-{% endblock %} diff --git a/sigi/templates/material/admin/index.html b/sigi/templates/material/admin/index.html index 9ab178a..e9d7e79 100644 --- a/sigi/templates/material/admin/index.html +++ b/sigi/templates/material/admin/index.html @@ -1,6 +1,21 @@ {% extends "admin/index.html" %} -{% load static %} +{% load static i18n %} - {% block content %} - {% include 'sigi/snippets/dashboard.html' %} +{% block extrastyle %} + {{ block.super }} + {% endblock %} + +{% block extrahead %} + + {{ block.super }} +{% endblock %} + +{% block content %} + {% include 'sigi/snippets/dashboard.html' %} +{% endblock %} + +{% block footer %} + {{ block.super }} + +{% endblock footer %} diff --git a/sigi/templates/material/admin/side_nav.html b/sigi/templates/material/admin/side_nav.html index e124f80..d626556 100644 --- a/sigi/templates/material/admin/side_nav.html +++ b/sigi/templates/material/admin/side_nav.html @@ -1,31 +1,21 @@ {% load i18n material menus %}
- {% if not mobile %} - - {% endif %} - {% if available_apps %} -
    -
  • -
    -
    - {% if 'profile/user_picture.html'|template_exists %} - {% include 'profile/user_picture.html' %} - {% else %} - {% include 'material/admin/user_picture.html' %} - {% endif %} -
    -
    -
  • - {% show_menu 'main_menu' %} -
- {% else %} -
-
-
-

{% trans "You don't have permission to view or edit anything." %} -

-
-
- {% endif %} + {% if not mobile %} + + {% endif %} +
    +
  • +
    +
    + {% if 'profile/user_picture.html'|template_exists %} + {% include 'profile/user_picture.html' %} + {% else %} + {% include 'material/admin/user_picture.html' %} + {% endif %} +
    +
    +
  • + {% show_menu 'main_menu' %} +
diff --git a/sigi/templates/material/admin/user_picture.html b/sigi/templates/material/admin/user_picture.html new file mode 100644 index 0000000..52ddfbf --- /dev/null +++ b/sigi/templates/material/admin/user_picture.html @@ -0,0 +1,21 @@ +{% load static %} + +{% block user_profile %} + {% if user.servidor.foto %} + + {% elif profile_picture %} + + {% else %} + + {% endif %} + + {% if profile_bg %} + profile background + {% else %} + profile background + {% endif %} +
+ {% firstof user.get_short_name user.username|default_if_none:'' %}
+ {{ user.email|default_if_none:'' }} +
+{% endblock %} \ No newline at end of file diff --git a/sigi/templates/pdf/base_report.html b/sigi/templates/pdf/base_report.html index 74fd9d4..99707e8 100644 --- a/sigi/templates/pdf/base_report.html +++ b/sigi/templates/pdf/base_report.html @@ -41,6 +41,15 @@ tr:nth-child(even) { .title_row { background-color: #b2b2b2; } +.right-align { + text-align: right; +} +ul { + list-style-type: none; + margin: 0px; + padding: 0px; +} + {% endblock extra_style %} {% block body_content %} @@ -48,7 +57,7 @@ tr:nth-child(even) {

{% trans 'SENADO FEDERAL' %}

{% trans 'Instituto Legislativo Brasileiro - ILB' %}

{% trans "INTERLEGIS" %}

-

{{ title|upper }}

{% endblock header %} +

{% block report_name %}{{ title|upper }}{% endblock report_name %}

{% endblock header %}