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. 55
      sigi/apps/eventos/admin.py
  2. 50
      sigi/apps/eventos/migrations/0049_evento_aprovados_saberes_evento_data_sincronizacao_and_more.py
  3. 106
      sigi/apps/eventos/models.py
  4. 6
      sigi/settings.py

55
sigi/apps/eventos/admin.py

@ -89,8 +89,6 @@ class SolicitacaoResource(LabeledResourse):
class EventoResource(ValueLabeledResource): class EventoResource(ValueLabeledResource):
# categoria_evento = Field(column_name="tipo_evento__categoria")
# status = Field(column_name="status")
class Meta: class Meta:
model = Evento model = Evento
fields = ( fields = (
@ -119,6 +117,9 @@ class EventoResource(ValueLabeledResource):
"observacao", "observacao",
"publico_alvo", "publico_alvo",
"total_participantes", "total_participantes",
"inscritos_saberes",
"aprovados_saberes",
"data_sincronizacao",
"status", "status",
"data_cancelamento", "data_cancelamento",
"motivo_cancelamento", "motivo_cancelamento",
@ -594,11 +595,14 @@ class EventoAdmin(CartExportMixin, admin.ModelAdmin):
}, },
), ),
( (
_("Status"), _("Status/participação"),
{ {
"fields": ( "fields": (
"status", "status",
"total_participantes", "total_participantes",
"inscritos_saberes",
"aprovados_saberes",
"data_sincronizacao",
"data_cancelamento", "data_cancelamento",
"motivo_cancelamento", "motivo_cancelamento",
) )
@ -666,6 +670,11 @@ class EventoAdmin(CartExportMixin, admin.ModelAdmin):
"solicitante", "solicitante",
"num_processo", "num_processo",
) )
readonly_fields = (
"inscritos_saberes",
"aprovados_saberes",
"data_sincronizacao",
)
inlines = ( inlines = (
EquipeInline, EquipeInline,
ConviteInline, ConviteInline,
@ -1146,41 +1155,33 @@ class EventoAdmin(CartExportMixin, admin.ModelAdmin):
) )
return redirect(change_url) return redirect(change_url)
api_url = f"{settings.MOODLE_BASE_URL}/webservice/rest/server.php"
mws = Moodle(api_url, settings.MOODLE_API_TOKEN)
try: try:
inscritos = mws.post( evento.sincroniza_saberes()
"core_enrol_get_enrolled_users", except Evento.SaberesSyncException as e:
courseid=evento.moodle_courseid,
)
except Exception as e:
self.message_user( self.message_user(
request, request,
_( _(f"Erro ao sincronizar dados do Saberes: '{e.message}'"),
"Ocorreu um erro ao acessar o curso no Saberes com "
f"a mensagem {e.message}"
),
level=messages.ERROR, level=messages.ERROR,
) )
return redirect(change_url) return redirect(change_url)
evento.total_participantes = len(
list( self.message_user(
filter( request,
lambda u: any( _(
r["roleid"] in settings.MOODLE_STUDENT_ROLES f"Foram encontrados {evento.inscritos_saberes} alunos "
for r in u["roles"] f"no Saberes. Destes, {evento.aprovados_saberes} concluíram."
), ),
inscritos, level=messages.SUCCESS,
)
)
) )
evento.save() if evento.total_participantes != evento.inscritos_saberes:
self.message_user( self.message_user(
request, request,
_( _(
f"Foram encontrados {evento.total_participantes} alunos " "O total de participantes ficou em "
"no Saberes" f"{evento.total_participantes} alunos, pois o campo "
"já estava preenchido."
), ),
level=messages.SUCCESS, level=messages.WARNING,
) )
return redirect(change_url) 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",
),
),
]

106
sigi/apps/eventos/models.py

@ -1,7 +1,9 @@
from collections.abc import Iterable from collections.abc import Iterable
import datetime import datetime
import re import re
from moodle import Moodle
from tinymce.models import HTMLField from tinymce.models import HTMLField
from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -306,6 +308,11 @@ class AnexoSolicitacao(models.Model):
class Evento(models.Model): class Evento(models.Model):
class SaberesSyncException(Exception):
@property
def message(self):
return str(self)
STATUS_PLANEJAMENTO = "E" STATUS_PLANEJAMENTO = "E"
STATUS_AGUARDANDOSIGAD = "G" STATUS_AGUARDANDOSIGAD = "G"
STATUS_PREVISAO = "P" STATUS_PREVISAO = "P"
@ -404,13 +411,37 @@ class Evento(models.Model):
observacao = models.TextField(_("Observações e anotações"), blank=True) observacao = models.TextField(_("Observações e anotações"), blank=True)
publico_alvo = models.TextField(_("Público alvo"), blank=True) publico_alvo = models.TextField(_("Público alvo"), blank=True)
total_participantes = models.PositiveIntegerField( total_participantes = models.PositiveIntegerField(
_("Total de participantes"), _("total de participantes"),
default=0, default=0,
help_text=_( help_text=_(
"Se informar quantidade de participantes na aba de " "Se informar quantidade de participantes na aba de "
"convites, este campo será ajustado com a somatória " "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 = models.CharField(
_("Status"), max_length=1, choices=STATUS_CHOICES _("Status"), max_length=1, choices=STATUS_CHOICES
@ -543,6 +574,69 @@ class Evento(models.Model):
+ f"/course/view.php?id={self.moodle_courseid}" + 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): def save(self, *args, **kwargs):
if self.status != Evento.STATUS_CANCELADO: if self.status != Evento.STATUS_CANCELADO:
self.data_cancelamento = None self.data_cancelamento = None
@ -579,9 +673,11 @@ class Evento(models.Model):
# É preciso salvar para poder usar o relacionamento com convites # # É preciso salvar para poder usar o relacionamento com convites #
super().save(*args, **kwargs) super().save(*args, **kwargs)
total = self.convite_set.aggregate(total=Sum("qtde_participantes"))[
"total" 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: if total and total > 0 and total != self.total_participantes:
self.total_participantes = total self.total_participantes = total
# Salva de novo se o total de participantes mudou # # Salva de novo se o total de participantes mudou #

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_BASE_URL = env("MOODLE_BASE_URL", default=None)
MOODLE_API_TOKEN = env("MOODLE_API_TOKEN", default=None) MOODLE_API_TOKEN = env("MOODLE_API_TOKEN", default=None)
MOODLE_STUDENT_ROLES = env("MOODLE_STUDENT_ROLES", eval, default=(5, 9)) 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