Browse Source

Relatório de alunos por UF e região. Gertiq #160538

dependabot/pip/requirements/djangorestframework-3.15.2
Sesóstris Vieira 7 months ago
parent
commit
b2715fb359
  1. 12
      sigi/apps/eventos/admin.py
  2. 5
      sigi/apps/eventos/admin_urls.py
  3. 5
      sigi/apps/eventos/forms.py
  4. 69
      sigi/apps/eventos/migrations/0063_participantesevento_and_more.py
  5. 25
      sigi/apps/eventos/migrations/0064_participantes_evento_carga_inicial.py
  6. 58
      sigi/apps/eventos/models.py
  7. 36
      sigi/apps/eventos/templates/eventos/report/alunos_por_uf_report_view/dataset_snippet.html
  8. 35
      sigi/apps/eventos/templates/eventos/report/alunos_por_uf_report_view/report.html
  9. 83
      sigi/apps/eventos/templates/eventos/report/alunos_por_uf_report_view/report_pdf.html
  10. 144
      sigi/apps/eventos/views.py
  11. 9
      sigi/menu_conf.yaml
  12. 1
      sigi/settings.py

12
sigi/apps/eventos/admin.py

@ -3,7 +3,6 @@ import pandas as pd
import time
from admin_auto_filters.filters import AutocompleteFilter
from moodle import Moodle
from typing import Any
from django.db import models
from django.db.models import (
F,
@ -23,7 +22,6 @@ from django.db.models.functions import ExtractDay, Cast
from django.conf import settings
from django.contrib import admin, messages
from django.contrib.admin.utils import get_deleted_objects
from django.core.exceptions import ValidationError
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render, redirect
from django.template import Template, Context
@ -53,6 +51,7 @@ from sigi.apps.eventos.models import (
Equipe,
Convite,
Anexo,
ParticipantesEvento,
)
from sigi.apps.eventos.forms import EventoAdminForm, SelecionaModeloForm
from sigi.apps.servidores.models import Servidor
@ -360,6 +359,12 @@ class CronogramaInline(admin.StackedInline):
extra = 0
class ParticipantesEventoInline(admin.TabularInline):
model = ParticipantesEvento
fields = ("uf", "inscritos", "aprovados")
autocomplete_fields = ["uf"]
class ItemSolicitadoInline(admin.StackedInline):
model = ItemSolicitado
fields = (
@ -993,6 +998,7 @@ class EventoAdmin(AsciifyQParameter, CartExportReportMixin, admin.ModelAdmin):
inlines = (
EquipeInline,
ConviteInline,
ParticipantesEventoInline,
ModuloInline,
AnexoInline,
CronogramaInline,
@ -1819,7 +1825,7 @@ class EventoAdmin(AsciifyQParameter, CartExportReportMixin, admin.ModelAdmin):
"startdate": inicio,
"enddate": fim,
}
res = mws.core.course.update_courses([changes])
mws.core.course.update_courses([changes])
except Exception as e:
erros.append(
_(

5
sigi/apps/eventos/admin_urls.py

@ -7,6 +7,11 @@ urlpatterns = [
"alocacaoequipe/", views.alocacao_equipe, name="eventos_alocacaoequipe"
),
path("eventosporuf/", views.eventos_por_uf, name="eventos_eventosporuf"),
path(
"alunosporuf/",
views.AlunosPorUfReportView.as_view(),
name="eventos_alunosporuf",
),
path(
"solicitacoesporperiodo/",
views.solicitacoes_por_periodo,

5
sigi/apps/eventos/forms.py

@ -1,9 +1,4 @@
from collections.abc import Mapping
from typing import Any
from django import forms
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,

69
sigi/apps/eventos/migrations/0063_participantesevento_and_more.py

@ -0,0 +1,69 @@
# Generated by Django 5.0.4 on 2024-05-28 13:30
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("contatos", "0007_alter_mesorregiao_options"),
("eventos", "0062_create_viw_eventos"),
]
operations = [
migrations.CreateModel(
name="ParticipantesEvento",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"inscritos",
models.PositiveIntegerField(
default=0, verbose_name="total de inscritos"
),
),
(
"aprovados",
models.PositiveIntegerField(
default=0, verbose_name="total de aprovados"
),
),
(
"evento",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="eventos.evento",
verbose_name="evento",
),
),
(
"uf",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="contatos.unidadefederativa",
verbose_name="uf",
),
),
],
options={
"verbose_name": "Participante por UF",
"verbose_name_plural": "Participantes por UF",
},
),
migrations.AddConstraint(
model_name="participantesevento",
constraint=models.UniqueConstraint(
models.F("evento"), models.F("uf"), name="unique_evento_uf"
),
),
]

25
sigi/apps/eventos/migrations/0064_participantes_evento_carga_inicial.py

@ -0,0 +1,25 @@
# Generated by Django 5.0.4 on 2024-05-28 13:41
from django.db import migrations
from django.utils import timezone
from sigi.apps.eventos.models import Evento
def carga(apps, schema_editor):
for evento in Evento.objects.exclude(moodle_courseid=None).filter(
data_termino__lt=timezone.localtime()
):
try:
evento.sincroniza_saberes()
print(f"\t{evento.nome} sincronizado.")
except Evento.SaberesSyncException as err:
print(f"\tERRO: {evento.nome}: {err.message}")
class Migration(migrations.Migration):
dependencies = [
("eventos", "0063_participantesevento_and_more"),
]
operations = [migrations.RunPython(carga, migrations.RunPython.noop)]

58
sigi/apps/eventos/models.py

@ -14,9 +14,8 @@ from django.utils import timezone
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
from sigi.apps.casas.models import Orgao, Servidor
from sigi.apps.contatos.models import Municipio
from sigi.apps.contatos.models import UnidadeFederativa
from sigi.apps.espacos.models import Reserva
from sigi.apps.servidores.models import Servidor
class TipoEvento(models.Model):
@ -577,7 +576,25 @@ class Evento(models.Model):
)
aprovados = 0
self.participantesevento_set.update(inscritos=0, aprovados=0)
for participante in participantes:
try:
nome_uf = [
f["value"].lower()
for f in participante["customfields"]
if f["shortname"] == settings.MOODLE_UF_CUSTOMFIELD
][0]
uf = UnidadeFederativa.objects.get(nome__iexact=nome_uf)
except (
IndexError,
UnidadeFederativa.DoesNotExist,
UnidadeFederativa.MultipleObjectsReturned,
):
uf = None
part_uf, created = ParticipantesEvento.objects.get_or_create(
evento=self, uf=uf
)
part_uf.inscritos += 1
try:
completion_data = mws.post(
"core_completion_get_course_completion_status",
@ -599,6 +616,8 @@ class Evento(models.Model):
)
):
aprovados += 1
part_uf.aprovados += 1
part_uf.save()
self.inscritos_saberes = len(participantes)
self.aprovados_saberes = aprovados
@ -937,6 +956,41 @@ class Modulo(models.Model):
return _(f"{self.nome} ({self.get_tipo_display()})")
class ParticipantesEvento(models.Model):
evento = models.ForeignKey(
Evento, verbose_name=_("evento"), on_delete=models.CASCADE
)
uf = models.ForeignKey(
UnidadeFederativa,
verbose_name=_("uf"),
on_delete=models.CASCADE,
blank=True,
null=True,
)
inscritos = models.PositiveIntegerField(_("total de inscritos"), default=0)
aprovados = models.PositiveIntegerField(_("total de aprovados"), default=0)
class Meta:
constraints = [
models.UniqueConstraint("evento", "uf", name="unique_evento_uf")
]
verbose_name = _("Participante por UF")
verbose_name_plural = _("Participantes por UF")
def __str__(self):
if self.uf is None:
return _(
f"{self.inscritos} pessoas se inscreveram no "
f"evento {self.evento.nome}, tendo {self.aprovados} "
"pessoas aprovadas, mas não informaram a UF onde residem."
)
return _(
f"{self.inscritos} pessoas de {self.uf.nome} se inscreveram no "
f"evento {self.evento.nome}, tendo {self.aprovados} "
"pessoas aprovadas"
)
class ModeloDeclaracao(models.Model):
FORMATO_CHOICES = (
("A4 portrait", _("A4 retrato")),

36
sigi/apps/eventos/templates/eventos/report/alunos_por_uf_report_view/dataset_snippet.html

@ -0,0 +1,36 @@
{% load i18n %}
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">{{ data_title }}</span>
<table class="striped">
<thead>
<tr>
<th>{% translate "Evento" %}</th>
{% for label in dataset.columns %}
<th class="right-align">{{ label }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row_data in dataset.itertuples %}
<tr {% if forloop.last %}class="total-row"{% endif %}>
{% for value in row_data %}
{% if forloop.first %}
<td class="left-align">{{ value.0 }} - <strong>{{ value.1 }}</strong></td>
{% else %}
<td class="right-align{% if forloop.last %} total-col{% endif %}">
{{ value|default:"" }}
</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<blockquote>* {% translate "Alunos que não informaram a UF de residência no Saberes" %}</blockquote>
</div>
</div>
</div>
</div>

35
sigi/apps/eventos/templates/eventos/report/alunos_por_uf_report_view/report.html

@ -0,0 +1,35 @@
{% extends 'utils/report/report.html' %}
{% load i18n %}
{% block extrastyle %}
{{ block.super }}
<style type="text/css">
tr.total-row td, td.total-col {
background: var(--darkened-bg);
border-top: 1px solid var(--hairline-color);
border-bottom: 1px solid var(--hairline-color);
color: var(--body-quiet-color);
font-size: 0.6875rem;
font-weight: 600;
line-height: normal;
padding: 5px 10px;
text-transform: uppercase;
}
</style>
{% endblock %}
{% block data %}
{% if not inscritos_uf is None %}
{% include 'eventos/report/alunos_por_uf_report_view/dataset_snippet.html' with dataset=inscritos_uf data_title=_("Total de alunos inscritos por UF") %}
{% endif %}
{% if not aprovados_uf is None %}
{% include 'eventos/report/alunos_por_uf_report_view/dataset_snippet.html' with dataset=aprovados_uf data_title=_("Total de alunos aprovados por UF") %}
{% endif %}
{% if not inscritos_regiao is None %}
{% include 'eventos/report/alunos_por_uf_report_view/dataset_snippet.html' with dataset=inscritos_regiao data_title=_("Total de alunos inscritos por região") %}
{% endif %}
{% if not aprovados_regiao is None %}
{% include 'eventos/report/alunos_por_uf_report_view/dataset_snippet.html' with dataset=aprovados_regiao data_title=_("Total de alunos aprovados por região") %}
{% endif %}
{% endblock data %}

83
sigi/apps/eventos/templates/eventos/report/alunos_por_uf_report_view/report_pdf.html

@ -0,0 +1,83 @@
{% extends 'utils/report/report_pdf.html' %}
{% load i18n %}
{% block extra_style %}
{{ block.super }}
tr.total-row td, td.total-col {
background-color: #007433;
border-top: 1px solid var(--hairline-color);
border-bottom: 1px solid var(--hairline-color);
color: white;
font-weight: 600;
padding: 5px 10px;
text-transform: uppercase;
}
.card-title {
font-weight: 600;
font-size: 1.5em;
margin: 20px 0;
}
table.applied-filter caption {
font-size: 1.2em;
font-weight: 600;
text-transform: uppercase;
margin-left: 4px;
}
table.applied-filter {
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 5px;
}
table.applied-filter tr {
background-color: white !important;
font-weight: 600;
}
table.applied-filter tr th {
text-align: left;
padding-right: 48px;
width: 20%;
background-color: unset !important;
}
table.applied-filter tr td {
width: 80%;
background-color: unset !important;
padding: 0 24px;
border-bottom: 1px solid #e0e0e0;
}
{% endblock %}
{% block main_content %}
<table class="applied-filter">
<caption>{% translate "Filtros aplicados" %}</caption>
{% for field in form %}
<tr>
<th>{{ field.label }}</th>
<td>
{% for choice in field.field.choices %}
{% if choice.0 in field.value %}{{ choice.1 }}, {% endif %}
{% empty %}
{{ field.value|default:"" }}
{% endfor %}
</td>
</tr>
{% endfor %}
</table>
<div style="height: 24px; width: 100%;"></div>
{% if not inscritos_uf is None %}
{% include 'eventos/report/alunos_por_uf_report_view/dataset_snippet.html' with dataset=inscritos_uf data_title=_("Total de alunos inscritos por UF") %}
<div class="new-page">
{% endif %}
{% if not aprovados_uf is None %}
{% include 'eventos/report/alunos_por_uf_report_view/dataset_snippet.html' with dataset=aprovados_uf data_title=_("Total de alunos aprovados por UF") %}
<div class="new-page">
{% endif %}
{% if not inscritos_regiao is None %}
{% include 'eventos/report/alunos_por_uf_report_view/dataset_snippet.html' with dataset=inscritos_regiao data_title=_("Total de alunos inscritos por região") %}
<div class="new-page">
{% endif %}
{% if not aprovados_regiao is None %}
{% include 'eventos/report/alunos_por_uf_report_view/dataset_snippet.html' with dataset=aprovados_regiao data_title=_("Total de alunos aprovados por região") %}
{% endif %}
{% endblock %}

144
sigi/apps/eventos/views.py

@ -10,7 +10,18 @@ from typing import OrderedDict
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.db.models import Count, Sum, Q, F, OuterRef, Subquery
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.db.models import (
Count,
Sum,
Q,
F,
OuterRef,
Subquery,
Case,
When,
Value,
)
from django.http import HttpResponse
from django.shortcuts import render
from django.utils import timezone
@ -30,6 +41,7 @@ from sigi.apps.eventos.models import (
Equipe,
Solicitacao,
ItemSolicitado,
ParticipantesEvento,
)
from sigi.apps.eventos.forms import (
EventosPorUfForm,
@ -39,6 +51,136 @@ from sigi.apps.eventos.serializers import (
EventoSerializer,
EventoListSerializer,
)
from sigi.apps.utils.views import ReportListView
class AlunosPorUfReportView(
LoginRequiredMixin, UserPassesTestMixin, ReportListView
):
title = _("Alunos por UF")
empty_message = _("Nenhum evento para os parâmetros solicitados")
filter_form = EventosPorUfForm
list_fields = ["evento__nome", "uf__sigla", "inscritos", "aprovados"]
list_labels = [""]
def test_func(self):
return self.request.user.is_staff
def get_queryset(self):
form = self.get_filter_form_instance()
queryset = ParticipantesEvento.objects.none()
if form.is_valid():
data_inicio = form.cleaned_data.get("data_inicio")
data_fim = form.cleaned_data.get("data_fim")
categorias = form.cleaned_data.get(
"categoria", [c[0] for c in TipoEvento.CATEGORIA_CHOICES]
)
modo = form.cleaned_data.get("virtual", ["V", "P"])
queryset = ParticipantesEvento.objects.filter(
evento__status=Evento.STATUS_REALIZADO,
evento__data_inicio__gte=data_inicio,
evento__data_termino__lte=data_fim,
evento__tipo_evento__categoria__in=categorias,
)
if len(modo) == 1:
if "V" in modo:
queryset = queryset.filter(evento__virtual=True)
else:
queryset = queryset.filter(evento__virtual=False)
return queryset
def get_dataset(self):
queryset = self.get_queryset()
fieldnames = [
"evento__nome",
"evento__virtual",
"uf__nome",
"uf__sigla",
"uf__regiao",
"inscritos",
"aprovados",
]
queryset = queryset.values(*fieldnames)
return queryset, fieldnames
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
queryset = self.get_queryset()
if queryset:
uf_sigla = Case(
When(uf__sigla=None, then=Value("*")), default="uf__sigla"
)
uf_regiao = Case(
*[
When(uf__regiao=r[0], then=Value(r[1]))
for r in UnidadeFederativa.REGIAO_CHOICES
],
default=Value("*"),
)
modo = Case(
When(evento__virtual=True, then=Value("Virtual")),
default=Value("Presencial"),
)
df = pd.DataFrame(
queryset.order_by("evento", "uf").values(
"evento__nome",
"inscritos",
"aprovados",
uf_sigla=uf_sigla,
modo=modo,
)
)
context["inscritos_uf"] = df.pivot_table(
values="inscritos",
index=["evento__nome", "modo"],
columns="uf_sigla",
aggfunc="sum",
margins=True,
margins_name="Total",
sort=True,
fill_value=0,
).astype(pd.Int64Dtype())
context["aprovados_uf"] = df.pivot_table(
values="aprovados",
index=["evento__nome", "modo"],
columns="uf_sigla",
aggfunc="sum",
margins=True,
margins_name="Total",
sort=True,
fill_value=0,
).astype(pd.Int64Dtype())
df = pd.DataFrame(
queryset.order_by("evento", "uf__regiao").values(
"evento__nome",
"inscritos",
"aprovados",
uf_regiao=uf_regiao,
modo=modo,
)
)
context["inscritos_regiao"] = df.pivot_table(
values="inscritos",
index=["evento__nome", "modo"],
columns="uf_regiao",
aggfunc="sum",
margins=True,
margins_name="Total",
sort=True,
fill_value=0,
).astype(pd.Int64Dtype())
context["aprovados_regiao"] = df.pivot_table(
values="aprovados",
index=["evento__nome", "modo"],
columns="uf_regiao",
aggfunc="sum",
margins=True,
margins_name="Total",
sort=True,
fill_value=0,
).astype(pd.Int64Dtype())
return context
@login_required

9
sigi/menu_conf.yaml

@ -29,14 +29,17 @@ main_menu:
- title: Relatórios
icon: print
children:
- title: Erros importação Gescon
- title: VALIDAÇÃO - Erros importação Gescon
view_name: convenios-report_erros_gescon
- title: Órgãos com CNPJ duplicado
- title: VALIDAÇÃO - Órgãos com CNPJ duplicado
view_name: casas_cnpj_duplicado
- title: Órgãos com CNPJ errado
- title: VALIDAÇÃO - Órgãos com CNPJ errado
view_name: casas_cnpj_errado
separator: [after,]
- title: Eventos por UF
view_name: eventos_eventosporuf
- title: Alunos por UF
view_name: eventos_alunosporuf
- title: Solicitações de eventos por período
view_name: eventos_solicitacoesporperiodo
- title: Calendário de eventos

1
sigi/settings.py

@ -298,6 +298,7 @@ MOODLE_STUDENT_ROLES = env("MOODLE_STUDENT_ROLES", eval, default=(5, 9))
MOODLE_COMPLETE_CRITERIA_TYPE = env(
"MOODLE_COMPLETE_CRITERIA_TYPE", int, default=6 # Type Grade
)
MOODLE_UF_CUSTOMFIELD = env("MOODLE_UF_CUSTOMFIELD", str, default="estado")
# Integração com reserva de salas

Loading…
Cancel
Save