Browse Source

Relatório de eventos por UF

pull/172/head 3.0.79
Sesóstris Vieira 9 months ago
parent
commit
ce9b6fa3f5
  1. 1
      sigi/apps/eventos/admin_urls.py
  2. 52
      sigi/apps/eventos/forms.py
  3. 93
      sigi/apps/eventos/templates/eventos/eventos_por_uf.html
  4. 48
      sigi/apps/eventos/templates/eventos/eventos_por_uf_pdf.html
  5. 105
      sigi/apps/eventos/templates/eventos/snippets/eventos_por_uf_snippet.html
  6. 255
      sigi/apps/eventos/views.py
  7. 27
      sigi/menu_conf.yaml

1
sigi/apps/eventos/admin_urls.py

@ -6,4 +6,5 @@ urlpatterns = [
path( path(
"alocacaoequipe/", views.alocacao_equipe, name="eventos_alocacaoequipe" "alocacaoequipe/", views.alocacao_equipe, name="eventos_alocacaoequipe"
), ),
path("eventosporuf/", views.eventos_por_uf, name="eventos_eventosporuf"),
] ]

52
sigi/apps/eventos/forms.py

@ -5,10 +5,18 @@ from django.core.files.base import File
from django.db.models.base import Model from django.db.models.base import Model
from django.forms.utils import ErrorList from django.forms.utils import ErrorList
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from material.admin.widgets import MaterialAdminTextareaWidget from material.admin.widgets import (
MaterialAdminTextareaWidget,
MaterialAdminDateWidget,
)
from sigi.apps.casas.models import Funcionario, Orgao from sigi.apps.casas.models import Funcionario, Orgao
from sigi.apps.espacos.models import Espaco, Reserva from sigi.apps.espacos.models import Espaco, Reserva
from sigi.apps.eventos.models import Convite, ModeloDeclaracao, Evento from sigi.apps.eventos.models import (
Convite,
ModeloDeclaracao,
Evento,
TipoEvento,
)
from sigi.apps.parlamentares.models import Parlamentar from sigi.apps.parlamentares.models import Parlamentar
@ -115,6 +123,46 @@ class SelecionaModeloForm(forms.Form):
) )
class EventosPorUfForm(forms.Form):
MODO_CHOICES = (
("V", _("Virtual")),
("P", _("Presencial")),
)
data_inicio = forms.DateField(
required=True,
label=_("data de início"),
widget=MaterialAdminDateWidget,
)
data_fim = forms.DateField(
required=True,
label=_("data de término"),
widget=MaterialAdminDateWidget,
)
categoria = forms.MultipleChoiceField(
required=False,
label=_("Categoria"),
choices=TipoEvento.CATEGORIA_CHOICES,
widget=forms.CheckboxSelectMultiple,
)
virtual = forms.MultipleChoiceField(
required=False,
label=_("Modo"),
choices=MODO_CHOICES,
widget=forms.CheckboxSelectMultiple,
)
class Media:
css = {"all": ["css/change_form.css"]}
js = [
"admin/js/vendor/select2/select2.full.js",
"admin/js/change_form.js",
"admin/js/vendor/select2/i18n/pt-BR.js",
"material/admin/js/widgets/TimeInput.js",
"admin/js/core.js",
"/admin/jsi18n/",
]
class ConviteForm(forms.ModelForm): class ConviteForm(forms.ModelForm):
class Meta: class Meta:
model = Convite model = Convite

93
sigi/apps/eventos/templates/eventos/eventos_por_uf.html

@ -0,0 +1,93 @@
{% extends "admin/base_site.html" %}
{% load static i18n %}
{% block extrastyle %}
{{ block.super }}
<style type="text/css">
#content {
display: block;
}
.table-responsive {
overflow: auto;
width: 100%;
}
table {
table-layout: auto !important;
width: 100%;
}
table.fixed {
table-layout: fixed;
}
th.sep_regiao {
text-align: center;
text-transform: uppercase;
}
tr:nth-child(even) {
background: var(--body-bg);
}
.numero {
text-align: right;
}
</style>
{% endblock %}
{% block extrahead %}
{{ block.super }}
{% endblock %}
{% block coltype %}colMS{% endblock %}
{% block content_title %}
<h5>{% trans 'Eventos por Unidade da Federação' %}</h5>
{% if data_inicio %}
<h6>
{% blocktranslate with inicio=data_inicio|date:"SHORT_DATE_FORMAT" fim=data_fim|date:"SHORT_DATE_FORMAT" %}
Período: {{ inicio }} a {{ fim }}
{% endblocktranslate %}
</h6>
{% endif %}
{% endblock %}
{% block breadcrumbs %}{% endblock %}
{% block content %}
<div class="row">
<div class="col s12">
<div class="card">
<form>
<div class="card-content">
{{ form }}
</div>
<div class="card-action">
<button type="submit" class="waves-effect waves-light btn">{% trans 'Pesquisar' %}</button>
{% if not pivo_uf is None %}
<div class="fixed-action-btn">
<a class="btn-floating">
<i class="large material-icons">print</i>
</a>
<ul>
<li><button type="submit" name="fmt" value="pdf" class="btn-floating" title="{% trans 'Exportar para PDF' %}"><i class="material-icons">picture_as_pdf</i></button></li>
<li><button type="submit" name="fmt" value="csv" class="btn-floating" title="{% trans 'Exportar para CSV' %}"><i class="material-icons">file_download</i></button></li>
</ul>
</div>
{% endif %}
</div>
</form>
</div>
</div>
</div>
{% if not pivo_uf is None %}
{% include "eventos/snippets/eventos_por_uf_snippet.html" with mode="html" %}
{% endif %}
{% endblock %}
{% block footer %}
{{ block.super }}
{{ form.media }}
<script>
$(document).ready(function(){
M.FloatingActionButton.init($('.fixed-action-btn'), {hoverEnabled: false});
M.Modal.init($(".modal"));
})
</script>
{% endblock %}

48
sigi/apps/eventos/templates/eventos/eventos_por_uf_pdf.html

@ -0,0 +1,48 @@
{% extends "pdf/base_report.html" %}
{% load static %}
{% load i18n %}
{% block page_size %}A4 landscape{% endblock page_size %}
{% block extra_style %}
{{ block.super }}
a {
color: black;
text-decoration: none;
}
h4 {
padding: 24px 0 24px 0;
line-height: 1.5em;
}
.row {
margin-bottom: 12px;
}
.card-title {
font-size: 1.2em;
margin: 20px 4px;
padding-left: 1.5rem;
border-left: 5px solid #ee6e73;
}
.sep_regiao {
text-align: center;
text-transform: uppercase;
}
.center {
text-align: center;
}
.numero {
text-align: right;
}
{% endblock %}
{% block main_content %}
<h4>
{% blocktranslate with inicio=data_inicio|date:"SHORT_DATE_FORMAT" fim=data_fim|date:"SHORT_DATE_FORMAT" %}
Período: {{ inicio }} a {{ fim }}
{% endblocktranslate %}
<br/>
{% trans 'Categoria(s)' %}: {{ categorias|join:", " }}<br/>
{% trans 'Modo(s)' %}: {{ virtual|join:", " }}<br/>
</h4>
{% include "eventos/snippets/eventos_por_uf_snippet.html" %}
{% endblock %}

105
sigi/apps/eventos/templates/eventos/snippets/eventos_por_uf_snippet.html

@ -0,0 +1,105 @@
{% load i18n %}
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">{% trans 'Resumo por Região' %}</span>
<div class="table-responsive">
<table class="striped">
<thead>
<tr>
<th rowspan="2">{% trans 'Região' %}</th>
{% for label, items in cabecalho_regiao %}
<th colspan="{{ items|length }}" class="center">{{ label|capfirst }}</th>
{% endfor %}
</tr>
<tr>
{% for top, items in cabecalho_regiao %}
{% for label in items %}
<th class="numero">{{ label }}</th>
{% endfor %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for datarow in pivo_regiao.itertuples %}
<tr>
{% for datacol in datarow %}
{% if forloop.first %}
<th>{{ datacol }}</th>
{% else %}
<td class="numero">{{ datacol|default:"-" }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
<tr>
<th>{% trans 'Sumário' %}</th>
{% for total in total_regiao %}
<th class="numero">{{ total }}</th>
{% endfor %}
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">{% trans 'Resumo por Unidade da Federação' %}</span>
<div class="table-responsive">
<table class="striped">
<thead>
<tr>
<th rowspan="2">{% trans 'Unidade Federativa' %}</th>
{% for label, items in cabecalho_uf %}
<th colspan="{{ items|length }}" class="center">{{ label|capfirst }}</th>
{% endfor %}
</tr>
<tr>
{% for top, items in cabecalho_uf %}
{% for label in items %}
<th class="numero">{{ label }}</th>
{% endfor %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for datarow in pivo_uf.itertuples %}
{% ifchanged datarow.Index.0 %}
<tr>
{% with l1=cabecalho_uf.0.1|length l2=cabecalho_uf.1.1|length l3=cabecalho_uf.2.1|length l4=cabecalho_uf.3.1|length %}
<th class="sep_regiao" colspan="{{ l1|add:l2|add:l3|add:l4|add:1 }}">{{ datarow.Index.0 }}</th>
{% endwith %}
</th>
</tr>
{% endifchanged %}
<tr>
{% for datacol in datarow %}
{% if forloop.first %}
<th>{{ datacol.1 }}</th>
{% else %}
<td class="numero">{{ datacol|default:"-" }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
<tr>
<th>{% trans 'Sumário' %}</th>
{% for total in total_uf %}
<th class="numero">{{ total }}</th>
{% endfor %}
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

255
sigi/apps/eventos/views.py

@ -1,7 +1,10 @@
import calendar import calendar
import csv import csv
import datetime
import locale import locale
import pandas as pd
from functools import reduce from functools import reduce
from itertools import groupby
from rest_framework import mixins, generics from rest_framework import mixins, generics
from typing import OrderedDict from typing import OrderedDict
from django import forms from django import forms
@ -9,6 +12,7 @@ from django.contrib import messages
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.db.models import Count, Sum, Q
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect, render, get_object_or_404 from django.shortcuts import redirect, render, get_object_or_404
from django.template import Template, Context from django.template import Template, Context
@ -27,11 +31,13 @@ from django_weasyprint.utils import django_url_fetcher
from django_weasyprint.views import WeasyTemplateResponse from django_weasyprint.views import WeasyTemplateResponse
from weasyprint import HTML from weasyprint import HTML
from sigi.apps.casas.models import Funcionario, Orgao from sigi.apps.casas.models import Funcionario, Orgao
from sigi.apps.contatos.models import UnidadeFederativa
from sigi.apps.convenios.models import Projeto from sigi.apps.convenios.models import Projeto
from sigi.apps.eventos.models import TipoEvento, Evento from sigi.apps.eventos.models import TipoEvento, Evento
from sigi.apps.eventos.forms import ( from sigi.apps.eventos.forms import (
SelecionaModeloForm, SelecionaModeloForm,
ConviteForm, ConviteForm,
EventosPorUfForm,
CasaForm, CasaForm,
FuncionarioForm, FuncionarioForm,
ParlamentarForm, ParlamentarForm,
@ -345,7 +351,7 @@ def alocacao_equipe(request):
context["title"] = _("Alocação de equipe") context["title"] = _("Alocação de equipe")
context["pdf"] = True context["pdf"] = True
return WeasyTemplateResponse( return WeasyTemplateResponse(
# filename="alocacao_equipe.pdf", filename="alocacao_equipe.pdf",
request=request, request=request,
template="eventos/alocacao_equipe_pdf.html", template="eventos/alocacao_equipe_pdf.html",
context=context, context=context,
@ -364,6 +370,253 @@ def alocacao_equipe(request):
return render(request, "eventos/alocacao_equipe.html", context) return render(request, "eventos/alocacao_equipe.html", context)
@login_required
@staff_member_required
def eventos_por_uf(request):
formato = request.GET.get("fmt", "html")
initials = {
"data_inicio": datetime.date.today().replace(day=1),
"data_fim": datetime.date.today().replace(
day=calendar.monthrange(
datetime.date.today().year, datetime.date.today().month
)[1]
),
"categoria": [c[0] for c in TipoEvento.CATEGORIA_CHOICES],
"virtual": [m[0] for m in EventosPorUfForm.MODO_CHOICES],
}
if "data_inicio" in request.GET or "data_fim" in request.GET:
form = EventosPorUfForm(request.GET)
else:
form = EventosPorUfForm(initial=initials)
if not form.is_valid():
return render(
request, "eventos/eventos_por_uf.html", context={"form": form}
)
data_inicio = form.cleaned_data.get("data_inicio")
data_fim = form.cleaned_data.get("data_fim")
categorias = form.cleaned_data.get("categoria", initials["categoria"])
virtual = form.cleaned_data.get("virtual", initials["virtual"])
annotates = dict()
aggfuncs = dict()
if "P" in virtual:
annotates["eventos_presenciais"] = Count(
"municipio__orgao__evento__id",
distinct=True,
filter=Q(municipio__orgao__evento__virtual=False),
)
annotates["participantes_presenciais"] = Sum(
"municipio__orgao__evento__total_participantes",
filter=Q(municipio__orgao__evento__virtual=False),
)
aggfuncs["nº eventos presenciais"] = sum
aggfuncs["participantes presenciais"] = sum
if "V" in virtual:
annotates["eventos_virtuais"] = Count(
"municipio__orgao__evento__id",
distinct=True,
filter=Q(municipio__orgao__evento__virtual=True),
)
annotates["participantes_virtuais"] = Sum(
"municipio__orgao__evento__total_participantes",
filter=Q(municipio__orgao__evento__virtual=True),
)
aggfuncs["nº eventos virtuais"] = sum
aggfuncs["participantes virtuais"] = sum
eventos = (
UnidadeFederativa.objects.filter(
municipio__orgao__evento__status=Evento.STATUS_REALIZADO,
municipio__orgao__evento__data_inicio__range=(
data_inicio,
data_fim,
),
municipio__orgao__evento__tipo_evento__categoria__in=categorias,
)
.order_by("regiao", "nome")
.values(
"regiao",
"nome",
"municipio__orgao__evento__tipo_evento__categoria",
)
.annotate(**annotates)
)
df = pd.DataFrame(eventos)
if df.empty:
messages.add_message(
request,
messages.ERROR,
_("Nenhum evento foi realizado no período solicitado"),
)
return render(
request, "eventos/eventos_por_uf.html", context={"form": form}
)
# Renomeia colunas
df.rename(
columns={
"municipio__orgao__evento__tipo_evento__categoria": "categoria",
"eventos_presenciais": "nº eventos presenciais",
"eventos_virtuais": "nº eventos virtuais",
"participantes_presenciais": "participantes presenciais",
"participantes_virtuais": "participantes virtuais",
},
inplace=True,
)
# Troca a sigla pelo nome da região
for sigla, nome in UnidadeFederativa.REGIAO_CHOICES:
df["regiao"].replace(sigla, nome, inplace=True)
# Troca o código pelo nome da categoria de eventos
for cod, nome in TipoEvento.CATEGORIA_CHOICES:
df["categoria"].replace(cod, nome, inplace=True)
# Cria tabela pivot das UFs
pivo_uf = df.pivot_table(
index=["regiao", "nome"],
columns="categoria",
aggfunc=aggfuncs,
fill_value=0,
)
if len(categorias) > 1:
# calcula os totais de eventos e de participantes para as UFs
ix_eventos_presenciais = [
i for i in pivo_uf.columns if i[0] == "nº eventos presenciais"
]
ix_eventos_virtuais = [
i for i in pivo_uf.columns if i[0] == "nº eventos virtuais"
]
ix_participantes_presenciais = [
i for i in pivo_uf.columns if i[0] == "participantes presenciais"
]
ix_participantes_virtuais = [
i for i in pivo_uf.columns if i[0] == "participantes virtuais"
]
if ix_eventos_presenciais:
pivo_uf[("nº eventos presenciais", "total")] = pivo_uf[
ix_eventos_presenciais
].sum(axis=1)
ix_eventos_presenciais.append(("nº eventos presenciais", "total"))
if ix_eventos_virtuais:
pivo_uf[("nº eventos virtuais", "total")] = pivo_uf[
ix_eventos_virtuais
].sum(axis=1)
ix_eventos_virtuais.append(("nº eventos virtuais", "total"))
if ix_participantes_presenciais:
pivo_uf[("participantes presenciais", "total")] = pivo_uf[
ix_participantes_presenciais
].sum(axis=1)
ix_participantes_presenciais.append(
("participantes presenciais", "total")
)
if ix_participantes_virtuais:
pivo_uf[("participantes virtuais", "total")] = pivo_uf[
ix_participantes_virtuais
].sum(axis=1)
ix_participantes_virtuais.append(
("participantes virtuais", "total")
)
pivo_uf = pivo_uf[
ix_eventos_presenciais
+ ix_eventos_virtuais
+ ix_participantes_presenciais
+ ix_participantes_virtuais
]
# Cria tabela pivot das regiões
pivo_regiao = df.pivot_table(
index="regiao",
columns="categoria",
aggfunc=aggfuncs,
fill_value=0,
)
# Calcula os totais de eventos e participantes para as regiões
if len(categorias) > 1:
ix_eventos_presenciais = [
i for i in pivo_regiao.columns if i[0] == "nº eventos presenciais"
]
ix_eventos_virtuais = [
i for i in pivo_regiao.columns if i[0] == "nº eventos virtuais"
]
ix_participantes_presenciais = [
i
for i in pivo_regiao.columns
if i[0] == "participantes presenciais"
]
ix_participantes_virtuais = [
i for i in pivo_regiao.columns if i[0] == "participantes virtuais"
]
if ix_eventos_presenciais:
pivo_regiao[("nº eventos presenciais", "total")] = pivo_regiao[
ix_eventos_presenciais
].sum(axis=1)
ix_eventos_presenciais.append(("nº eventos presenciais", "total"))
if ix_eventos_virtuais:
pivo_regiao[("nº eventos virtuais", "total")] = pivo_regiao[
ix_eventos_virtuais
].sum(axis=1)
ix_eventos_virtuais.append(("nº eventos virtuais", "total"))
if ix_participantes_presenciais:
pivo_regiao[("participantes presenciais", "total")] = pivo_regiao[
ix_participantes_presenciais
].sum(axis=1)
ix_participantes_presenciais.append(
("participantes presenciais", "total")
)
if ix_participantes_virtuais:
pivo_regiao[("participantes virtuais", "total")] = pivo_regiao[
ix_participantes_virtuais
].sum(axis=1)
ix_participantes_virtuais.append(
("participantes virtuais", "total")
)
pivo_regiao = pivo_regiao[
ix_eventos_presenciais
+ ix_eventos_virtuais
+ ix_participantes_presenciais
+ ix_participantes_virtuais
]
# Cabeçalhos para impressão
cabecalho_uf = [
(k, [i[1] for i in v])
for k, v in groupby(pivo_uf.columns, lambda x: x[0])
]
cabecalho_regiao = [
(k, [i[1] for i in v])
for k, v in groupby(pivo_regiao.columns, lambda x: x[0])
]
# Imprimir
context = {
"form": form,
"data_inicio": data_inicio,
"data_fim": data_fim,
"categorias": [
c[1] for c in TipoEvento.CATEGORIA_CHOICES if c[0] in categorias
],
"virtual": [
m[1] for m in EventosPorUfForm.MODO_CHOICES if m[0] in virtual
],
"pivo_uf": pivo_uf,
"pivo_regiao": pivo_regiao,
"cabecalho_uf": cabecalho_uf,
"cabecalho_regiao": cabecalho_regiao,
"total_uf": pivo_uf.sum(),
"total_regiao": pivo_regiao.sum(),
}
if formato == "pdf":
context["title"] = _("Eventos por Unidade da Federação")
context["pdf"] = True
return WeasyTemplateResponse(
filename=f"eventos_por_uf-{data_inicio}-{data_fim}.pdf",
request=request,
template="eventos/eventos_por_uf_pdf.html",
context=context,
content_type="application/pdf",
)
elif formato == "csv":
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = (
f'attachment; filename="eventos_por_uf-{data_inicio}-{data_fim}.csv"'
)
pivo_uf.to_csv(response)
return response
return render(request, "eventos/eventos_por_uf.html", context=context)
class ApiEventoAbstract: class ApiEventoAbstract:
queryset = ( queryset = (
Evento.objects.filter(publicar=True) Evento.objects.filter(publicar=True)

27
sigi/menu_conf.yaml

@ -26,6 +26,23 @@ admin_menu:
- title: Configurações - title: Configurações
view_name: admin:utils_config_changelist view_name: admin:utils_config_changelist
main_menu: main_menu:
- title: Relatórios
icon: print
children:
- title: Eventos por UF
view_name: eventos_eventosporuf
- title: Calendário de eventos
view_name: eventos_calendario
- title: Alocação de equipe eventos
view_name: eventos_alocacaoequipe
separator: [after,]
- title: Reservas de espaços
view_name: espacos_agenda
- title: Uso dos espaços
view_name: espacos_usoespaco
separator: [after,]
- title: Lista de gerentes
view_name: casas_gerentes
- title: Municípios - title: Municípios
icon: location_city icon: location_city
children: children:
@ -71,8 +88,6 @@ main_menu:
view_name: view_name:
- title: Organizar relacionamentos - title: Organizar relacionamentos
view_name: view_name:
- title: Lista de gerentes
view_name: casas_gerentes
- title: Convênios - title: Convênios
icon: assignment icon: assignment
children: children:
@ -101,10 +116,6 @@ main_menu:
- title: Reservas - title: Reservas
view_name: admin:espacos_reserva_changelist view_name: admin:espacos_reserva_changelist
querystr: status__exact=A querystr: status__exact=A
- title: Agenda de reservas
view_name: espacos_agenda
- title: Uso dos espaços
view_name: espacos_usoespaco
- title: Eventos - title: Eventos
icon: school icon: school
children: children:
@ -128,10 +139,6 @@ main_menu:
- title: Visitas Interlegis - title: Visitas Interlegis
view_name: admin:eventos_evento_changelist view_name: admin:eventos_evento_changelist
querystr: tipo_evento__categoria__exact=V querystr: tipo_evento__categoria__exact=V
- title: Calendário mensal
view_name: eventos_calendario
- title: Alocação de equipe
view_name: eventos_alocacaoequipe
- title: Servidores - title: Servidores
icon: account_circle icon: account_circle
children: children:

Loading…
Cancel
Save