mirror of https://github.com/interlegis/sigi.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
615 lines
20 KiB
615 lines
20 KiB
import datetime
|
|
import time
|
|
from moodle import Moodle
|
|
from django.conf import settings
|
|
from django.contrib import admin, messages
|
|
from django.http import HttpResponse
|
|
from django.shortcuts import get_object_or_404, render, redirect
|
|
from django.template import Template, Context
|
|
from django.urls import path, reverse
|
|
from django.utils.safestring import mark_safe
|
|
from django.utils.translation import gettext as _
|
|
from django_weasyprint.utils import django_url_fetcher
|
|
from django_weasyprint.views import WeasyTemplateResponse
|
|
from import_export.fields import Field
|
|
from tinymce.models import HTMLField
|
|
from tinymce.widgets import AdminTinyMCE
|
|
from weasyprint import HTML
|
|
from sigi.apps.eventos.models import (
|
|
Checklist,
|
|
Cronograma,
|
|
ModeloDeclaracao,
|
|
Modulo,
|
|
TipoEvento,
|
|
Funcao,
|
|
Evento,
|
|
Equipe,
|
|
Convite,
|
|
Anexo,
|
|
)
|
|
from sigi.apps.eventos.forms import EventoAdminForm, SelecionaModeloForm
|
|
from sigi.apps.utils import abreviatura
|
|
from sigi.apps.utils.filters import EmptyFilter, DateRangeFilter
|
|
from sigi.apps.utils.mixins import CartExportMixin, ValueLabeledResource
|
|
|
|
|
|
class EventoResource(ValueLabeledResource):
|
|
# categoria_evento = Field(column_name="tipo_evento__categoria")
|
|
# status = Field(column_name="status")
|
|
class Meta:
|
|
model = Evento
|
|
fields = (
|
|
"id",
|
|
"tipo_evento__nome",
|
|
"tipo_evento__categoria",
|
|
"nome",
|
|
"descricao",
|
|
"virtual",
|
|
"solicitante",
|
|
"num_processo",
|
|
"data_pedido",
|
|
"data_inicio",
|
|
"data_termino",
|
|
"carga_horaria",
|
|
"casa_anfitria__nome",
|
|
"casa_anfitria__logradouro",
|
|
"casa_anfitria__bairro",
|
|
"casa_anfitria__municipio__nome",
|
|
"casa_anfitria__municipio__uf__sigla",
|
|
"casa_anfitria__cep",
|
|
"casa_anfitria__email",
|
|
"local",
|
|
"municipio__nome",
|
|
"municipio__uf__sigla",
|
|
"observacao",
|
|
"publico_alvo",
|
|
"total_participantes",
|
|
"status",
|
|
"data_cancelamento",
|
|
"motivo_cancelamento",
|
|
"equipe__membro__nome_completo",
|
|
"equipe__funcao__nome",
|
|
"convite__casa__nome",
|
|
"convite__casa__municipio__nome",
|
|
"convite__casa__municipio__uf__sigla",
|
|
"convite__casa__cep",
|
|
"convite__casa__email",
|
|
"convite__qtde_participantes",
|
|
"convite__nomes_participantes",
|
|
)
|
|
export_order = fields
|
|
|
|
def dehydrate_tipo_evento__categoria(self, obj):
|
|
return dict(TipoEvento.CATEGORIA_CHOICES)[obj["tipo_evento__categoria"]]
|
|
|
|
def dehydrate_virtual(self, obj):
|
|
return "Sim" if obj["virtual"] else "Não"
|
|
|
|
def dehydrate_status(self, obj):
|
|
return dict(Evento.STATUS_CHOICES)[obj["status"]]
|
|
|
|
|
|
class ChecklistInline(admin.StackedInline):
|
|
model = Checklist
|
|
|
|
|
|
class EquipeInline(admin.StackedInline):
|
|
model = Equipe
|
|
autocomplete_fields = ("membro", "funcao")
|
|
|
|
|
|
class ConviteInline(admin.StackedInline):
|
|
model = Convite
|
|
autocomplete_fields = ("casa",)
|
|
|
|
|
|
class ModuloInline(admin.StackedInline):
|
|
model = Modulo
|
|
autocomplete_fields = ("apresentador", "monitor")
|
|
|
|
|
|
class AnexoInline(admin.StackedInline):
|
|
model = Anexo
|
|
exclude = ("data_pub", "convite")
|
|
|
|
|
|
class CronogramaInline(admin.StackedInline):
|
|
model = Cronograma
|
|
extra = 0
|
|
|
|
|
|
@admin.register(TipoEvento)
|
|
class TipoEventoAdmin(admin.ModelAdmin):
|
|
list_display = ["nome", "categoria"]
|
|
list_filter = ["categoria", "casa_solicita"]
|
|
search_fields = ["nome"]
|
|
inlines = [ChecklistInline]
|
|
|
|
|
|
@admin.register(Funcao)
|
|
class FuncaoAdmin(admin.ModelAdmin):
|
|
list_display = (
|
|
"nome",
|
|
"descricao",
|
|
)
|
|
search_fields = (
|
|
"nome",
|
|
"descricao",
|
|
)
|
|
|
|
|
|
@admin.register(ModeloDeclaracao)
|
|
class ModeloDeclaracaoAdmin(admin.ModelAdmin):
|
|
list_display = ("nome", "formato")
|
|
formfield_overrides = {HTMLField: {"widget": AdminTinyMCE}}
|
|
|
|
|
|
@admin.register(Evento)
|
|
class EventoAdmin(CartExportMixin, admin.ModelAdmin):
|
|
form = EventoAdminForm
|
|
resource_class = EventoResource
|
|
date_hierarchy = "data_inicio"
|
|
list_display = (
|
|
"get_banner",
|
|
"get_tipo_evento",
|
|
"nome",
|
|
"turma",
|
|
"status",
|
|
"publicar",
|
|
"link_sigad",
|
|
"data_inicio",
|
|
"data_termino",
|
|
"municipio",
|
|
"solicitante",
|
|
"total_participantes",
|
|
)
|
|
list_display_links = ("get_banner", "nome")
|
|
list_filter = (
|
|
"status",
|
|
"publicar",
|
|
("num_processo", EmptyFilter),
|
|
"tipo_evento",
|
|
"tipo_evento__categoria",
|
|
("data_inicio", DateRangeFilter),
|
|
"virtual",
|
|
"municipio__uf",
|
|
"solicitante",
|
|
)
|
|
autocomplete_fields = (
|
|
"tipo_evento",
|
|
"solicitacao",
|
|
"casa_anfitria",
|
|
"municipio",
|
|
)
|
|
search_fields = (
|
|
"nome",
|
|
"tipo_evento__nome",
|
|
"casa_anfitria__search_text",
|
|
"municipio__search_text",
|
|
"solicitante",
|
|
"num_processo",
|
|
)
|
|
inlines = (
|
|
EquipeInline,
|
|
ConviteInline,
|
|
ModuloInline,
|
|
AnexoInline,
|
|
CronogramaInline,
|
|
)
|
|
save_as = True
|
|
|
|
@admin.display(description=_("Tipo Evento"))
|
|
def get_tipo_evento(self, obj):
|
|
return obj.tipo_evento.nome
|
|
|
|
@admin.display(description=_("número do processo SIGAD"))
|
|
def link_sigad(self, obj):
|
|
if obj.pk is None:
|
|
return ""
|
|
return mark_safe(obj.get_sigad_url())
|
|
|
|
@admin.display(description=_("banner"))
|
|
def get_banner(self, obj):
|
|
if obj.banner:
|
|
return mark_safe(
|
|
f'<img src="{obj.banner.url}" width="60" height="60" />'
|
|
)
|
|
else:
|
|
return ""
|
|
|
|
def render_change_form(self, request, context, add, change, form_url, obj):
|
|
perm = request.user.has_perm("eventos.createcourse_evento")
|
|
context.update(
|
|
{
|
|
"can_createcourse": (
|
|
perm
|
|
and obj
|
|
and obj.moodle_courseid is None
|
|
and obj.tipo_evento.moodle_template_courseid is not None
|
|
and obj.tipo_evento.moodle_categoryid is not None
|
|
),
|
|
"can_updateparticipantes": (
|
|
perm and obj and obj.moodle_courseid is not None
|
|
),
|
|
}
|
|
)
|
|
return super().render_change_form(
|
|
request, context, add, change, form_url, obj
|
|
)
|
|
|
|
def lookup_allowed(self, lookup, value):
|
|
return super(EventoAdmin, self).lookup_allowed(
|
|
lookup, value
|
|
) or lookup in [
|
|
"tipo_evento__nome__exact",
|
|
"tipo_evento__nome__contains",
|
|
]
|
|
|
|
def get_urls(self):
|
|
urls = super().get_urls()
|
|
model_info = self.get_model_info()
|
|
my_urls = [
|
|
path(
|
|
"<path:object_id>/declaracao/",
|
|
self.admin_site.admin_view(self.declaracao_report),
|
|
name="%s_%s_declaracaoreport" % model_info,
|
|
),
|
|
path(
|
|
"<path:object_id>/gant/",
|
|
self.admin_site.admin_view(self.gant_report),
|
|
name="%s_%s_gantreport" % model_info,
|
|
),
|
|
path(
|
|
"<path:object_id>/checklist/",
|
|
self.admin_site.admin_view(self.checklist_report),
|
|
name="%s_%s_checklistreport" % model_info,
|
|
),
|
|
path(
|
|
"<path:object_id>/comunicacao/",
|
|
self.admin_site.admin_view(self.plano_comunicacao),
|
|
name="%s_%s_comunicacaoreport" % model_info,
|
|
),
|
|
path(
|
|
"<path:object_id>/createcourse/",
|
|
self.admin_site.admin_view(self.create_course),
|
|
name="%s_%s_createcourse" % model_info,
|
|
),
|
|
path(
|
|
"<path:object_id>/updateparticipantes/",
|
|
self.admin_site.admin_view(self.update_participantes),
|
|
name="%s_%s_updateparticipantes" % model_info,
|
|
),
|
|
]
|
|
return my_urls + urls
|
|
|
|
def declaracao_report(self, request, object_id):
|
|
if request.method == "POST":
|
|
form = SelecionaModeloForm(request.POST)
|
|
if form.is_valid():
|
|
evento = get_object_or_404(Evento, id=object_id)
|
|
modelo = form.cleaned_data["modelo"]
|
|
membro = (
|
|
evento.equipe_set.filter(assina_oficio=True).first()
|
|
or evento.equipe_set.first()
|
|
)
|
|
if membro:
|
|
servidor = membro.membro
|
|
else:
|
|
servidor = None
|
|
template_string = (
|
|
"""
|
|
{% extends "eventos/declaracao_pdf.html" %}
|
|
{% block text_body %}"""
|
|
+ modelo.texto
|
|
+ """
|
|
{% endblock %}
|
|
"""
|
|
)
|
|
context = Context(
|
|
{
|
|
"pagesize": modelo.formato,
|
|
"pagemargin": modelo.margem,
|
|
"evento": evento,
|
|
"servidor": servidor,
|
|
"data": evento.data_inicio.date(),
|
|
}
|
|
)
|
|
string = Template(template_string).render(context)
|
|
# return HttpResponse(string)
|
|
response = HttpResponse(
|
|
headers={
|
|
"Content-Type": "application/pdf",
|
|
"Content-Disposition": 'attachment; filename="declaração.pdf"',
|
|
}
|
|
)
|
|
pdf = HTML(
|
|
string=string,
|
|
url_fetcher=django_url_fetcher,
|
|
encoding="utf-8",
|
|
base_url=request.build_absolute_uri("/"),
|
|
)
|
|
pdf.write_pdf(target=response)
|
|
return response
|
|
else:
|
|
form = SelecionaModeloForm()
|
|
|
|
context = {
|
|
"form": form,
|
|
"evento_id": object_id,
|
|
"opts": self.model._meta,
|
|
"preserved_filters": self.get_preserved_filters(request),
|
|
}
|
|
return render(
|
|
request, "admin/eventos/evento/seleciona_modelo.html", context
|
|
)
|
|
|
|
def gant_report(self, request, object_id):
|
|
evento = get_object_or_404(Evento, id=object_id)
|
|
cronograma = list(
|
|
evento.cronograma_set.order_by("data_prevista_inicio")
|
|
)
|
|
inicio = min(
|
|
cronograma[0].data_prevista_inicio,
|
|
cronograma[0].data_inicio or cronograma[0].data_prevista_inicio,
|
|
)
|
|
termino = max(
|
|
cronograma[-1].data_prevista_termino,
|
|
cronograma[-1].data_termino or cronograma[-1].data_prevista_termino,
|
|
)
|
|
datas = [
|
|
inicio + datetime.timedelta(days=x)
|
|
for x in range((termino - inicio).days + 1)
|
|
]
|
|
context = {
|
|
"cronograma": cronograma,
|
|
"datas": datas,
|
|
"hoje": datetime.date.today(),
|
|
"title": evento.nome,
|
|
}
|
|
|
|
return WeasyTemplateResponse(
|
|
filename="grafico-gant.pdf",
|
|
request=request,
|
|
template="admin/eventos/evento/gant_report.html",
|
|
context=context,
|
|
content_type="application/pdf",
|
|
)
|
|
|
|
def checklist_report(self, request, object_id):
|
|
evento = get_object_or_404(Evento, id=object_id)
|
|
cronograma = list(
|
|
evento.cronograma_set.order_by("data_prevista_inicio")
|
|
)
|
|
context = {"cronograma": cronograma, "title": evento.nome}
|
|
return WeasyTemplateResponse(
|
|
filename="checklist.pdf",
|
|
request=request,
|
|
template="admin/eventos/evento/checklist_report.html",
|
|
context=context,
|
|
content_type="application/pdf",
|
|
)
|
|
|
|
def plano_comunicacao(self, request, object_id):
|
|
evento = get_object_or_404(Evento, id=object_id)
|
|
matrix = {}
|
|
for etapa in evento.cronograma_set.order_by("data_prevista_inicio"):
|
|
for responsavel in etapa.responsaveis.splitlines():
|
|
if responsavel not in matrix:
|
|
matrix[responsavel] = {}
|
|
for destinatario in etapa.comunicar_inicio.splitlines():
|
|
if destinatario not in matrix[responsavel]:
|
|
matrix[responsavel][destinatario] = []
|
|
matrix[responsavel][destinatario].append(
|
|
_(f"Início da etapa {etapa.nome}")
|
|
)
|
|
for destinatario in etapa.comunicar_termino.splitlines():
|
|
if destinatario not in matrix[responsavel]:
|
|
matrix[responsavel][destinatario] = []
|
|
matrix[responsavel][destinatario].append(
|
|
_(f"Término da etapa {etapa.nome}")
|
|
)
|
|
responsaveis = list(matrix.keys())
|
|
destinatarios = list(
|
|
{x for xs in [v.keys() for v in matrix.values()] for x in xs}
|
|
)
|
|
responsaveis.sort()
|
|
destinatarios.sort()
|
|
matrix = {
|
|
resp: {
|
|
dest: matrix[resp][dest] if dest in matrix[resp] else []
|
|
for dest in destinatarios
|
|
}
|
|
for resp in responsaveis
|
|
}
|
|
context = {
|
|
"matrix": matrix,
|
|
"destinatarios": destinatarios,
|
|
"title": evento.nome,
|
|
}
|
|
return WeasyTemplateResponse(
|
|
filename="comunicação.pdf",
|
|
request=request,
|
|
template="admin/eventos/evento/plano_comunicacao.html",
|
|
context=context,
|
|
content_type="application/pdf",
|
|
)
|
|
|
|
def create_course(self, request, object_id):
|
|
evento = get_object_or_404(Evento, id=object_id)
|
|
change_url = (
|
|
reverse(
|
|
"admin:%s_%s_change" % self.get_model_info(), args=[object_id]
|
|
)
|
|
+ "?"
|
|
+ self.get_preserved_filters(request)
|
|
)
|
|
if evento.moodle_courseid is not None:
|
|
self.message_user(
|
|
request,
|
|
_("Este evento já tem curso associado no Saberes"),
|
|
level=messages.ERROR,
|
|
)
|
|
return redirect(change_url)
|
|
if (
|
|
evento.tipo_evento.moodle_template_courseid is None
|
|
or evento.tipo_evento.moodle_categoryid is None
|
|
):
|
|
self.message_user(
|
|
request,
|
|
_("Este tipo de evento não possui template no Saberes"),
|
|
level=messages.ERROR,
|
|
)
|
|
return redirect(change_url)
|
|
if evento.data_inicio is None or evento.data_termino is None:
|
|
self.message_user(
|
|
request,
|
|
_(
|
|
"O evento precisa ter datas de início e término para criar "
|
|
"curso no Saberes."
|
|
),
|
|
level=messages.ERROR,
|
|
)
|
|
return redirect(change_url)
|
|
|
|
if evento.turma == "":
|
|
self.message_user(
|
|
request,
|
|
_(
|
|
"Preencha (e salve!) o campo Turma para poder criar o "
|
|
"curso no Saberes"
|
|
),
|
|
level=messages.ERROR,
|
|
)
|
|
return redirect(change_url)
|
|
|
|
api_url = f"{settings.MOODLE_BASE_URL}/webservice/rest/server.php"
|
|
mws = Moodle(api_url, settings.MOODLE_API_TOKEN)
|
|
fullname = f"{evento.tipo_evento.nome} - {evento.municipio.nome}/{evento.municipio.uf.sigla} - {evento.tipo_evento.prefixo_turma}{evento.turma}"
|
|
shortname = f"{abreviatura(evento.tipo_evento.nome)} - {evento.tipo_evento.prefixo_turma}{evento.turma}"
|
|
inicio = int(time.mktime(evento.data_inicio.astimezone().timetuple()))
|
|
fim = int(time.mktime(evento.data_termino.astimezone().timetuple()))
|
|
erros = []
|
|
try: # Criar novo curso a partir do template
|
|
novo_curso = mws.core.course.duplicate_course(
|
|
evento.tipo_evento.moodle_template_courseid,
|
|
fullname=fullname,
|
|
shortname=shortname,
|
|
categoryid=evento.tipo_evento.moodle_categoryid,
|
|
visible=0,
|
|
)
|
|
evento.moodle_courseid = novo_curso.id
|
|
evento.save()
|
|
except Exception as e:
|
|
self.message_user(
|
|
request,
|
|
_(
|
|
"Ocorreu um erro ao criar o curso no Saberes com "
|
|
f"a mensagem {e.message}"
|
|
),
|
|
level=messages.ERROR,
|
|
)
|
|
return redirect(change_url)
|
|
try: # Atualiza configuração do curso
|
|
changes = {
|
|
"id": novo_curso.id,
|
|
"summary": evento.descricao,
|
|
"startdate": inicio,
|
|
"enddate": fim,
|
|
}
|
|
res = mws.core.course.update_courses([changes])
|
|
except Exception as e:
|
|
erros.append(
|
|
_(
|
|
"Falha na tentativa de alterar o sumário e as datas de "
|
|
"início e término do curso, com a seguinte mensagem: "
|
|
f"{e.message}"
|
|
)
|
|
)
|
|
try: # Matricular professores/membros
|
|
membros = evento.equipe_set.exclude(
|
|
membro__moodle_userid=None
|
|
).exclude(funcao__moodle_roleid=None)
|
|
equipe = []
|
|
for membro in membros:
|
|
equipe.append(
|
|
{
|
|
"roleid": membro.funcao.moodle_roleid,
|
|
"userid": membro.membro.moodle_userid,
|
|
"courseid": evento.moodle_courseid,
|
|
}
|
|
)
|
|
mws.enrol.manual.enrol_users(equipe)
|
|
except Exception as e:
|
|
erros.append(
|
|
_(
|
|
"Falha ao tentar inscrever a equipe no curso do Saberes, "
|
|
f"com a seguinte mensagem: {e.message}"
|
|
)
|
|
)
|
|
context = {
|
|
"evento": evento,
|
|
"fullname": fullname,
|
|
"shortname": shortname,
|
|
"membros": membros,
|
|
"erros": erros,
|
|
"opts": self.model._meta,
|
|
"preserved_filters": self.get_preserved_filters(request),
|
|
}
|
|
return render(
|
|
request, "admin/eventos/evento/createcourse.html", context
|
|
)
|
|
|
|
def update_participantes(self, request, object_id):
|
|
evento = get_object_or_404(Evento, id=object_id)
|
|
change_url = (
|
|
reverse(
|
|
"admin:%s_%s_change" % self.get_model_info(), args=[object_id]
|
|
)
|
|
+ "?"
|
|
+ self.get_preserved_filters(request)
|
|
)
|
|
if evento.moodle_courseid is None:
|
|
self.message_user(
|
|
request,
|
|
_("Este evento não tem curso associado no Saberes"),
|
|
level=messages.ERROR,
|
|
)
|
|
return redirect(change_url)
|
|
|
|
api_url = f"{settings.MOODLE_BASE_URL}/webservice/rest/server.php"
|
|
mws = Moodle(api_url, settings.MOODLE_API_TOKEN)
|
|
try:
|
|
inscritos = mws.post(
|
|
"core_enrol_get_enrolled_users", courseid=evento.moodle_courseid
|
|
)
|
|
except Exception as e:
|
|
self.message_user(
|
|
request,
|
|
_(
|
|
"Ocorreu um erro ao acessar o curso no Saberes com "
|
|
f"a mensagem {e.message}"
|
|
),
|
|
level=messages.ERROR,
|
|
)
|
|
return redirect(change_url)
|
|
evento.total_participantes = len(
|
|
list(
|
|
filter(
|
|
lambda u: any(
|
|
r["roleid"] in settings.MOODLE_STUDENT_ROLES
|
|
for r in u["roles"]
|
|
),
|
|
inscritos,
|
|
)
|
|
)
|
|
)
|
|
evento.save()
|
|
self.message_user(
|
|
request,
|
|
_(
|
|
f"Foram encontrados {evento.total_participantes} alunos "
|
|
"no Saberes"
|
|
),
|
|
level=messages.SUCCESS,
|
|
)
|
|
return redirect(change_url)
|
|
|