Browse Source

Aprimora sincronização de inscritos e aprovados SIGI x Saberes. Gertiq #163655

pull/166/head
Sesóstris Vieira 1 year ago
parent
commit
f1e9f30f39
  1. 57
      sigi/apps/eventos/admin.py
  2. 50
      sigi/apps/eventos/migrations/0049_evento_aprovados_saberes_evento_data_sincronizacao_and_more.py
  3. 114
      sigi/apps/eventos/models.py
  4. 6
      sigi/settings.py

57
sigi/apps/eventos/admin.py

@ -89,8 +89,6 @@ class SolicitacaoResource(LabeledResourse):
class EventoResource(ValueLabeledResource):
# categoria_evento = Field(column_name="tipo_evento__categoria")
# status = Field(column_name="status")
class Meta:
model = Evento
fields = (
@ -119,6 +117,9 @@ class EventoResource(ValueLabeledResource):
"observacao",
"publico_alvo",
"total_participantes",
"inscritos_saberes",
"aprovados_saberes",
"data_sincronizacao",
"status",
"data_cancelamento",
"motivo_cancelamento",
@ -594,11 +595,14 @@ class EventoAdmin(CartExportMixin, admin.ModelAdmin):
},
),
(
_("Status"),
_("Status/participação"),
{
"fields": (
"status",
"total_participantes",
"inscritos_saberes",
"aprovados_saberes",
"data_sincronizacao",
"data_cancelamento",
"motivo_cancelamento",
)
@ -666,6 +670,11 @@ class EventoAdmin(CartExportMixin, admin.ModelAdmin):
"solicitante",
"num_processo",
)
readonly_fields = (
"inscritos_saberes",
"aprovados_saberes",
"data_sincronizacao",
)
inlines = (
EquipeInline,
ConviteInline,
@ -1146,41 +1155,33 @@ class EventoAdmin(CartExportMixin, admin.ModelAdmin):
)
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:
evento.sincroniza_saberes()
except Evento.SaberesSyncException as e:
self.message_user(
request,
_(
"Ocorreu um erro ao acessar o curso no Saberes com "
f"a mensagem {e.message}"
),
_(f"Erro ao sincronizar dados do Saberes: '{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"
f"Foram encontrados {evento.inscritos_saberes} alunos "
f"no Saberes. Destes, {evento.aprovados_saberes} concluíram."
),
level=messages.SUCCESS,
)
if evento.total_participantes != evento.inscritos_saberes:
self.message_user(
request,
_(
"O total de participantes ficou em "
f"{evento.total_participantes} alunos, pois o campo "
"já estava preenchido."
),
level=messages.WARNING,
)
return redirect(change_url)

50
sigi/apps/eventos/migrations/0049_evento_aprovados_saberes_evento_data_sincronizacao_and_more.py

@ -0,0 +1,50 @@
# Generated by Django 4.2.4 on 2023-10-02 11:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("eventos", "0048_evento_set_defaults"),
]
operations = [
migrations.AddField(
model_name="evento",
name="aprovados_saberes",
field=models.PositiveIntegerField(
default=0,
editable=False,
help_text="Número de pessoas que concluíram o curso no Saberes. Computado via integração SIGI x Saberes.",
verbose_name="aprovados no Saberes",
),
),
migrations.AddField(
model_name="evento",
name="data_sincronizacao",
field=models.DateTimeField(
editable=False,
null=True,
verbose_name="data da última sincronização com Saberes",
),
),
migrations.AddField(
model_name="evento",
name="inscritos_saberes",
field=models.PositiveIntegerField(
default=0,
editable=False,
help_text="Número de pessoas que se inscreveram no evento no Saberes. Computado via integração SIGI x Saberes.",
verbose_name="inscritos no Saberes",
),
),
migrations.AlterField(
model_name="evento",
name="total_participantes",
field=models.PositiveIntegerField(
default=0,
help_text="Se informar quantidade de participantes na aba de convites, este campo será ajustado com a somatória dos participantes naquela aba. Senão, será igual ao número de inscritos no Saberes.",
verbose_name="total de participantes",
),
),
]

114
sigi/apps/eventos/models.py

@ -1,7 +1,9 @@
from collections.abc import Iterable
import datetime
import re
from moodle import Moodle
from tinymce.models import HTMLField
from django.conf import settings
from django.contrib import admin
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
@ -306,6 +308,11 @@ class AnexoSolicitacao(models.Model):
class Evento(models.Model):
class SaberesSyncException(Exception):
@property
def message(self):
return str(self)
STATUS_PLANEJAMENTO = "E"
STATUS_AGUARDANDOSIGAD = "G"
STATUS_PREVISAO = "P"
@ -404,13 +411,37 @@ class Evento(models.Model):
observacao = models.TextField(_("Observações e anotações"), blank=True)
publico_alvo = models.TextField(_("Público alvo"), blank=True)
total_participantes = models.PositiveIntegerField(
_("Total de participantes"),
_("total de participantes"),
default=0,
help_text=_(
"Se informar quantidade de participantes na aba de "
"convites, este campo será ajustado com a somatória "
"dos participantes naquela aba."
"dos participantes naquela aba. Senão, será igual ao número de "
"inscritos no Saberes."
),
)
inscritos_saberes = models.PositiveIntegerField(
_("inscritos no Saberes"),
default=0,
help_text=_(
"Número de pessoas que se inscreveram no evento no Saberes. "
"Computado via integração SIGI x Saberes."
),
editable=False,
)
aprovados_saberes = models.PositiveIntegerField(
_("aprovados no Saberes"),
default=0,
help_text=_(
"Número de pessoas que concluíram o curso no Saberes. "
"Computado via integração SIGI x Saberes."
),
editable=False,
)
data_sincronizacao = models.DateTimeField(
_("data da última sincronização com Saberes"),
null=True,
editable=False,
)
status = models.CharField(
_("Status"), max_length=1, choices=STATUS_CHOICES
@ -543,6 +574,69 @@ class Evento(models.Model):
+ f"/course/view.php?id={self.moodle_courseid}"
)
def sincroniza_saberes(self):
if self.moodle_courseid is None:
raise Evento.SaberesSyncException(
_("Este evento não tem curso associado no Saberes"),
)
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=self.moodle_courseid,
)
except Exception as e:
raise Evento.SaberesSyncException(
_(
"Ocorreu um erro ao acessar o curso no Saberes com "
f"a mensagem {e.message}"
),
)
participantes = list(
filter(
lambda u: any(
r["roleid"] in settings.MOODLE_STUDENT_ROLES
for r in u["roles"]
),
inscritos,
)
)
aprovados = 0
for participante in participantes:
try:
completion_data = mws.post(
"core_completion_get_course_completion_status",
courseid=self.moodle_courseid,
userid=participante["id"],
)
except Exception:
completion_data = None
if completion_data and (
completion_data["completionstatus"]["completed"]
or any(
filter(
lambda c: c["type"]
== settings.MOODLE_COMPLETE_CRITERIA_TYPE
and c["complete"],
completion_data["completionstatus"]["completions"],
)
)
):
aprovados += 1
self.inscritos_saberes = len(participantes)
self.aprovados_saberes = aprovados
self.data_sincronizacao = timezone.localtime()
if self.total_participantes == 0:
self.total_participantes = self.inscritos_saberes
self.save()
def save(self, *args, **kwargs):
if self.status != Evento.STATUS_CANCELADO:
self.data_cancelamento = None
@ -579,13 +673,15 @@ class Evento(models.Model):
# É preciso salvar para poder usar o relacionamento com convites #
super().save(*args, **kwargs)
total = self.convite_set.aggregate(total=Sum("qtde_participantes"))[
"total"
]
if total and total > 0 and total != self.total_participantes:
self.total_participantes = total
# Salva de novo se o total de participantes mudou #
super().save(*args, **kwargs)
if self.total_participantes == 0:
total = self.convite_set.aggregate(
total=Sum("qtde_participantes")
)["total"]
if total and total > 0 and total != self.total_participantes:
self.total_participantes = total
# Salva de novo se o total de participantes mudou #
super().save(*args, **kwargs)
if self.status in [
Evento.STATUS_PLANEJAMENTO,

6
sigi/settings.py

@ -280,3 +280,9 @@ REGISTRO_PATH = Path(env("REGISTRO_PATH", default="/tmp/DNS/"))
MOODLE_BASE_URL = env("MOODLE_BASE_URL", default=None)
MOODLE_API_TOKEN = env("MOODLE_API_TOKEN", default=None)
MOODLE_STUDENT_ROLES = env("MOODLE_STUDENT_ROLES", eval, default=(5, 9))
# See [webroot]/completion/criteria/completion_criteria.php moodle code
# Search for COMPLETION_CRITERIA_TYPE_GRADE const definition
# define('COMPLETION_CRITERIA_TYPE_GRADE', 6);
MOODLE_COMPLETE_CRITERIA_TYPE = env(
"MOODLE_COMPLETE_CRITERIA_TYPE", int, default=6 # Type Grade
)

Loading…
Cancel
Save