Browse Source

Changes in Saberes dashboard

producao
Sesostris Vieira 10 years ago
parent
commit
8f286996ed
  1. 64
      sigi/apps/mdl/models.py
  2. 0
      sigi/apps/saberes/management/__init__.py
  3. 0
      sigi/apps/saberes/management/commands/__init__.py
  4. 103
      sigi/apps/saberes/management/commands/get_moodle_stats.py
  5. 34
      sigi/apps/saberes/models.py
  6. 14
      sigi/apps/saberes/templates/saberes/snippets.html
  7. 53
      sigi/apps/saberes/views.py

64
sigi/apps/mdl/models.py

@ -3,34 +3,52 @@
from __future__ import unicode_literals
from django.db import models
class CourseStatus(models.Model):
class CourseStats(models.Model):
# databaseview: (postgresql dialect):
#
# CREATE OR REPLACE VIEW ilb_course_status AS
# SELECT DISTINCT u.id AS userid, c.id AS courseid, c.category,
# -- View: sigi_course_stats
#
# DROP VIEW sigi_course_stats;
#
# CREATE OR REPLACE VIEW sigi_course_stats AS
# SELECT cc.id AS categoryid, c.id AS courseid,
# CASE
# WHEN e.enrol::text = 'ilbeadtutorado'::text AND ue.status = 1::bigint THEN 'Matrícula rejeitada'::text
# WHEN e.enrol::text = 'ilbead'::text AND ue.timeend < date_part('epoch'::text, now())::integer THEN 'Em curso'::text
# WHEN co.timestarted = 0 OR co.timestarted IS NULL THEN 'Abandono'::text
# WHEN co.timestarted > 0 AND co.timecompleted IS NULL THEN 'Reprovado'::text
# WHEN co.timecompleted IS NOT NULL THEN 'Aprovado'::text
# WHEN e.enrol::text <> 'ilbeadtutorado'::text THEN '?'::text
# ELSE ''::text
# END AS status
# FROM mdl_user u
# JOIN mdl_user_enrolments ue ON ue.userid = u.id
# JOIN mdl_enrol e ON e.id = ue.enrolid
# JOIN mdl_course c ON c.id = e.courseid
# LEFT JOIN mdl_course_completions co ON co.userid = u.id AND co.course = c.id;
userid = models.IntegerField(primary_key=True)
courseid = models.IntegerField()
category = models.IntegerField()
status = models.CharField(max_length=100)
# WHEN e.enrol = 'ilbeadtutorado' AND ue.status = 1 THEN 'N' -- Rejeitada
# WHEN e.enrol = 'ilbead' AND ue.timeend > date_part('epoch', now()) THEN 'C' -- Em curso
# WHEN (co.timestarted = 0 OR co.timestarted IS NULL) AND gg.finalgrade IS NOT NULL THEN 'R' -- Reprovada
# WHEN co.timestarted = 0 OR co.timestarted IS NULL THEN 'L' -- Abandono
# WHEN co.timestarted > 0 AND co.timecompleted IS NULL THEN 'R' -- Reprovado
# WHEN co.timecompleted IS NOT NULL THEN 'A' -- Aprovado
# ELSE 'I' -- Indeterminado
# END AS completionstatus, count(ue.id) AS usercount, avg(gg.finalgrade) as gradeaverage
# FROM mdl_course_categories cc
# JOIN mdl_course c ON c.category = cc.id
# JOIN mdl_enrol e ON e.courseid = c.id
# JOIN mdl_user_enrolments ue ON ue.enrolid = e.id
# JOIN mdl_grade_items gi ON gi.courseid = c.id AND gi.itemtype = 'course'
# LEFT JOIN mdl_grade_grades gg ON gg.itemid = gi.id AND gg.userid = ue.userid
# LEFT JOIN mdl_course_completions co ON co.userid = ue.userid AND co.course = c.id
# GROUP BY cc.id, c.id, completionstatus;
COMPLETIONSTATUS_CHOICES = (
('N', u'Matrículas rejeitadas'),
('C', u'Em curso'),
('R', u'Reprovação'),
('L', u'Abandono'),
('A', u'Aprovação'),
('I', u'Indeterminado'),)
category = models.ForeignKey('CourseCategories', db_column='categoryid', primary_key=True)
course = models.ForeignKey('Course', db_column='courseid')
completionstatus = models.CharField(max_length=1, choices=COMPLETIONSTATUS_CHOICES)
usercount = models.IntegerField()
gradeaverage = models.FloatField()
class Meta:
managed = False
db_table = 'ilb_course_status'
db_table = 'sigi_course_stats'
def __unicode__(self):
return '%s - %s: %s' % (self.category.name, self.course.fullname, self.usercount)
class Cohort(models.Model):
id = models.BigIntegerField(primary_key=True)

0
sigi/apps/saberes/management/__init__.py

0
sigi/apps/saberes/management/commands/__init__.py

103
sigi/apps/saberes/management/commands/get_moodle_stats.py

@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
#
# sigi.apps.servicos.management.commands.get_moodle_stats
#
# Copyright (c) 2014 by Interlegis
#
# GNU General Public License (GPL)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
from django.utils.translation import ugettext as _
from django.core.management.base import BaseCommand
from django.db.models import Sum, Avg
from sigi.apps.metas.views import gera_map_data_file
from sigi.apps.saberes.models import CategoriasInteresse, PainelItem
from sigi.apps.mdl.models import User, CourseStats
class Command(BaseCommand):
help = u'Get Moodle data and generate statistical panels.'
def handle(self, *args, **options):
areas = []
numeros = [
{'descricao': _(u'Total de usuários cadastrados'), 'valor': User.objects.count()},
{'descricao': _(u'Novos usuários cadastrados') , 'valor': User.objects.filter(firstaccess__gte=1392326052).count()}
]
for ci in CategoriasInteresse.objects.all():
if ci.coorte:
total_matriculas = ci.total_alunos_coorte()
elif ci.apurar_conclusao:
data = {x['completionstatus']: x for x in CourseStats.objects.filter(category__in=ci.categorias(subcategorias=True)). \
values('completionstatus').annotate(total_users=Sum('usercount'),grade_average=Avg('gradeaverage'))}
total_matriculas = sum(x['total_users'] for k, x in data.items())
else:
total_matriculas = CourseStats.objects.filter(category__in=ci.categorias(subcategorias=True)). \
aggreate(total_users=Sum('usercount'))['total_users']
dados = [{'descricao': _(u'Total de matrículas'), 'valor': total_matriculas}]
if ci.coorte:
for c in ci.categorias(subcategorias=True):
dados.append({'descricao': c.name, 'valor': c.total_alunos_cohort()})
if ci.apurar_conclusao:
if 'N' in data:
dados.append({'descricao': _(u'Matrículas rejeitadas'), 'help_text': _(u'demanda reprimida'),
'valor': data['N']['total_users'], 'percentual': 100.0 * data['N']['total_users'] / total_matriculas})
total_alunos = total_matriculas - data['N']['total_users']
dados.append({'descricao': _(u'Alunos efetivos'), 'help_text': _(u'os percentuais seguintes se referem a este indicador'),
'valor': total_alunos})
else:
total_alunos = total_matriculas
if 'C' in data:
dados.append({'descricao': _(u'Alunos em curso'), 'valor': data['C']['total_users'],
'percentual': 100.0 * data['C']['total_users'] / total_alunos })
if 'L' in data:
dados.append({'descricao': _(u'Alunos que abandonaram o curso'), 'valor': data['L']['total_users'],
'percentual': 100.0 * data['L']['total_users'] / total_alunos })
if 'R' in data:
dados.append({'descricao': _(u'Alunos reprovados'), 'valor': data['R']['total_users'],
'percentual': 100.0 * data['R']['total_users'] / total_alunos})
if 'A' in data:
dados.append({'descricao': _(u'Alunos aprovados'), 'valor': data['A']['total_users'],
'percentual': 100.0 * data['A']['total_users'] / total_alunos})
if 'I' in data:
dados.append({'descricao': _(u'Situação indefinida'), 'valor': data['I']['total_users'],
'help_text': _(u'Situação do aluno não pode ser determinada pelo sistema'),
'percentual': 100.0 * data['I']['total_users'] / total_alunos})
if 'A' in data:
dados.append({'descricao': _(u'Média das notas dos alunos aprovados (%)'), 'valor': int(data['A']['grade_average'])})
if 'R' in data:
dados.append({'descricao': _(u'Média das notas dos alunos reprovados (%)'), 'valor': int(data['R']['grade_average'])})
areas.append({'titulo': ci.descricao, 'dados': dados})
paineis = [{'titulo': _(u'Saberes em números'), 'dados': numeros}] + areas
PainelItem.objects.all().delete() # Clear dashboard
for p in paineis:
for d in p['dados']:
PainelItem.objects.create(painel=p['titulo'], descricao=d['descricao'], help_text= d['help_text'] if 'help_text' in
d else '', valor=d['valor'], percentual=d['percentual'] if 'percentual' in d else None)

34
sigi/apps/saberes/models.py

@ -23,8 +23,20 @@ class CategoriasInteresse(models.Model):
def __unicode__(self):
return self.descricao
def categorias(self):
return CourseCategories.objects.filter(idnumber__startswith=self.prefixo)
def categorias(self, subcategorias=False):
def get_sub_categorias(categorias):
result = CourseCategories.objects.none()
for c in categorias:
c_children = CourseCategories.objects.filter(parent=c)
result = result | c_children | get_sub_categorias(c_children)
return result
q = CourseCategories.objects.filter(idnumber__startswith=self.prefixo)
if subcategorias:
q = q | get_sub_categorias(q)
return q
def get_all_courses(self, only_visible=False):
q = Course.objects.none()
@ -44,8 +56,16 @@ class CategoriasInteresse(models.Model):
q = q | c.get_matriculas()
return q
def total_alunos(self):
if self.coorte:
return sum(c.total_alunos_cohort() for c in self.categorias())
else:
return sum([c.total_alunos() for c in self.categorias()])
def total_alunos_coorte(self):
return sum(c.total_alunos_cohort() for c in self.categorias())
# A temporary model to store Moodle processed data by management command (called from CRON)
class PainelItem(models.Model):
painel = models.CharField(max_length=255)
descricao = models.CharField(max_length=255)
help_text = models.CharField(max_length=255)
valor = models.IntegerField()
percentual = models.FloatField(null=True)
class Meta:
ordering = ['pk']

14
sigi/apps/saberes/templates/saberes/snippets.html

@ -1,7 +1,7 @@
{% load charts %}
<div class="row row-flex row-flex-wrap">
{% for painel in paineis %}
{% for k, painel in paineis.items %}
<div class="col-md-4">
<div class="panel panel-primary flex-col">
<div class="panel-heading">{{ painel.titulo }}</div>
@ -9,8 +9,16 @@
<table class='table'>
{% for linha in painel.dados %}
<tr>
<th>{{ linha.descricao }}</th>
<td class='number'>{{ linha.valor }}</td>
<th>
{{ linha.descricao }}
{% if linha.help_text %}
<span class="help-block">{{ linha.help_text }}</span>
{% endif %}
</th>
<td class='number' {% if not linha.percentual %}colspan="2"{% endif %}>{{ linha.valor }}</td>
{% if linha.percentual %}
<td class='number'>{{ linha.percentual|floatformat:2 }}%</td>
{% endif %}
</tr>
{% endfor %}
</table>

53
sigi/apps/saberes/views.py

@ -1,54 +1,21 @@
# -*- coding: utf-8 -*-
from collections import OrderedDict
from django.utils.translation import ugettext as _
from django.db.models import Count
from django.db.models import Sum, Avg
from django.shortcuts import render, render_to_response
from django.template import RequestContext
from sigi.apps.mdl.models import User, CourseStatus
from sigi.apps.saberes.models import CategoriasInteresse
from sigi.apps.mdl.models import User, CourseStats
from sigi.apps.saberes.models import CategoriasInteresse, PainelItem
def dashboard(request):
areas = []
numeros = [
{'descricao': _(u'Total de usuários cadastrados'), 'valor': User.objects.count()},
{'descricao': _(u'Novos usuários cadastrados') , 'valor': User.objects.filter(firstaccess__gte=1392326052).count()}
]
paineis = OrderedDict()
for ci in CategoriasInteresse.objects.all():
matriculas = ci.total_alunos()
numeros.append({'descricao': _(u'Total de matrículas em %s' % ci.descricao.lower()), 'valor': matriculas})
area = {'titulo': ci.descricao, 'dados': [{'descricao': _(u'Total de matrículas'), 'valor': matriculas}]}
for course in ci.get_all_courses(only_visible=True):
area['dados'].append({'descricao': course.fullname, 'valor': course.total_alunos()})
# if ci.apurar_alunos: # Apurar número de alunos
# valor = sum([curso.total_ativos() for curso in ci.get_all_courses()])
# area['dados'].append({'descricao': _(u'Total de alunos aceitos'), 'valor': valor})
# if ci.apurar_conclusao:
# cl = [curso.id for curso in ci.get_all_courses()]
# for cs in CourseStatus.objects.filter(courseid__in=cl).values('status').annotate(valor=Count('userid')):
# area['dados'].append({'descricao': cs['status'], 'valor': cs['valor']})
#
areas.append(area)
paineis = [{'titulo': _(u'Saberes em números'), 'dados': numeros}] + areas
for p in PainelItem.objects.all():
if p.painel not in paineis:
paineis[p.painel] = {'titulo': p.painel, 'dados': []}
paineis[p.painel]['dados'].append(p)
totais = []
# for i in MapaCategorias.INTERESSE_CHOICES:
# totais.append({'nome': i[1], 'total_turmas': '', 'total_alunos': '', 'padding': 0})
# for mapa in MapaCategorias.objects.filter(area_interesse=i[0]):
# totais.append({'nome': mapa.categoria.name, 'total_turmas': mapa.total_turmas(), 'total_alunos': mapa.total_alunos(), 'padding': 1})
# for c in mapa.categoria.children.all():
# totais.append({'nome': c.name, 'total_turmas': c.total_turmas(), 'total_alunos': c.total_alunos(), 'padding': 2})
tutorias = []
# for mapa in MapaCategorias.objects.filter(area_interesse='CT'):
# tutorias.append({'nome': mapa.categoria.name, 'inscritos': mapa.total_alunos(), 'padding': 0})
# for c in mapa.categoria.courses.all():
# tutorias.append({'nome': c.fullname, 'inscritos': c.total_alunos(), 'padding': 1})
extra_context = {'numeros': numeros, 'paineis': paineis, 'totais': totais, 'tutorias': tutorias}
extra_context = {'paineis': paineis}
return render_to_response('saberes/dashboard.html', extra_context, context_instance=RequestContext(request))
Loading…
Cancel
Save