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(
"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.forms.utils import ErrorList
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.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
@ -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 Meta:
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 csv
import datetime
import locale
import pandas as pd
from functools import reduce
from itertools import groupby
from rest_framework import mixins, generics
from typing import OrderedDict
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.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db.models import Count, Sum, Q
from django.http import HttpResponse
from django.shortcuts import redirect, render, get_object_or_404
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 weasyprint import HTML
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.eventos.models import TipoEvento, Evento
from sigi.apps.eventos.forms import (
SelecionaModeloForm,
ConviteForm,
EventosPorUfForm,
CasaForm,
FuncionarioForm,
ParlamentarForm,
@ -345,7 +351,7 @@ def alocacao_equipe(request):
context["title"] = _("Alocação de equipe")
context["pdf"] = True
return WeasyTemplateResponse(
# filename="alocacao_equipe.pdf",
filename="alocacao_equipe.pdf",
request=request,
template="eventos/alocacao_equipe_pdf.html",
context=context,
@ -364,6 +370,253 @@ def alocacao_equipe(request):
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:
queryset = (
Evento.objects.filter(publicar=True)

27
sigi/menu_conf.yaml

@ -26,6 +26,23 @@ admin_menu:
- title: Configurações
view_name: admin:utils_config_changelist
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
icon: location_city
children:
@ -71,8 +88,6 @@ main_menu:
view_name:
- title: Organizar relacionamentos
view_name:
- title: Lista de gerentes
view_name: casas_gerentes
- title: Convênios
icon: assignment
children:
@ -101,10 +116,6 @@ main_menu:
- title: Reservas
view_name: admin:espacos_reserva_changelist
querystr: status__exact=A
- title: Agenda de reservas
view_name: espacos_agenda
- title: Uso dos espaços
view_name: espacos_usoespaco
- title: Eventos
icon: school
children:
@ -128,10 +139,6 @@ main_menu:
- title: Visitas Interlegis
view_name: admin:eventos_evento_changelist
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
icon: account_circle
children:

Loading…
Cancel
Save