@expose(template='.templates.welcome')
@identity.require(identity.not_anonymous())
def dashboard(self):
+ now = DateTimeCol.now()
if 'admin' in identity.current.permissions:
# TODO : Fijar el curso !!
correcciones = Correccion.selectBy(corrector=identity.current.user,
corregido=None).count()
- now = DateTimeCol.now()
instancias = list(InstanciaDeEntrega.select(
AND(InstanciaDeEntrega.q.inicio <= now,
InstanciaDeEntrega.q.fin > now))
.orderBy(InstanciaDeEntrega.q.fin))
- return dict(a_corregir=correcciones,
- instancias_activas=instancias, now=now)
+ return dict(a_corregir=correcciones,
+ instancias_activas=instancias, now=now)
+
+ if 'entregar' in identity.current.permissions:
+ instancias = list(InstanciaDeEntrega.select(
+ AND(InstanciaDeEntrega.q.inicio <= now,
+ InstanciaDeEntrega.q.fin > now))
+ .orderBy(InstanciaDeEntrega.q.fin))
+ return dict(instancias_activas=instancias, now=now)
+ return dict()
@expose(template='.templates.login')
def login(self, forward_url=None, previous_url=None, tg_errors=None, *args,
admin = identity.SecureObject(CatWalk(model), identity.has_permission('admin'))
+ mis_entregas = MisEntregasController()
+
#{{{ Agrega summarize a namespace tg de KID
def summarize(text, size, concat=True, continuation='...'):
"""Summarize a string if it's length is greater than a specified size. This
pk = DatabaseIndex(instancia, entregador, fecha, unique=True)
# Campos
archivos = BLOBCol(notNone=True) # ZIP con fuentes de la entrega
+ archivos_nombre = UnicodeCol(length=255)
correcta = BoolCol(default=None) # None es que no se sabe qué pasó
inicio_tareas = DateTimeCol(default=None) # Si es None no se procesó
fin_tareas = DateTimeCol(default=None) # Si es None pero inicio no, se está procesando
from grupo import GrupoController
from correccion import CorreccionController
from alumno_inscripto import AlumnoInscriptoController
+from misentregas import MisEntregasController
--- /dev/null
+# vim: set et sw=4 sts=4 encoding=utf-8 foldmethod=marker :
+
+#{{{ Imports
+import cherrypy
+from turbogears import controllers, expose, redirect
+from turbogears import validate, flash, error_handler
+from turbogears import validators as V
+from turbogears import widgets as W
+from turbogears import identity
+from turbogears import paginate
+from docutils.core import publish_parts
+from sercom.subcontrollers import validate as val
+from sercom.model import Entrega, Correccion, Curso, Ejercicio, InstanciaDeEntrega
+from sqlobject import *
+
+#}}}
+
+#{{{ Configuración
+cls = Entrega
+name = 'entrega'
+namepl = name + 's'
+#}}}
+
+#{{{ Validación
+def validate_get(id):
+ return val.validate_get(cls, name, id)
+
+def validate_set(id, data):
+ return val.validate_set(cls, name, id, data)
+
+def validate_new(data):
+ return val.validate_new(cls, name, data)
+#}}}
+
+def get_ejercicios_activos():
+ # TODO : Mostrar solo los ejercicios con instancias de entrega activos
+ return [(0, _(u'--'))] + [(fk.id, fk.shortrepr()) for fk in Ejercicio.select()]
+
+ajax = """
+ function clearInstancias ()
+ {
+ l = MochiKit.DOM.getElement('form_instancia');
+ l.options.length = 0;
+ l.disabled = true;
+ }
+
+ function mostrarInstancias(res)
+ {
+ clearInstancias();
+ for(i=0; i < res.instancias.length; i++) {
+ id = res.instancias[i].id;
+ label = res.instancias[i].numero;
+ MochiKit.DOM.appendChildNodes("form_instancia", OPTION({"value":id}, label))
+ }
+ if (l.options.length > 0)
+ l.disabled = false;
+ }
+
+ function err (err)
+ {
+ alert("The metadata for MochiKit.Async could not be fetched :(");
+ }
+
+ function actualizar_instancias ()
+ {
+ l = MochiKit.DOM.getElement('form_ejercicio');
+ id = l.options[l.selectedIndex].value;
+ if (id == 0) {
+ clearInstancias();
+ return;
+ }
+
+ url = "/mis_entregas/instancias?ejercicio_id="+id;
+ var d = loadJSONDoc(url);
+ d.addCallbacks(mostrarInstancias, err);
+ }
+
+ function prepare()
+ {
+ connect('form_ejercicio', 'onchange', actualizar_instancias);
+ clearInstancias();
+ }
+
+ MochiKit.DOM.addLoadEvent(prepare)
+"""
+#{{{ Formulario
+class EntregaForm(W.TableForm):
+ class Fields(W.WidgetsList):
+ ejercicio = W.SingleSelectField(label=_(u'Ejercicio'),
+ options=get_ejercicios_activos, validator=V.Int(not_empty=True))
+ instancia = W.SingleSelectField(label=_(u'Instancia de Entrega'), validator=V.Int(not_empty=True))
+ archivo = W.FileField(label=_(u'Archivo'), help_text=_(u'Archivo en formaro ZIP con tu entrega'))
+ fields = Fields()
+ javascript = [W.JSSource("MochiKit.DOM.focusOnLoad('form_ejercicio');"), W.JSSource(ajax)]
+
+form = EntregaForm()
+#}}}
+
+#{{{ Controlador
+class MisEntregasController(controllers.Controller, identity.SecureResource):
+ """Basic model admin interface"""
+ require = identity.has_permission('entregar')
+
+ @expose()
+ def default(self, tg_errors=None):
+ """handle non exist urls"""
+ raise redirect('list')
+
+ @expose()
+ def index(self):
+ raise redirect('list')
+
+ @expose(template='kid:%s.templates.new' % __name__)
+ def new(self, **kw):
+ """Create new records in model"""
+ return dict(name=name, namepl=namepl, form=form, values=kw)
+
+ @expose(template='kid:%s.templates.list' % __name__)
+ @paginate('records')
+ def list(self):
+ """List records in model"""
+ r = cls.select(cls.q.entregadorID == identity.current.user.id)
+ return dict(records=r, name=name, namepl=namepl)
+
+ @expose(template='kid:%s.templates.show' % __name__)
+ def show(self,id, **kw):
+ """Show record in model"""
+ r = validate_get(id)
+ if r.observaciones is None:
+ r.obs = ''
+ else:
+ r.obs = publish_parts(r.observaciones, writer_name='html')['html_body']
+ return dict(name=name, namepl=namepl, record=r)
+
+ @validate(form=form)
+ @error_handler(new)
+ @expose()
+ def create(self, archivo, ejercicio, **kw):
+ """Save or create record to model"""
+ kw['archivos'] = archivo.file.read()
+ kw['archivos_nombre'] = archivo.filename
+ kw['entregador'] = identity.current.user
+ validate_new(kw)
+ flash(_(u'Se creó una nueva %s.') % name)
+ raise redirect('list')
+
+ @expose("json")
+ def instancias(self, ejercicio_id):
+ c = Ejercicio.get(ejercicio_id)
+ return dict(instancias=c.instancias)
+#}}}
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<?python from sercom.model import Grupo, AlumnoInscripto ?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
+ py:extends="'../../../templates/master.kid'">
+<head>
+<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
+<title>list</title>
+</head>
+<body>
+
+<h1>Administración de <span py:replace="namepl">Objetos</span></h1>
+
+<table class="list">
+ <tr>
+ <th>Ejercicio</th>
+ <th><span title="Instancia de Entrega">IE</span></th>
+ <th>Correcta</th>
+ <th>InicioTareas</th>
+ <th>FinTareas</th>
+ <th>Observaciones</th>
+ <th>Operaciones</th>
+ </tr>
+ <tr py:for="record in records">
+ <td><span py:replace="record.instancia.ejercicio.enunciado.nombre">usuario</span></td>
+ <td><span py:replace="record.instancia.shortrepr()">usuario</span></td>
+ <td><span py:replace="record.correcta">fecha asignado</span></td>
+ <td><span py:replace="record.inicio_tareas">fecha corregido</span></td>
+ <td><span py:replace="record.fin_tareas">fecha corregido</span></td>
+ <td><span py:replace="record.observaciones">nota</span></td>
+ <td>
+ <a href="${tg.url('/mis_entregas/show/%d' % record.id)}">Ver</a>
+ <a href="${tg.url('/mis_entregas/show/%d' % record.id)}">Bajar Archivo</a>
+ </td>
+ </tr>
+</table>
+
+<br/>
+<a href="${tg.url('/mis_entregas/new')}">Agregar</a>
+
+<div py:for="page in tg.paginate.pages">
+ <a py:if="page != tg.paginate.current_page"
+ href="${tg.paginate.get_href(page)}">${page}</a>
+ <b py:if="page == tg.paginate.current_page">${page}</b>
+</div>
+
+</body>
+</html>
+
+<!-- vim: set et sw=4 sts=4 : -->
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
+ py:extends="'../../../templates/master.kid'">
+<head>
+<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
+<title>new</title>
+</head>
+<body>
+
+<h1>Crear Nuevo <span py:replace="name">Objeto</span></h1>
+
+<p py:replace="form(action=tg.url('/mis_entregas/create'), value=values, submit_text=_('Entregar!'))">Formulario</p>
+
+<br/>
+<a href="${tg.url('/mis_entregas/list')}">Cancelar</a>
+
+</body>
+</html>
</div>
</div>
-
<div py:if="'entregar' in identity.current.permissions and 'admin' not in identity.current.permissions">
- <h1>Soy entregar</h1>
+ <h1>Soy entregar</h1>
+ <h2>Instancias de Entrega</h2>
+ <div py:if="len(instancias_activas)">
+ <ul py:for="instancia in instancias_activas">
+ <li>
+ <?python delta = instancia.fin - now ?>
+ La entrega ${instancia.numero} del
+ ejercicio ${instancia.ejercicio.numero} vence
+ el ${instancia.fin.strftime(r'%A %d de %B a las %R')}
+ <br />
+ (falta ${delta.days} días,
+ ${delta.seconds//3600} horas y
+ ${delta.seconds//60%60} minutos).
+ </li>
+ </ul>
+ </div>
+ <div py:if="not len(instancias_activas)">
+ No hay Ejercicios con entregas en curso en este momento.
+ </div>
</div>
</body>
</html>
list(ifilterfalse(itemgetter('selected'), $(fromSelect).options))
);
}
-
'''
class AjaxDosListasSelect(widgets.MultipleSelectField):