From 0614cb0f3a4de38c68d61a090ef4820f19ff72e2 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Wed, 7 Mar 2007 03:30:21 +0000 Subject: [PATCH 01/16] Agregar algunos detalles a la TODO. --- TODO.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/TODO.txt b/TODO.txt index b656dc9..eb606aa 100644 --- a/TODO.txt +++ b/TODO.txt @@ -10,3 +10,10 @@ - Hacer DB con datos de prueba (nico) - Hacer archivos de prueba para las cargas masivas (nico) - Hacer Informe bonito para pelu (un poco cada uno) +- InstanciaDeEntrega (ejercicio/entrega): + * Sacar el campo "procesada?" de la creación (lo maneja internamente el + backend, en modificar tal vez se podría poner pero mucho sentido no tiene + tampoco, mejor que se vea nomás, pero que no se modifique). + * Poner el checkbox Activo? por default chequeado. +- Ejercicio: + * Poner una columna en el listado con la cantidad de entregas. -- 2.43.0 From d96b27ddfa2dfdd72abb3d7d18fd74564052aa12 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Wed, 7 Mar 2007 19:11:09 +0000 Subject: [PATCH 02/16] =?utf8?q?Actualizar=20parte=20de=20tareas=20y=20pru?= =?utf8?q?ebas=20del=20modelo.=20Nuevo=20modelo=20de=20la=20parte=20de=20p?= =?utf8?q?ruebas.=20Ahora=20un=20enunciado=20tiene=20tareas:=20"tareas=20d?= =?utf8?q?e=20fuente"=20(TareaFuente)=20y=20"tareas=20de=20prueba"=20(Tare?= =?utf8?q?aPrueba).=20Ambos=20no=20son=20mucho=20m=C3=A1s=20que=20contened?= =?utf8?q?ores=20de=20Comandos=20(ComandoFuente=20y=20ComandoPrueba),=20pe?= =?utf8?q?ro=20con=20un=20cierto=20orden.=20Las=20cosas=20"de=20fuente"=20?= =?utf8?q?se=20aplican=20a=20los=20fuentes,=20una=20sola=20vez.=20Un=20Com?= =?utf8?q?ando/TareaFuente=20podr=C3=ADa=20ser=20compilar.=20O=20pasar=20u?= =?utf8?q?n=20analizador=20est=C3=A1tico=20de=20complejidad,=20o=20un=20de?= =?utf8?q?tector=20de=20copias.=20Un=20Comando/TareaPrueba,=20se=20corre?= =?utf8?q?=20sobre=20cada=20CasoDePrueba=20que=20tenga=20el=20Enunciado=20?= =?utf8?q?y=20podr=C3=ADa=20ser=20una=20prueba=20llana=20y=20sencilla,=20o?= =?utf8?q?=20correrlo=20con=20valgrind,=20etc.=20Cada=20entrega=20tiene=20?= =?utf8?q?ComandoFuenteEjecutado,=20que=20es=20el=20resultado=20de=20corre?= =?utf8?q?r=20un=20ComandoFuente=20sobre=20una=20cierta=20entrega.=20Tambi?= =?utf8?q?=C3=A9n=20cada=20entrega=20tiene=20varias=20Pruebas,=20cada=20un?= =?utf8?q?a=20con=20informaci=C3=B3n=20de=20la=20corrida=20de=20un=20CasoD?= =?utf8?q?ePrueba.=20Cada=20prueba,=20tiene=20a=20su=20vez=20varios=20Coma?= =?utf8?q?ndoPruebaEjecutado,=20que=20representan=20como=20fue=20corrido?= =?utf8?q?=20cada=20ComandoPrueba=20sobre=20ese=20CasoDePrueba=20para=20es?= =?utf8?q?a=20entrega.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Faltaría resolver el tema de los archivos, que seguramente van a ser guardados en el filesystem, pero conceptualmente, cada comando tiene archivos de entrada (entrada para el comando) y archivos de salida (archivos generados por el comando). Además la entraga tiene que tener archivos asociados, el código fuente que entrega el alumno. --- doc/testdata.py | 54 +++--- sercom/model.py | 484 ++++++++++++++++++++++++------------------------ 2 files changed, 268 insertions(+), 270 deletions(-) diff --git a/doc/testdata.py b/doc/testdata.py index 26d01d1..5a6d79f 100644 --- a/doc/testdata.py +++ b/doc/testdata.py @@ -1,66 +1,74 @@ # vim: set et sw=4 sts=4 encoding=utf-8 foldmethod=marker : +# Roles r1 = Rol(nombre='admin', permisos=(entregar_tp, admin)) - r2 = Rol(nombre='alumno', permisos=(entregar_tp,)) +# Usuarios d = Docente(usuario='luca', nombre=u'Leandro Lucarella', password='luca', email='llucax@gmail.com', telefono='4554-1554', roles=[r1], activo=True) - a = Alumno(padron='77891', nombre='Tito Puente', password='77891', roles=[r2]) -t1 = Tarea(nombre='compilar') -t2 = Tarea(nombre='probar', dependencias=(t1,)) -t3 = Tarea(nombre=u'configurar detector de copias') -t4 = Tarea(nombre=u'detectar copias', dependencias=(t3, t2)) - +# Tareas y comandos +tf = TareaFuente(nombre='Compilar C con Makefile', + terminar_si_falla=True, rechazar_si_falla=True) +cf = tf.add_comando(1, 'make -f Makefile', retorno=0, terminar_si_falla=True, + rechazar_si_falla=True, + descripcion='Compila usando un Makefile predeterminado') +tp = TareaPrueba(nombre='Probar', terminar_si_falla=True, + rechazar_si_falla=True) +cp = tp.add_comando(1, [], retorno=0, terminar_si_falla=True, + rechazar_si_falla=True, descripcion='Prueba normalmente, sin filtros') + +# Enunciados e1 = Enunciado(nombre=u'Un enunciado', anio=2007, cuatrimestre=1, autor=d, - descripcion=u'Ejercicio reeee jodido', tareas=(t4,)) + descripcion=u'Ejercicio reeee jodido', tareas=(tf, tp)) e2 = Enunciado(nombre=u'Otro enunciado', anio=2007, cuatrimestre=1, autor=d, - descripcion=u'Ejercicio facilongo', tareas=(t2, t4)) + descripcion=u'Ejercicio facilongo', tareas=(tf,)) e3 = d.add_enunciado(u'Más enunciados', anio=2007, cuatrimestre=1, descripcion=u'Ejercicio anónimo') +# Cursos c = Curso(anio=2007, cuatrimestre=1, numero=1, descripcion=u'Martes', docentes=[d], ejercicios=[e1, e2]) +# Casos de prueba cp1 = e1.add_caso_de_prueba(nombre=u'Sin parámetros', retorno=0, descripcion=u'Un caso') cp2 = e1.add_caso_de_prueba(nombre=u'2 parámetross', retorno=0, parametros='--test -c "con espacios"') +# Ejercicios ej1 = c.ejercicios[0] ej1.grupal = True ej2 = c.ejercicios[1] -ide = ej1.add_instancia(numero=1, inicio=datetime(2007, 1, 25), - fin=datetime(2007, 1, 31, 20), observaciones='Entrega fea', activo=False, - tareas=(t2, t4)) +# Instancias de entrega +ide = ej1.add_instancia(numero=1, inicio=datetime(2007, 2, 25), + fin=datetime(2007, 3, 31, 20), observaciones='Entrega fea', activo=True) +# Docentes/Alumnos/Grupos inscriptos di = c.docentes[0] - ai1 = c.add_alumno(a) ai2 = c.add_alumno(Alumno(padron='83525', nombre=u'Pepe Lui'), tutor=di) - g1 = c.add_grupo(5) g2 = c.add_grupo(8, responsable=ai2, miembros=[ai1], tutores=[di]) - g2.add_miembro(ai2) +# Entregas entrega = ai1.add_entrega(ide) ai2.add_entrega(ide, correcta=True) entrega2 = g1.add_entrega(ide, correcta=True) d.add_entrega(ide, correcta=True, observaciones='Prueba de docente') -te = entrega.add_tarea_ejecutada(t1) -entrega.add_tarea_ejecutada(t2) -entrega2.add_tarea_ejecutada(t1, inicio=datetime(2007, 1, 2), - fin=datetime.now(), exito=True) -entrega2.add_tarea_ejecutada(t2, observaciones='Va a tardar') - -te.add_prueba(cp1, inicio=datetime(2007, 1, 7), fin=datetime.now(), pasada=True) -te.add_prueba(cp2) +# Comandos ejecutados / pruebas +cpe = entrega.add_comando_ejecutado(cf, exito=True, + fin=datetime(2007, 2, 25, 10, 13, 34), + inicio=datetime(2007, 2, 25, 10, 12, 34)) +p = entrega.add_prueba(cp1) +p.add_comando_ejecutado(cp) +# Correcciones di.add_correccion(entrega, asignado=datetime(2007, 1, 19), nota=7.5, corregido=datetime.now(), observaciones=u'Le faltó un punto') di.add_correccion(entrega2) diff --git a/sercom/model.py b/sercom/model.py index b2bc3a2..7d4ed29 100644 --- a/sercom/model.py +++ b/sercom/model.py @@ -14,7 +14,7 @@ from formencode import Invalid hub = PackageHub("sercom") __connection__ = hub -__all__ = ('Curso', 'Usuario', 'Docente', 'Alumno', 'Tarea', 'CasoDePrueba') +__all__ = ('Curso', 'Usuario', 'Docente', 'Alumno', 'CasoDePrueba') #{{{ Custom Columns @@ -87,20 +87,6 @@ class ParamsCol(UnicodeCol): #}}} -#{{{ Tablas intermedias - -# BUG en SQLObject, SQLExpression no tiene cálculo de hash pero se usa como -# key de un dict. Workarround hasta que lo arreglen. -SQLExpression.__hash__ = lambda self: hash(str(self)) - -instancia_tarea_t = table.instancia_tarea - -enunciado_tarea_t = table.enunciado_tarea - -dependencia_t = table.dependencia - -#}}} - #{{{ Clases def srepr(obj): #{{{ @@ -244,8 +230,7 @@ class Usuario(InheritableSQLObject): #{{{ def by_user_name(cls, user_name): # para identity user = cls.byUsuario(user_name) if not user.activo: - raise SQLObjectNotFound, "The object %s with user_name %s is " \ - "not active" % (cls.__name__, user_name) + raise SQLObjectNotFound(_(u'El %s está inactivo' % cls.__name__)) return user def _get_groups(self): # para identity @@ -266,7 +251,7 @@ class Usuario(InheritableSQLObject): #{{{ return self.contrasenia def __repr__(self): - raise NotImplementedError, _('Clase abstracta!') + raise NotImplementedError(_(u'Clase abstracta!')) def shortrepr(self): return '%s (%s)' % (self.usuario, self.nombre) @@ -288,8 +273,7 @@ class Docente(Usuario): #{{{ autor=self, **kw) def remove_enunciado(self, nombre, anio, cuatrimestre): - Enunciado.pk.get(nombre=nombre, anio=anio, - cuatrimestre=cuatrimestre).destroySelf() + Enunciado.pk.get(nombre, anio, cuatrimestre).destroySelf() def __repr__(self): return 'Docente(id=%s, usuario=%s, nombre=%s, password=%s, email=%s, ' \ @@ -332,94 +316,118 @@ class Alumno(Usuario): #{{{ #}}} class Tarea(InheritableSQLObject): #{{{ - class sqlmeta: - createSQL = dict(sqlite=r''' -CREATE TABLE dependencia ( - padre_id INTEGER NOT NULL CONSTRAINT tarea_id_exists - REFERENCES tarea(id) ON DELETE CASCADE, - hijo_id INTEGER NOT NULL CONSTRAINT tarea_id_exists - REFERENCES tarea(id) ON DELETE CASCADE, - orden INT, - PRIMARY KEY (padre_id, hijo_id) -)''') # Clave - nombre = UnicodeCol(length=30, alternateID=True) + nombre = UnicodeCol(length=30, alternateID=True) # Campos - descripcion = UnicodeCol(length=255, default=None) + descripcion = UnicodeCol(length=255, default=None) + terminar_si_falla = BoolCol(notNone=True, default=True) + rechazar_si_falla = BoolCol(notNone=True, default=True) # Joins + enunciados = RelatedJoin('Enunciado', addRemoveName='_enunciado') + + def __repr__(self): + raise NotImplementedError('Tarea es una clase abstracta') - def __init__(self, dependencias=(), **kw): - super(Tarea, self).__init__(**kw) - if dependencias: - self.dependencias = dependencias - - def set(self, dependencias=None, **kw): - super(Tarea, self).set(**kw) - if dependencias is not None: - self.dependencias = dependencias - - def _get_dependencias(self): - OtherTarea = Alias(Tarea, 'other_tarea') - self.__dependencias = tuple(Tarea.select( - AND( - Tarea.q.id == dependencia_t.hijo_id, - OtherTarea.q.id == dependencia_t.padre_id, - self.id == dependencia_t.padre_id, - ), - clauseTables=(dependencia_t,), - orderBy=dependencia_t.orden, - )) - return self.__dependencias - - def _set_dependencias(self, dependencias): - orden = {} - for i, t in enumerate(dependencias): - orden[t.id] = i - new = frozenset([t.id for t in dependencias]) - old = frozenset([t.id for t in self.dependencias]) - dependencias = dict([(t.id, t) for t in dependencias]) - for tid in old - new: # eliminadas - self._connection.query(str(Delete(dependencia_t, where=AND( - dependencia_t.padre_id == self.id, - dependencia_t.hijo_id == tid)))) - for tid in new - old: # creadas - self._connection.query(str(Insert(dependencia_t, values=dict( - padre_id=self.id, hijo_id=tid, orden=orden[tid] - )))) - for tid in new & old: # actualizados - self._connection.query(str(Update(dependencia_t, - values=dict(orden=orden[tid]), where=AND( - dependencia_t.padre_id == self.id, - dependencia_t.hijo_id == tid, - )))) + def shortrepr(self): + return self.nombre +#}}} + +class TareaFuente(Tarea): #{{{ + # Joins + comandos = MultipleJoin('ComandoFuente') + + def add_comando(self, orden, comando, **kw): + return ComandoFuente(tarea=self, orden=orden, comando=comando, **kw) + + def remove_comando(self, orden): + ComandoFuente.pk.get(self.id, orden).destroySelf() def __repr__(self): - return 'Tarea(id=%s, nombre=%s, descripcion=%s)' \ + return 'TareaFuente(id=%s, nombre=%s, descripcion=%s)' \ % (self.id, self.nombre, self.descripcion) +#}}} + +class TareaPrueba(Tarea): #{{{ + # Joins + comandos = MultipleJoin('ComandoPrueba') + + def add_comando(self, orden, comando, **kw): + return ComandoPrueba(tarea=self, orden=orden, comando=comando, **kw) + + def remove_comando(self, orden): + ComandoPrueba.pk.get(self.id, orden).destroySelf() + + def __repr__(self): + return 'TareaPrueba(id=%s, nombre=%s, descripcion=%s)' \ + % (self.id, self.nombre, self.descripcion) +#}}} + +class Comando(SQLObject): #{{{ + # Campos + comando = ParamsCol(length=255, notNone=True) + descripcion = UnicodeCol(length=255, default=None) + retorno = IntCol(default=None) + terminar_si_falla = BoolCol(notNone=True, default=True) + rechazar_si_falla = BoolCol(notNone=True, default=True) +# archivos_entrada = list(ArchivoEntrada) #TODO +# archivos_salida = list(ArchivoSalida) #TODO + + def ejecutar(self): pass # TODO + + def __repr__(self): + raise NotImplementedError('Comando es una clase abstracta') def shortrepr(self): return self.nombre #}}} +class ComandoFuente(Comando): #{{{ + # Clave + tarea = ForeignKey('TareaFuente', notNone=True, cascade=True) + orden = IntCol(notNone=True) + pk = DatabaseIndex(tarea, orden, unique=True) + # Campos + tiempo_cpu = FloatCol(default=None) + + def ejecutar(self): pass # TODO + + def __repr__(self): + return 'ComandoFuente(tarea=%s, orden=%s, comando=%s, descripcion=%s, ' \ + 'retorno=%s, tiempo_cpu=%s, terminar_si_falla=%s, ' \ + 'rechazar_si_falla=%s)' \ + % (srepr(self.tarea), self.orden, self.comando, self.descripcion, + self.retorno, self.tiempo_cpu, self.terminar_si_falla, + self.rechazar_si_falla) +#}}} + +class ComandoPrueba(Comando): #{{{ + # Clave + tarea = ForeignKey('TareaPrueba', notNone=True, cascade=True) + orden = IntCol(notNone=True) + pk = DatabaseIndex(tarea, orden, unique=True) + # Campos + multipl_tiempo_cpu = FloatCol(notNone=True, default=1.0) + + def ejecutar(self): pass # TODO + + def __repr__(self): + return 'ComandoPrueba(tarea=%s, orden=%s, comando=%s, descripcion=%s, ' \ + 'retorno=%s, tiempo_cpu=%s, terminar_si_falla=%s, ' \ + 'rechazar_si_falla=%s)' \ + % (srepr(self.tarea), self.orden, self.comando, self.descripcion, + self.retorno, self.tiempo_cpu, self.terminar_si_falla, + self.rechazar_si_falla) +#}}} + class Enunciado(SQLObject): #{{{ - class sqlmeta: - createSQL = dict(sqlite=r''' -CREATE TABLE enunciado_tarea ( - enunciado_id INTEGER NOT NULL CONSTRAINT enunciado_id_exists - REFERENCES enunciado(id) ON DELETE CASCADE, - tarea_id INTEGER NOT NULL CONSTRAINT tarea_id_exists - REFERENCES tarea(id) ON DELETE CASCADE, - orden INT, - PRIMARY KEY (enunciado_id, tarea_id) -)''') # Clave nombre = UnicodeCol(length=60) anio = IntCol(notNone=True) cuatrimestre = IntCol(notNone=True) pk = DatabaseIndex(nombre, anio, cuatrimestre, unique=True) # Campos - autor = ForeignKey('Docente', cascade='null') descripcion = UnicodeCol(length=255, default=None) + autor = ForeignKey('Docente', cascade='null') creado = DateTimeCol(notNone=True, default=DateTimeCol.now) archivo = BLOBCol(default=None) archivo_name = UnicodeCol(length=255, default=None) @@ -427,16 +435,20 @@ CREATE TABLE enunciado_tarea ( # Joins ejercicios = MultipleJoin('Ejercicio') casos_de_prueba = MultipleJoin('CasoDePrueba') + tareas = RelatedJoin('Tarea', addRemoveName='_tarea') - def __init__(self, tareas=(), **kw): + def __init__(self, tareas=[], **kw): super(Enunciado, self).__init__(**kw) - if tareas: - self.tareas = tareas + for tarea in tareas: + self.add_tarea(tarea) def set(self, tareas=None, **kw): super(Enunciado, self).set(**kw) if tareas is not None: - self.tareas = tareas + for tarea in self.tareas: + self.remove_tarea(tarea) + for tarea in tareas: + self.add_tarea(tarea) @classmethod def selectByCurso(self, curso): @@ -445,40 +457,6 @@ CREATE TABLE enunciado_tarea ( def add_caso_de_prueba(self, nombre, **kw): return CasoDePrueba(enunciado=self, nombre=nombre, **kw) - def _get_tareas(self): - self.__tareas = tuple(Tarea.select( - AND( - Tarea.q.id == enunciado_tarea_t.tarea_id, - Enunciado.q.id == enunciado_tarea_t.enunciado_id, - Enunciado.q.id == self.id - ), - clauseTables=(enunciado_tarea_t, Enunciado.sqlmeta.table), - orderBy=enunciado_tarea_t.orden, - )) - return self.__tareas - - def _set_tareas(self, tareas): - orden = {} - for i, t in enumerate(tareas): - orden[t.id] = i - new = frozenset([t.id for t in tareas]) - old = frozenset([t.id for t in self.tareas]) - tareas = dict([(t.id, t) for t in tareas]) - for tid in old - new: # eliminadas - self._connection.query(str(Delete(enunciado_tarea_t, where=AND( - enunciado_tarea_t.enunciado_id == self.id, - enunciado_tarea_t.tarea_id == tid)))) - for tid in new - old: # creadas - self._connection.query(str(Insert(enunciado_tarea_t, values=dict( - enunciado_id=self.id, tarea_id=tid, orden=orden[tid] - )))) - for tid in new & old: # actualizados - self._connection.query(str(Update(enunciado_tarea_t, - values=dict(orden=orden[tid]), where=AND( - enunciado_tarea_t.enunciado_id == self.id, - enunciado_tarea_t.tarea_id == tid, - )))) - def __repr__(self): return 'Enunciado(id=%s, autor=%s, nombre=%s, descripcion=%s, ' \ 'creado=%s)' \ @@ -491,18 +469,19 @@ CREATE TABLE enunciado_tarea ( class CasoDePrueba(SQLObject): #{{{ # Clave - enunciado = ForeignKey('Enunciado', cascade=True) - nombre = UnicodeCol(length=40, notNone=True) - pk = DatabaseIndex(enunciado, nombre, unique=True) + enunciado = ForeignKey('Enunciado', cascade=True) + nombre = UnicodeCol(length=40, notNone=True) + pk = DatabaseIndex(enunciado, nombre, unique=True) # Campos - privado = IntCol(default=None) # TODO iria en instancia_de_entrega_caso_de_prueba - parametros = ParamsCol(length=255, default=None) - retorno = IntCol(default=None) - tiempo_cpu = FloatCol(default=None) - descripcion = UnicodeCol(length=255, default=None) - activo = BoolCol(notNone=True, default=True) + descripcion = UnicodeCol(length=255, default=None) + terminar_si_falla = BoolCol(notNone=True, default=False) + rechazar_si_falla = BoolCol(notNone=True, default=True) + parametros = ParamsCol(length=255, default=None) + retorno = IntCol(default=None) + tiempo_cpu = FloatCol(default=None) + activo = BoolCol(notNone=True, default=True) # Joins - pruebas = MultipleJoin('Prueba') + pruebas = MultipleJoin('Prueba') def __repr__(self): return 'CasoDePrueba(enunciado=%s, nombre=%s, parametros=%s, ' \ @@ -521,7 +500,7 @@ class Ejercicio(SQLObject): #{{{ pk = DatabaseIndex(curso, numero, unique=True) # Campos enunciado = ForeignKey('Enunciado', notNone=True, cascade=False) - grupal = BoolCol(notNone=True, default=False) + grupal = BoolCol(default=False) # None es grupal o individual # Joins instancias = MultipleJoin('InstanciaDeEntrega') @@ -530,7 +509,8 @@ class Ejercicio(SQLObject): #{{{ fin=fin, **kw) def remove_instancia(self, numero): - InstanciaDeEntrega.pk.get(ejercicio=self, numero=numero).destroySelf() + # FIXME self.id + InstanciaDeEntrega.pk.get(self.id, numero).destroySelf() def __repr__(self): return 'Ejercicio(id=%s, curso=%s, numero=%s, enunciado=%s, ' \ @@ -545,16 +525,6 @@ class Ejercicio(SQLObject): #{{{ #}}} class InstanciaDeEntrega(SQLObject): #{{{ - class sqlmeta: - createSQL = dict(sqlite=r''' -CREATE TABLE instancia_tarea ( - instancia_id INTEGER NOT NULL CONSTRAINT instancia_id_exists - REFERENCES instancia_de_entrega(id) ON DELETE CASCADE, - tarea_id INTEGER NOT NULL CONSTRAINT tarea_id_exists - REFERENCES tarea(id) ON DELETE CASCADE, - orden INT, - PRIMARY KEY (instancia_id, tarea_id) -)''') # Clave ejercicio = ForeignKey('Ejercicio', notNone=True, cascade=True) numero = IntCol(notNone=True) @@ -569,50 +539,6 @@ CREATE TABLE instancia_tarea ( entregas = MultipleJoin('Entrega', joinColumn='instancia_id') correcciones = MultipleJoin('Correccion', joinColumn='instancia_id') - def __init__(self, tareas=(), **kw): - super(InstanciaDeEntrega, self).__init__(**kw) - if tareas: - self.tareas = tareas - - def set(self, tareas=None, **kw): - super(InstanciaDeEntrega, self).set(**kw) - if tareas is not None: - self.tareas = tareas - - def _get_tareas(self): - self.__tareas = tuple(Tarea.select( - AND( - Tarea.q.id == instancia_tarea_t.tarea_id, - InstanciaDeEntrega.q.id == instancia_tarea_t.instancia_id, - InstanciaDeEntrega.q.id == self.id, - ), - clauseTables=(instancia_tarea_t, InstanciaDeEntrega.sqlmeta.table), - orderBy=instancia_tarea_t.orden, - )) - return self.__tareas - - def _set_tareas(self, tareas): - orden = {} - for i, t in enumerate(tareas): - orden[t.id] = i - new = frozenset([t.id for t in tareas]) - old = frozenset([t.id for t in self.tareas]) - tareas = dict([(t.id, t) for t in tareas]) - for tid in old - new: # eliminadas - self._connection.query(str(Delete(instancia_tarea_t, where=AND( - instancia_tarea_t.instancia_id == self.id, - instancia_tarea_t.tarea_id == tid)))) - for tid in new - old: # creadas - self._connection.query(str(Insert(instancia_tarea_t, values=dict( - instancia_id=self.id, tarea_id=tid, orden=orden[tid] - )))) - for tid in new & old: # actualizados - self._connection.query(str(Update(instancia_tarea_t, - values=dict(orden=orden[tid]), where=AND( - instancia_tarea_t.instancia_id == self.id, - instancia_tarea_t.tarea_id == tid, - )))) - def __repr__(self): return 'InstanciaDeEntrega(id=%s, numero=%s, inicio=%s, fin=%s, ' \ 'procesada=%s, observaciones=%s, activo=%s)' \ @@ -641,8 +567,8 @@ class DocenteInscripto(SQLObject): #{{{ entregador=entrega.entregador, corrector=self, **kw) def remove_correccion(self, instancia, entregador): - Correccion.pk.get(instancia=instancia, - entregador=entregador).destroySelf() + # FIXME instancia.id, entregador.id + Correccion.pk.get(instancia.id, entregador.id).destroySelf() def __repr__(self): return 'DocenteInscripto(id=%s, docente=%s, corrige=%s, ' \ @@ -705,29 +631,25 @@ class Grupo(Entregador): #{{{ def add_miembro(self, alumno, **kw): if isinstance(alumno, AlumnoInscripto): - kw['alumno'] = alumno - else: - kw['alumnoID'] = alumno - return Miembro(grupo=self, **kw) + alumno = alumno.id + return Miembro(grupo=self, alumnoID=alumno, **kw) def remove_miembro(self, alumno): if isinstance(alumno, AlumnoInscripto): - Miembro.pk.get(grupo=self, alumno=alumno).destroySelf() - else: - Miembro.pk.get(grupo=self, alumnoID=alumno).destroySelf() + alumno = alumno.id + # FIXME self.id + Miembro.pk.get(self.id, alumno).destroySelf() def add_tutor(self, docente, **kw): if isinstance(docente, DocenteInscripto): - kw['docente'] = docente - else: - kw['docenteID'] = docente - return Tutor(grupo=self, **kw) + docente = docente.id + return Tutor(grupo=self, docenteID=docente, **kw) def remove_tutor(self, docente): if isinstance(docente, DocenteInscripto): - Tutor.pk.get(grupo=self, docente=docente).destroySelf() - else: - Tutor.pk.get(grupo=self, docenteID=docente).destroySelf() + docente = docente.id + # FIXME self.id + Tutor.pk.get(self.id, docente).destroySelf() def __repr__(self): return 'Grupo(id=%s, nombre=%s, responsable=%s, nota=%s, ' \ @@ -754,6 +676,9 @@ class AlumnoInscripto(Entregador): #{{{ entregas = MultipleJoin('Entrega', joinColumn='alumno_id') correcciones = MultipleJoin('Correccion', joinColumn='alumno_id') + def _get_nombre(self): + return self.alumno.padron + def __repr__(self): return 'AlumnoInscripto(id=%s, alumno=%s, condicional=%s, nota=%s, ' \ 'nota_cursada=%s, tutor=%s, observaciones=%s, activo=%s)' \ @@ -804,21 +729,39 @@ class Miembro(SQLObject): #{{{ class Entrega(SQLObject): #{{{ # Clave - instancia = ForeignKey('InstanciaDeEntrega', notNone=True, cascade=False) - entregador = ForeignKey('Entregador', default=None, cascade=False) # Si es None era un Docente - fecha = DateTimeCol(notNone=True, default=DateTimeCol.now) - pk = DatabaseIndex(instancia, entregador, fecha, unique=True) + instancia = ForeignKey('InstanciaDeEntrega', notNone=True, cascade=False) + entregador = ForeignKey('Entregador', default=None, cascade=False) # Si es None era un Docente + fecha = DateTimeCol(notNone=True, default=DateTimeCol.now) + pk = DatabaseIndex(instancia, entregador, fecha, unique=True) # Campos - correcta = BoolCol(notNone=True, default=False) - observaciones = UnicodeCol(default=None) + correcta = BoolCol(default=None) + inicio_tareas = DateTimeCol(default=None) + fin_tareas = DateTimeCol(default=None) + observaciones = UnicodeCol(default=None) # Joins - tareas = MultipleJoin('TareaEjecutada') + comandos_ejecutados = MultipleJoin('ComandoFuenteEjecutado') + pruebas = MultipleJoin('Prueba') # Para generar código - codigo_dict = r'0123456789abcdefghijklmnopqrstuvwxyz_.,*@#+' - codigo_format = r'%m%d%H%M%S' + codigo_dict = r'0123456789abcdefghijklmnopqrstuvwxyz_.,*@#+' + codigo_format = r'%m%d%H%M%S' + + def add_comando_ejecutado(self, comando, **kw): + return ComandoFuenteEjecutado(entrega=self, comando=comando, **kw) + + def remove_comando_ejecutado(self, comando): + if isinstance(comando, ComandoFuente): + comando = comando.id + # FIXME self.id + ComandoFuenteEjecutado.pk.get(self.id, comando).destroySelf() + + def add_prueba(self, caso_de_prueba, **kw): + return Prueba(entrega=self, caso_de_prueba=caso_de_prueba, **kw) - def add_tarea_ejecutada(self, tarea, **kw): - return TareaEjecutada(tarea=tarea, entrega=self, **kw) + def remove_prueba(self, caso_de_prueba): + if isinstance(caso_de_prueba, CasoDePrueba): + caso_de_prueba = caso_de_prueba.id + # FIXME self.id, caso_de_prueba + Prueba.pk.get(self.id, caso_de_prueba).destroySelf() def _get_codigo(self): if not hasattr(self, '_codigo'): # cache @@ -836,11 +779,29 @@ class Entrega(SQLObject): #{{{ self._SO_set_fecha(fecha) if hasattr(self, '_codigo'): del self._codigo # bye, bye cache! + def _get_path(self): + import os.path + def path_join(*args): + return os.path.join(*[unicode(p) for p in args]) + curso = self.entregador.curso + instancia = self.instancia + ejercicio = instancia.ejercicio + fecha = self.fecha.strftime(r'%Y-%m-%d_%H.%M.%S') + print ejercicio + return path_join(curso.anio, curso.cuatrimestre, curso.numero, + ejercicio.numero, instancia.numero, self.entregador.nombre, fecha) + # FIXME un grupo con nombre tipo "../../lala" puede romper todo. + # Hacer que el nombre del grupo sea numérico (o validar que + # sean solo caracteres inofensivos: letras ASCII, espacio + # -traducirlos a underscores- y números). Creo que un numero + # que se autoasigne es lo más cómodo. + def __repr__(self): return 'Entrega(instancia=%s, entregador=%s, codigo=%s, fecha=%s, ' \ - 'correcta=%s, observaciones=%s)' \ + 'correcta=%s, inicio_tareas=%s, fin_tareas=%s, observaciones=%s)' \ % (self.instancia.shortrepr(), srepr(self.entregador), - self.codigo, self.fecha, self.correcta, self.observaciones) + self.codigo, self.fecha, self.inicio_tareas, + self.fin_tareas, self.correcta, self.observaciones) def shortrepr(self): return '%s-%s-%s' % (self.instancia.shortrepr(), srepr(self.entregador), @@ -877,53 +838,82 @@ class Correccion(SQLObject): #{{{ return '%s,%s' % (self.entrega.shortrepr(), self.corrector.shortrepr()) #}}} -class TareaEjecutada(InheritableSQLObject): #{{{ - # Clave - tarea = ForeignKey('Tarea', notNone=True, cascade=False) - entrega = ForeignKey('Entrega', notNone=True, cascade=False) - pk = DatabaseIndex(tarea, entrega, unique=True) +class ComandoEjecutado(InheritableSQLObject): #{{{ # Campos inicio = DateTimeCol(notNone=True, default=DateTimeCol.now) fin = DateTimeCol(default=None) exito = IntCol(default=None) observaciones = UnicodeCol(default=None) - # Joins - pruebas = MultipleJoin('Prueba') - def add_prueba(self, caso_de_prueba, **kw): - return Prueba(tarea_ejecutada=self, caso_de_prueba=caso_de_prueba, - **kw) + def __repr__(self): + raise NotImplementedError('ComandoEjecutado es una clase abstracta') +#}}} + +class ComandoFuenteEjecutado(ComandoEjecutado): #{{{ + # Clave + comando = ForeignKey('ComandoFuente', notNone=True, cascade=False) + entrega = ForeignKey('Entrega', notNone=True, cascade=False) + pk = DatabaseIndex(comando, entrega, unique=True) def __repr__(self): - return 'TareaEjecutada(tarea=%s, entrega=%s, inicio=%s, fin=%s, ' \ - 'exito=%s, observaciones=%s)' \ - % (self.tarea.shortrepr(), self.entrega.shortrepr(), + return 'ComandoFuenteEjecutado(comando=%s, entrega=%s, inicio=%s, ' \ + 'fin=%s, exito=%s, observaciones=%s)' \ + % (self.comando.shortrepr(), self.entrega.shortrepr(), self.inicio, self.fin, self.exito, self.observaciones) def shortrepr(self): return '%s-%s' % (self.tarea.shortrepr(), self.entrega.shortrepr()) #}}} +class ComandoPruebaEjecutado(SQLObject): #{{{ + # Clave + comando = ForeignKey('ComandoPrueba', notNone=True, cascade=False) + prueba = ForeignKey('Prueba', notNone=True, cascade=False) + pk = DatabaseIndex(comando, prueba, unique=True) + + def __repr__(self): + return 'ComandoPruebaEjecutado(comando=%s, prueba=%s, inicio=%s, ' \ + 'fin=%s, exito=%s, observaciones=%s)' \ + % (self.comando.shortrepr(), self.prueba.shortrepr(), + self.inicio, self.fin, self.exito, self.observaciones) + + def shortrepr(self): + return '%s:%s:%s' % (self.tarea.shortrepr(), self.entrega.shortrerp(), + self.caso_de_prueba.shortrerp()) +#}}} + class Prueba(SQLObject): #{{{ # Clave - tarea_ejecutada = ForeignKey('TareaEjecutada', notNone=True, cascade=False) - caso_de_prueba = ForeignKey('CasoDePrueba', notNone=True, cascade=False) - pk = DatabaseIndex(tarea_ejecutada, caso_de_prueba, unique=True) + entrega = ForeignKey('Entrega', notNone=True, cascade=False) + caso_de_prueba = ForeignKey('CasoDePrueba', notNone=True, cascade=False) + pk = DatabaseIndex(entrega, caso_de_prueba, unique=True) # Campos - inicio = DateTimeCol(notNone=True, default=DateTimeCol.now) - fin = DateTimeCol(default=None) - pasada = IntCol(default=None) - observaciones = UnicodeCol(default=None) + inicio = DateTimeCol(notNone=True, default=DateTimeCol.now) + fin = DateTimeCol(default=None) + pasada = IntCol(default=None) + observaciones = UnicodeCol(default=None) + # Joins + comandos_ejecutados = MultipleJoin('ComandoPruebaEjecutado') + + def add_comando_ejecutado(self, comando, **kw): + if isinstance(comando, ComandoPrueba): + comando = comando.id + return ComandoPruebaEjecutado(prueba=self, comandoID=comando, **kw) + + def remove_comando_ejecutado(self, comando): + if isinstance(comando, ComandoPrueba): + comando = comando.id + # FIXME self.id, comando.id + ComandoPruebaEjecutado.pk.get(self.id, comando).destroySelf() def __repr__(self): - return 'Prueba(tarea_ejecutada=%s, caso_de_prueba=%s, inicio=%s, ' \ - 'fin=%s, pasada=%s, observaciones=%s)' \ - % (self.tarea_ejecutada.shortrepr(), - self.caso_de_prueba.shortrepr(), self.inicio, self.fin, - self.pasada, self.observaciones) + return 'Prueba(entrega=%s, caso_de_prueba=%s, inicio=%s, fin=%s, ' \ + 'pasada=%s, observaciones=%s)' \ + % (self.entrega.shortrepr(), self.caso_de_prueba.shortrepr(), + self.inicio, self.fin, self.pasada, self.observaciones) def shortrepr(self): - return '%s:%s' % (self.tarea_ejecutada.shortrepr(), + return '%s:%s' % (self.entrega.shortrepr(), self.caso_de_prueba.shortrerp()) #}}} -- 2.43.0 From fb3f7fce5b6f99ff075f4a7b085588b50e14b7e2 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 8 Mar 2007 14:54:24 +0000 Subject: [PATCH 03/16] =?utf8?q?Mejorar=20model.Grupo=20para=20manejo=20de?= =?utf8?q?=20miembros=20y=20tutores.=20Ahora=20los=20m=C3=A9todos=20remove?= =?utf8?q?=5Fmiembro()=20y=20remove=5Falumno()=20en=20vez=20de=20eliminar?= =?utf8?q?=20las=20clases=20de=20la=20DB=20les=20setea=20la=20fecha=20de?= =?utf8?q?=20baja.=20Tambi=C3=A9n=20se=20agregan=20las=20propiedades=20'al?= =?utf8?q?umnos'=20y=20'docentes'=20que=20devuelven=20una=20lista=20de=20A?= =?utf8?q?lumnoInscripto=20y=20DocenteInscripto=20respectivamente=20(no=20?= =?utf8?q?devuelve=20Miembro/Tutor!)=20con=20los=20objetos=20*activos*=20(?= =?utf8?q?es=20decir,=20baja=3DNone).?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- sercom/model.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/sercom/model.py b/sercom/model.py index 7d4ed29..dc1beed 100644 --- a/sercom/model.py +++ b/sercom/model.py @@ -629,6 +629,14 @@ class Grupo(Entregador): #{{{ for t in tutores: self.add_tutor(t) + _doc_alumnos = 'Devuelve una lista de AlumnoInscriptos **activos**.' + def _get_alumnos(self): + return list([m.alumno for m in Miembro.selectBy(grupo=self, baja=None)]) + + _doc_docentes = 'Devuelve una lista de DocenteInscriptos **activos**.' + def _get_docentes(self): + return list([t.docente for t in Tutor.selectBy(grupo=self, baja=None)]) + def add_miembro(self, alumno, **kw): if isinstance(alumno, AlumnoInscripto): alumno = alumno.id @@ -637,8 +645,8 @@ class Grupo(Entregador): #{{{ def remove_miembro(self, alumno): if isinstance(alumno, AlumnoInscripto): alumno = alumno.id - # FIXME self.id - Miembro.pk.get(self.id, alumno).destroySelf() + m = Miembro.selectBy(grupo=self, alumnoID=alumno, baja=None).getOne() + m.baja = DateTimeCol.now() def add_tutor(self, docente, **kw): if isinstance(docente, DocenteInscripto): @@ -648,8 +656,8 @@ class Grupo(Entregador): #{{{ def remove_tutor(self, docente): if isinstance(docente, DocenteInscripto): docente = docente.id - # FIXME self.id - Tutor.pk.get(self.id, docente).destroySelf() + t = Tutor.selectBy(grupo=self, alumnoID=alumno, baja=None) + t.baja = DateTimeCol.now() def __repr__(self): return 'Grupo(id=%s, nombre=%s, responsable=%s, nota=%s, ' \ -- 2.43.0 From 5d9bc331f6dd7b1e4a0566a1844ab4d863a4cd48 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 8 Mar 2007 15:02:53 +0000 Subject: [PATCH 04/16] Bugfix. Tutor no tiene alumno. --- sercom/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sercom/model.py b/sercom/model.py index dc1beed..24e811a 100644 --- a/sercom/model.py +++ b/sercom/model.py @@ -656,7 +656,7 @@ class Grupo(Entregador): #{{{ def remove_tutor(self, docente): if isinstance(docente, DocenteInscripto): docente = docente.id - t = Tutor.selectBy(grupo=self, alumnoID=alumno, baja=None) + t = Tutor.selectBy(grupo=self, docenteID=docente, baja=None) t.baja = DateTimeCol.now() def __repr__(self): -- 2.43.0 From c505a0a6a9fed168f9550405018372473912ea9e Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 8 Mar 2007 21:17:51 +0000 Subject: [PATCH 05/16] Actualizar TODO. --- TODO.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/TODO.txt b/TODO.txt index eb606aa..2132277 100644 --- a/TODO.txt +++ b/TODO.txt @@ -6,7 +6,7 @@ * AlumnoInscripto (Alumno) (nico) * Rol * Grupo - * Juntar / Separar + * Juntar / Separar - Hacer DB con datos de prueba (nico) - Hacer archivos de prueba para las cargas masivas (nico) - Hacer Informe bonito para pelu (un poco cada uno) @@ -14,6 +14,8 @@ * Sacar el campo "procesada?" de la creación (lo maneja internamente el backend, en modificar tal vez se podría poner pero mucho sentido no tiene tampoco, mejor que se vea nomás, pero que no se modifique). - * Poner el checkbox Activo? por default chequeado. + * Poner el checkbox Activo? por default chequeado. - Ejercicio: * Poner una columna en el listado con la cantidad de entregas. +- Entrega: + * Interfaz para que el alumno suba una entrega. -- 2.43.0 From 233c80ea16c56e21e093152166d7b075f2cfb808 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 8 Mar 2007 21:21:39 +0000 Subject: [PATCH 06/16] Bugfix. Corregir un par de MultipleJoin y herencia incorrecta. --- sercom/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sercom/model.py b/sercom/model.py index 24e811a..879c35c 100644 --- a/sercom/model.py +++ b/sercom/model.py @@ -334,7 +334,7 @@ class Tarea(InheritableSQLObject): #{{{ class TareaFuente(Tarea): #{{{ # Joins - comandos = MultipleJoin('ComandoFuente') + comandos = MultipleJoin('ComandoFuente', joinColumn='tarea_id') def add_comando(self, orden, comando, **kw): return ComandoFuente(tarea=self, orden=orden, comando=comando, **kw) @@ -349,7 +349,7 @@ class TareaFuente(Tarea): #{{{ class TareaPrueba(Tarea): #{{{ # Joins - comandos = MultipleJoin('ComandoPrueba') + comandos = MultipleJoin('ComandoPrueba', joinColumn='tarea_id') def add_comando(self, orden, comando, **kw): return ComandoPrueba(tarea=self, orden=orden, comando=comando, **kw) @@ -873,7 +873,7 @@ class ComandoFuenteEjecutado(ComandoEjecutado): #{{{ return '%s-%s' % (self.tarea.shortrepr(), self.entrega.shortrepr()) #}}} -class ComandoPruebaEjecutado(SQLObject): #{{{ +class ComandoPruebaEjecutado(ComandoEjecutado): #{{{ # Clave comando = ForeignKey('ComandoPrueba', notNone=True, cascade=False) prueba = ForeignKey('Prueba', notNone=True, cascade=False) -- 2.43.0 From ea4ace841b1a029dbfd26dbcbab41b89d393230c Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 8 Mar 2007 21:24:30 +0000 Subject: [PATCH 07/16] Arreglas trailing spaces y sacar errores por stderr. --- sercom/config/app.cfg | 4 ++-- sercom/config/log.cfg | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sercom/config/app.cfg b/sercom/config/app.cfg index f0030a6..999b9d3 100644 --- a/sercom/config/app.cfg +++ b/sercom/config/app.cfg @@ -107,14 +107,14 @@ identity.soprovider.model.visit = "sercom.model.VisitaUsuario" # The SqlObjectProvider *will* encrypt passwords supplied as part of your login # form. If you set the password through the password property, like: # my_user.password = 'secret' -# the password will be encrypted in the database, provided identity is up and +# the password will be encrypted in the database, provided identity is up and # running, or you have loaded the configuration specifying what encryption to # use (in situations where identity may not yet be running, like tests). identity.soprovider.encryption_algorithm = 'sha1' # compress the data sends to the web browser -[/] +[/] gzip_filter.on = True gzip_filter.mime_types = ["application/x-javascript", "text/javascript", "text/html", "text/css", "text/plain"] diff --git a/sercom/config/log.cfg b/sercom/config/log.cfg index ce776f8..817de78 100644 --- a/sercom/config/log.cfg +++ b/sercom/config/log.cfg @@ -26,4 +26,4 @@ formatter='message_only' [[[error_out]]] class='StreamHandler' level='ERROR' -args='(sys.stdout,)' +args='(sys.stderr,)' -- 2.43.0 From 46efc3a84d19545f396bac742bef4bff4b12fbac Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 8 Mar 2007 21:25:37 +0000 Subject: [PATCH 08/16] =?utf8?q?Cambiar=20modelo=20para=20que=20almacene?= =?utf8?q?=20archivos.=20Ahora=20las=20clases=20Entrega,=20ComandoFuente,?= =?utf8?q?=20ComandoPrueba=20y=20CasoDePrueba=20guardan=20los=20archivos?= =?utf8?q?=20como=20un=20stream=20de=20bytes=20en=20formato=20.zip.=20Adem?= =?utf8?q?=C3=A1s=20se=20vuela=20el=20c=C3=B3digo=20de=20Entrega=20que=20n?= =?utf8?q?o=20ten=C3=ADa=20mucho=20m=C3=A1s=20sentido=20y=20se=20pone=20co?= =?utf8?q?mo=20notNone=3DTrue=20y=20default=3D''=20a=20algunas=20observaci?= =?utf8?q?ones=20donde=20tiene=20m=C3=A1s=20sentido=20para=20concatenar.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- doc/entrega.zip | Bin 0 -> 219 bytes doc/testdata.py | 15 +++++++--- sercom/model.py | 75 ++++++++++++++---------------------------------- 3 files changed, 33 insertions(+), 57 deletions(-) create mode 100644 doc/entrega.zip diff --git a/doc/entrega.zip b/doc/entrega.zip new file mode 100644 index 0000000000000000000000000000000000000000..615aacc2be3f451908617c5e54f490f8990698ba GIT binary patch literal 219 zcmWIWW@h1H0D;V|3^RercfGuUY!GH+5M?OIEXmhP4h`XCU_P4v!4-r Date: Thu, 8 Mar 2007 21:29:26 +0000 Subject: [PATCH 09/16] Agregar primer boceto del probador de entregas. --- sercom/tester.py | 263 +++++++++++++++++++++++++++++++++++++++++++++++ testtester.py | 28 +++++ 2 files changed, 291 insertions(+) create mode 100644 sercom/tester.py create mode 100644 testtester.py diff --git a/sercom/tester.py b/sercom/tester.py new file mode 100644 index 0000000..7638fc8 --- /dev/null +++ b/sercom/tester.py @@ -0,0 +1,263 @@ +# vim: set et sw=4 sts=4 encoding=utf-8 foldmethod=marker: + +from sercom import model as mod +import zipfile as zf +import cStringIO as sio +import shutil as shu +import datetime as dt +from os import path as osp +import os + +import logging +log = logging.getLogger('sercom.tester') + +#from Queue import Queue +#queue = Queue() + +class Error(StandardError): pass + +class ExecutionFailure(Error, RuntimeError): pass + +class RsyncError(Error, EnvironmentError): pass + +error_interno = u'\n**Error interno al preparar la entrega.**' + +def unzip(bytes, dst): # {{{ + log.debug(_(u'Intentando descomprimir en %s') % dst) + if bytes is None: + return + zfile = zf.ZipFile(sio.StringIO(bytes), 'r') + for f in zfile.namelist(): + if f.endswith(os.sep): + log.debug(_(u'Creando directorio %s') % f) + os.mkdir(osp.join(dst, f)) + else: + log.debug(_(u'Descomprimiendo archivo %s') % f) + file(osp.join(dst, f), 'w').write(zfile.read(f)) +#}}} + +class Tester(object): #{{{ + + def __init__(self, name, path, home, queue): #{{{ y properties + self.name = name + self.path = path + self.home = home + self.queue = queue + + @property + def build_path(self): + return osp.join(self.chroot, self.home, 'build') + + @property + def test_path(self): + return osp.join(self.chroot, self.home, 'test') + + @property + def chroot(self): + return osp.join(self.path, 'chroot_' + self.name) + #}}} + + @property + def orig_chroot(self): + return osp.join(self.path, 'chroot') + + def run(self): #{{{ + entrega_id = self.queue.get() # blocking + while entrega_id is not None: + entrega = mod.Entrega.get(entrega_id) + log.debug(_(u'Nueva entrega para probar en tester %s: %s') + % (self.name, entrega)) + self.test(entrega) + log.debug(_(u'Fin de pruebas de: %s') % entrega) + entrega_id = self.queue.get() # blocking + #}}} + + def test(self, entrega): #{{{ + log.debug(_(u'Tester.test(entrega=%s)') % entrega) + entrega.inicio_tareas = dt.datetime.now() + try: + try: + self.setup_chroot(entrega) + self.ejecutar_tareas_fuente(entrega) + self.ejecutar_tareas_prueba(entrega) + self.clean_chroot(entrega) + except ExecutionFailure, e: + entrega.correcta = False + log.debug(_(u'Entrega incorrecta: %s') % entrega) + except Exception, e: + entrega.observaciones += error_interno + log.exception(_(u'Hubo una excepción inesperada: %s') % e) + except: + entrega.observaciones += error_interno + log.exception(_(u'Hubo una excepción inesperada desconocida')) + else: + entrega.correcta = True + log.debug(_(u'Entrega correcta: %s') % entrega) + finally: + entrega.fin_tareas = dt.datetime.now() + #}}} + + def setup_chroot(self, entrega): #{{{ y clean_chroot() + log.debug(_(u'Tester.setup_chroot(entrega=%s)') % entrega) + rsync = 'rsync --stats --itemize-changes --human-readable --archive ' \ + '--acls --delete-during --force' # TODO config + orig_chroot = osp.join(self.orig_chroot, '') + cmd = '%s %s %s' % (rsync, orig_chroot, self.chroot) + log.debug(_(u'Ejecutando: %s') % cmd) + ret = os.system(cmd) + if ret != 0: + entrega.observaciones += error_interno + errstr = _(u'No se pudo hacer rsync al chroot para la prueba,' \ + u'falló el comando: %s (con código de error %d)') % (cmd, ret) + log.error(errstr) + raise RsyncError(errstr) + try: + unzip(entrega.archivos, self.build_path) + except zf.BadZipfile: + entrega.correcta = False + entrega.observaciones += error_interno + log.error(_(u'El archivo adjunto no está en formato ZIP')) + raise + except IOError, e: + entrega.observaciones += error_interno + log.error(_(u'Error de IO al descromprimir archivos del ZIP: %s') + % e) + raise + + def clean_chroot(self, entrega): + log.debug(_(u'Tester.clean_chroot(entrega=%s)') % entrega) + pass # Se limpia con el próximo rsync + #}}} + + def ejecutar_tareas_fuente(self, entrega): #{{{ y tareas_prueba + log.debug(_(u'Tester.ejecutar_tareas_fuente(entrega=%s)') % entrega) + tareas = [t for t in entrega.instancia.ejercicio.enunciado.tareas + if isinstance(t, mod.TareaFuente)] + for tarea in tareas: + tarea.ejecutar(self.build_path, entrega) + + def ejecutar_tareas_prueba(self, entrega): + log.debug(_(u'Tester.ejecutar_tareas_prueba(entrega=%s)') % entrega) + for caso in entrega.instancia.ejercicio.enunciado.casos_de_prueba: + caso.ejecutar(self.test_path, entrega) + #}}} + +#}}} + +def ejecutar_caso_de_prueba(self, path, entrega): #{{{ + log.debug(_(u'CasoDePrueba.ejecutar(path=%s, entrega=%s)') + % (path, entrega)) + tareas = [t for t in entrega.instancia.ejercicio.enunciado.tareas + if isinstance(t, mod.TareaPrueba)] + prueba = entrega.add_prueba(self) + try: + try: + for tarea in tareas: + tarea.ejecutar(path, prueba) + except ExecutionFailure, e: + prueba.pasada = False + if self.rechazar_si_falla: + entrega.exito = False + if self.terminar_si_falla: + raise ExecutionError(e.comando, e.tarea, prueba) + else: + prueba.pasada = True + finally: + prueba.fin = dt.datetime.now() +mod.CasoDePrueba.ejecutar = ejecutar_caso_de_prueba +#}}} + +def ejecutar_tarea_fuente(self, path, entrega): #{{{ + log.debug(_(u'TareaFuente.ejecutar(path=%s, entrega=%s)') % (path, entrega)) + try: + for cmd in self.comandos: + cmd.ejecutar(path, entrega) + except ExecutionFailure, e: + if self.rechazar_si_falla: + entrega.exito = False + if self.terminar_si_falla: + raise ExecutionError(e.comando, tarea) +mod.TareaFuente.ejecutar = ejecutar_tarea_fuente +#}}} + +def ejecutar_tarea_prueba(self, path, prueba): #{{{ + log.debug(_(u'TareaPrueba.ejecutar(path=%s, prueba=%s)') % (path, prueba)) + try: + for cmd in self.comandos: + cmd.ejecutar(path, prueba) + except ExecutionFailure, e: + if self.rechazar_si_falla: + prueba.exito = False + if self.terminar_si_falla: + raise ExecutionError(e.comando, tarea) +mod.TareaPrueba.ejecutar = ejecutar_tarea_prueba +#}}} + +def ejecutar_comando_fuente(self, path, entrega): #{{{ + log.debug(_(u'ComandoFuente.ejecutar(path=%s, entrega=%s)') + % (path, entrega)) + unzip(self.archivos_entrada, path) # TODO try/except + comando_ejecutado = entrega.add_comando_ejecutado(self) + # TODO ejecutar en chroot (path) + comando_ejecutado.fin = dt.datetime.now() +# if no_anda_ejecucion: # TODO +# comando_ejecutado.exito = False +# comando_ejecutado.observaciones += 'No anduvo xxx' # TODO mas info +# if self.rechazar_si_falla: +# entrega.exito = False +# if self.terminar_si_falla: # TODO +# raise ExecutionFailure(self) + # XXX ESTO EN REALIDAD EN COMANDOS FUENTE NO IRIA + # XXX SOLO HABRÍA QUE CAPTURAR stdout/stderr + # XXX PODRIA TENER ARCHIVOS DE SALIDA PERO SOLO PARA MOSTRAR COMO RESULTADO +# for archivo in self.archivos_salida: +# pass # TODO hacer diff +# if archivos_mal: # TODO +# comando_ejecutado.exito = False +# comando_ejecutado.observaciones += 'No anduvo xxx' # TODO mas info +# if self.rechazar_si_falla: +# entrega.exito = False +# if self.terminar_si_falla: # TODO +# raise ExecutionFailure(self) +# else: +# comando_ejecutado.exito = True +# comando_ejecutado.observaciones += 'xxx OK' # TODO + comando_ejecutado.exito = True + comando_ejecutado.observaciones += 'xxx OK' # TODO +mod.ComandoFuente.ejecutar = ejecutar_comando_fuente +#}}} + +def ejecutar_comando_prueba(self, path, prueba): #{{{ + log.debug(_(u'ComandoPrueba.ejecutar(path=%s, prueba=%s)') + % (path, prueba)) + shu.rmtree(path) + os.mkdir(path) + unzip(prueba.caso_de_prueba.archivos_entrada, path) # TODO try/except + unzip(self.archivos_entrada, path) # TODO try/except + comando_ejecutado = prueba.add_comando_ejecutado(self) + # TODO ejecutar en chroot (path) + comando_ejecutado.fin = dt.datetime.now() +# if no_anda_ejecucion: # TODO +# comando_ejecutado.exito = False +# comando_ejecutado.observaciones += 'No anduvo xxx' # TODO +# if self.rechazar_si_falla: +# entrega.exito = False +# if self.terminar_si_falla: # TODO +# raise ExecutionFailure(self) # TODO info de error +# for archivo in self.archivos_salida: +# pass # TODO hacer diff +# if archivos_mal: # TODO +# comando_ejecutado.exito = False +# comando_ejecutado.observaciones += 'No anduvo xxx' # TODO +# if self.rechazar_si_falla: +# entrega.exito = False +# if self.terminar_si_falla: # TODO +# raise ExecutionFailure(comando=self) # TODO info de error +# else: +# comando_ejecutado.exito = True +# comando_ejecutado.observaciones += 'xxx OK' # TODO + comando_ejecutado.exito = True + comando_ejecutado.observaciones += 'xxx OK' # TODO +mod.ComandoPrueba.ejecutar = ejecutar_comando_prueba +#}}} + diff --git a/testtester.py b/testtester.py new file mode 100644 index 0000000..4e7f951 --- /dev/null +++ b/testtester.py @@ -0,0 +1,28 @@ +#!/usr/bin/python +# vim: set et sw=4 sts=4 encoding=utf-8 foldmethod=marker : + +import turbogears +import turbogears.database +turbogears.update_config(configfile="dev.cfg", modulename="sercom.config") +from sercom.tester import * +from sercom import model +from Queue import Queue + +queue = Queue() + +queue.put(1) +#queue.put(5) +queue.put(None) + +tester = Tester(name='pepe', path='var', home=os.path.join('home', 'sercom'), + queue=queue) + +tester.run() + +model.hub.rollback() +#model.hub.commit() + +#model.hub.begin() +#model.Entrega.get(5).correcta = True +#model.hub.rollback() + -- 2.43.0 From 093cc6f1322e48e79088a864addd7fff4c34c252 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Fri, 9 Mar 2007 03:05:16 +0000 Subject: [PATCH 10/16] Nuevo Widget con 2 listas para pasar datos de una a otra. Esta lista tiene el objetivo de pasar cosas de FROM a TO dando un layout horizontal que queda mas piolas ;). Crea 2 listas ${name}_from y {$name}_to que son pasadas al controller (previamente hay que seleccionar desde JS todos los items de las listas que se deseen leer, puede ser una sola o ambas). Como propiedades utiles tiene : * title_from : El titulo que va arriba de la lista FROM (la de la derecha) * title_to : El titulo que va arriba de la lista FROM (la de la izquierda) * El resto igual que MultiSelectField No puse el texto de los botones configurable porque se rompe muy facil el layour :S, pero creo que con los titulitos se da a entender. Desde JS se accede con form_${name}_from y form_${name}_to a FROM y TO respectivamente. Para inicializar los valores de FROM se usa directamente el atributo options de MultipleSelectField y a TO se lo debe inicializar desde JS. --- sercom/subcontrollers/curso/__init__.py | 46 ++++++------ .../subcontrollers/curso/templates/edit.kid | 26 +------ sercom/subcontrollers/curso/templates/new.kid | 24 ------ sercom/widgets.py | 74 +++++++++++++++++++ 4 files changed, 100 insertions(+), 70 deletions(-) diff --git a/sercom/subcontrollers/curso/__init__.py b/sercom/subcontrollers/curso/__init__.py index 9d1c1f3..7b5fccd 100644 --- a/sercom/subcontrollers/curso/__init__.py +++ b/sercom/subcontrollers/curso/__init__.py @@ -68,7 +68,7 @@ ajax = u""" d.addCallbacks(procesar, err); } - function onsubmit() + function doSubmit() { /* TODO : Validar datos y evitar el submit si no esta completo */ @@ -78,7 +78,7 @@ ajax = u""" l.options[i].selected = true; } /* Selecciono todos los miembros si no, no llegan al controllere*/ - l = MochiKit.DOM.getElement('form_docentes_curso'); + l = MochiKit.DOM.getElement('form_docentes_to'); for (i=0; i - MochiKit.DOM.appendChildNodes("form_docentes_curso", OPTION({"value":${d['id']}}, '${d['label']}')) + + MochiKit.DOM.appendChildNodes("form_docentes_to", OPTION({"value":${d['id']}}, '${d['label']}')) @@ -18,28 +18,6 @@ } MochiKit.DOM.addLoadEvent(init_data) - - function makeOption(option) { - return OPTION({"value": option.value}, option.text); - } - - function moveOption( fromSelect, toSelect) { - // add 'selected' nodes toSelect - appendChildNodes(toSelect, - map( makeOption,ifilter(itemgetter('selected'), $(fromSelect).options))); - // remove the 'selected' fromSelect - // replaceChildNodes(fromSelect, - // list(ifilterfalse(itemgetter('selected'), $(fromSelect).options))); - } - - function mover( src, dest ) { - moveOption(src, dest) - } - - function remover (src, dest) { - replaceChildNodes(src,list(ifilterfalse(itemgetter('selected'), $(src).options))) - } - diff --git a/sercom/subcontrollers/curso/templates/new.kid b/sercom/subcontrollers/curso/templates/new.kid index e50ec76..1724584 100644 --- a/sercom/subcontrollers/curso/templates/new.kid +++ b/sercom/subcontrollers/curso/templates/new.kid @@ -2,30 +2,6 @@ - - new diff --git a/sercom/widgets.py b/sercom/widgets.py index 612d950..b9a14c1 100644 --- a/sercom/widgets.py +++ b/sercom/widgets.py @@ -108,3 +108,77 @@ class AjaxMultiSelect(widgets.MultipleSelectField): self.params.append('on_add') self.on_add = "alert('Not defined action');" widgets.MultipleSelectField.__init__(self, **kw) + +DosListasAjax = ''' + function makeOption(option) { + return OPTION({"value": option.value}, option.text); + } + + function moveOption( fromSelect, toSelect) { + // add 'selected' nodes toSelect + appendChildNodes(toSelect, + map( makeOption,ifilter(itemgetter('selected'), $(fromSelect).options))); + // remove the 'selected' fromSelect + replaceChildNodes(fromSelect, + list(ifilterfalse(itemgetter('selected'), $(fromSelect).options)) + ); + } + +''' + +class AjaxDosListasSelect(widgets.MultipleSelectField): + template = ''' +
+ + + + + + + + + + + +
${title_from} ${title_to}
+ + + +
+
+ +
+ +
+
+ ''' + javascript = [widgets.JSSource(DosListasAjax)] + title_from = "" + title_to = "" + + def __init__(self, **kw): + self.params.append('title_from') + self.params.append('title_to') + self.title_from = " " + self.title_to = " " + widgets.MultipleSelectField.__init__(self, **kw) + -- 2.43.0 From 1951014908ab4002a9a52931860a8a124f49655f Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Fri, 9 Mar 2007 03:29:25 +0000 Subject: [PATCH 11/16] Hago que se borren los elementos de FROM que ya estan en TO al editar. Esto para los docentes de un curso, para que no aparezcan como disponibles aquellos ya asignados. No veo forma de generalizarlo por lo que sera C&P :S --- sercom/subcontrollers/curso/templates/edit.kid | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/sercom/subcontrollers/curso/templates/edit.kid b/sercom/subcontrollers/curso/templates/edit.kid index 7cbef82..feda8f5 100644 --- a/sercom/subcontrollers/curso/templates/edit.kid +++ b/sercom/subcontrollers/curso/templates/edit.kid @@ -16,7 +16,18 @@ MochiKit.DOM.appendChildNodes("form_alumnos", OPTION({"value":${a['id']}}, '${a['label']}')) - } + // Saco de FROM los que ya estan en TO + replaceChildNodes('form_docentes_from', list(ifilterfalse( + partial(esta_en_to, $('form_docentes_to').options), + $('form_docentes_from').options + ))); + } + function esta_en_to (options, i) { + for (j=0; j < options.length; j++) + if (options[j].value == i.value) + return true; + return false; + } MochiKit.DOM.addLoadEvent(init_data) -- 2.43.0 From f7393e0e6363d1c56d579a9fe86c99f9a45bb4bf Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Fri, 9 Mar 2007 03:51:07 +0000 Subject: [PATCH 12/16] Bugfix: Comando debe ser Inheritable. --- sercom/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sercom/model.py b/sercom/model.py index 0ccb1a2..688fbb0 100644 --- a/sercom/model.py +++ b/sercom/model.py @@ -362,7 +362,7 @@ class TareaPrueba(Tarea): #{{{ % (self.id, self.nombre, self.descripcion) #}}} -class Comando(SQLObject): #{{{ +class Comando(InheritableSQLObject): #{{{ # Campos comando = ParamsCol(length=255, notNone=True) descripcion = UnicodeCol(length=255, default=None) -- 2.43.0 From 4e0c67cab6a67e4264be594342286ec8f2a5f838 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Fri, 9 Mar 2007 17:57:43 +0000 Subject: [PATCH 13/16] Bugfix: arreglar validadores personalizados de SQLObject. --- sercom/model.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sercom/model.py b/sercom/model.py index 688fbb0..4efdc63 100644 --- a/sercom/model.py +++ b/sercom/model.py @@ -41,8 +41,7 @@ class TupleValidator(PickleValidator): class SOTupleCol(SOPickleCol): def createValidators(self): - return [TupleValidator(name=self.name)] \ - + super(SOPickleCol, self).createValidators() + return [TupleValidator(name=self.name)] class TupleCol(PickleCol): baseClass = SOTupleCol @@ -79,8 +78,7 @@ class ParamsValidator(UnicodeStringValidator): class SOParamsCol(SOUnicodeCol): def createValidators(self): - return [ParamsValidator(db_encoding=self.dbEncoding, name=self.name)] \ - + super(SOParamsCol, self).createValidators() + return [ParamsValidator(db_encoding=self.dbEncoding, name=self.name)] class ParamsCol(UnicodeCol): baseClass = SOParamsCol -- 2.43.0 From f5a725465226d47f914425b2c1a814c4c27ee9a3 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Fri, 9 Mar 2007 17:58:28 +0000 Subject: [PATCH 14/16] Bugfix: ComandoPrueba tiene multipl_tiempo_cpu en vez de tiempo_cpu. --- sercom/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sercom/model.py b/sercom/model.py index 4efdc63..4f349ce 100644 --- a/sercom/model.py +++ b/sercom/model.py @@ -410,10 +410,10 @@ class ComandoPrueba(Comando): #{{{ def __repr__(self): return 'ComandoPrueba(tarea=%s, orden=%s, comando=%s, descripcion=%s, ' \ - 'retorno=%s, tiempo_cpu=%s, terminar_si_falla=%s, ' \ + 'retorno=%s, multipl_tiempo_cpu=%s, terminar_si_falla=%s, ' \ 'rechazar_si_falla=%s)' \ % (srepr(self.tarea), self.orden, self.comando, self.descripcion, - self.retorno, self.tiempo_cpu, self.terminar_si_falla, + self.retorno, self.multipl_tiempo_cpu, self.terminar_si_falla, self.rechazar_si_falla) #}}} -- 2.43.0 From d76442c99e5d7de7265d7b27a169a1fec9d2be00 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Fri, 9 Mar 2007 18:54:15 +0000 Subject: [PATCH 15/16] Bugfix: typo en shortrepr(). --- sercom/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sercom/model.py b/sercom/model.py index 4f349ce..0ac30a2 100644 --- a/sercom/model.py +++ b/sercom/model.py @@ -853,8 +853,8 @@ class ComandoPruebaEjecutado(ComandoEjecutado): #{{{ self.inicio, self.fin, self.exito, self.observaciones) def shortrepr(self): - return '%s:%s:%s' % (self.tarea.shortrepr(), self.entrega.shortrerp(), - self.caso_de_prueba.shortrerp()) + return '%s:%s:%s' % (self.tarea.shortrepr(), self.entrega.shortrepr(), + self.caso_de_prueba.shortrepr()) #}}} class Prueba(SQLObject): #{{{ @@ -889,7 +889,7 @@ class Prueba(SQLObject): #{{{ def shortrepr(self): return '%s:%s' % (self.entrega.shortrepr(), - self.caso_de_prueba.shortrerp()) + self.caso_de_prueba.shortrepr()) #}}} #{{{ Específico de Identity -- 2.43.0 From e3d9da6e7709e72c31a7b0a4d4209b0e7884344a Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Sat, 10 Mar 2007 05:43:34 +0000 Subject: [PATCH 16/16] =?utf8?q?Agregar=20m=C3=A1s=20campos=20de=20l=C3=AD?= =?utf8?q?mites=20a=20Comando.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- doc/testdata.py | 7 +++--- sercom/model.py | 59 +++++++++++++++++++++++++++++-------------------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/doc/testdata.py b/doc/testdata.py index 7b4b0e4..7485f59 100644 --- a/doc/testdata.py +++ b/doc/testdata.py @@ -14,9 +14,10 @@ a = Alumno(padron='77891', nombre='Tito Puente', password='77891', roles=[r2]) # Tareas y comandos tf = TareaFuente(nombre='Compilar C con Makefile', terminar_si_falla=True, rechazar_si_falla=True) -cf = tf.add_comando(1, 'make -f Makefile', retorno=0, terminar_si_falla=True, - rechazar_si_falla=True, - descripcion='Compila usando un Makefile predeterminado') +cf = tf.add_comando(1, 'make tito', retorno=0, max_cant_archivos=15, + max_cant_procesos=100, terminar_si_falla=True, rechazar_si_falla=True, + descripcion='Compila un programa en C con make ' \ + 'sin usar un Makefile (debe ser un solo archivo que se llame tito.c)') tp = TareaPrueba(nombre='Probar', terminar_si_falla=True, rechazar_si_falla=True) cp = tp.add_comando(1, [], retorno=0, terminar_si_falla=True, diff --git a/sercom/model.py b/sercom/model.py index 0ac30a2..a65b03a 100644 --- a/sercom/model.py +++ b/sercom/model.py @@ -361,22 +361,40 @@ class TareaPrueba(Tarea): #{{{ #}}} class Comando(InheritableSQLObject): #{{{ + RET_ANY = None + RET_FAIL = -1 # Campos comando = ParamsCol(length=255, notNone=True) descripcion = UnicodeCol(length=255, default=None) - retorno = IntCol(default=None) + retorno = IntCol(default=None) # None es que no importa + max_tiempo_cpu = IntCol(default=None) # En segundos + max_memoria = IntCol(default=None) # En MB + max_tam_archivo = IntCol(default=None) # En MB + max_cant_archivos = IntCol(default=None) + max_cant_procesos = IntCol(default=None) + max_locks_memoria = IntCol(default=None) terminar_si_falla = BoolCol(notNone=True, default=True) rechazar_si_falla = BoolCol(notNone=True, default=True) archivos_entrada = BLOBCol(default=None) # ZIP con archivos de entrada # stdin es caso especial archivos_salida = BLOBCol(default=None) # ZIP con archivos de salida # stdout y stderr son especiales + activo = BoolCol(notNone=True, default=True) - def __repr__(self): - raise NotImplementedError('Comando es una clase abstracta') + def __repr__(self, clave='', mas=''): + return ('%s(%s comando=%s, descripcion=%s, retorno=%s, ' + 'max_tiempo_cpu=%s, max_memoria=%s, max_tam_archivo=%s, ' + 'max_cant_archivos=%s, max_cant_procesos=%s, max_locks_memoria=%s, ' + 'terminar_si_falla=%s, rechazar_si_falla=%s%s)' + % (self.__class__.__name__, clave, self.comando, + self.descripcion, self.retorno, self.max_tiempo_cpu, + self.max_memoria, self.max_tam_archivo, + self.max_cant_archivos, self.max_cant_procesos, + self.max_locks_memoria, self.terminar_si_falla, + self.rechazar_si_falla)) def shortrepr(self): - return self.nombre + return '%s (%s)' % (self.comando, self.descripcion) #}}} class ComandoFuente(Comando): #{{{ @@ -384,37 +402,30 @@ class ComandoFuente(Comando): #{{{ tarea = ForeignKey('TareaFuente', notNone=True, cascade=True) orden = IntCol(notNone=True) pk = DatabaseIndex(tarea, orden, unique=True) - # Campos - tiempo_cpu = FloatCol(default=None) - - def ejecutar(self): pass # TODO def __repr__(self): - return 'ComandoFuente(tarea=%s, orden=%s, comando=%s, descripcion=%s, ' \ - 'retorno=%s, tiempo_cpu=%s, terminar_si_falla=%s, ' \ - 'rechazar_si_falla=%s)' \ - % (srepr(self.tarea), self.orden, self.comando, self.descripcion, - self.retorno, self.tiempo_cpu, self.terminar_si_falla, - self.rechazar_si_falla) + return super(ComandoFuente, self).__repr__('tarea=%s, orden=%s' + % (self.tarea.shortrepr(), self.orden)) + + def shortrepr(self): + return '%s:%s (%s)' % (self.tarea.shortrepr(), self.orden, self.comando) #}}} class ComandoPrueba(Comando): #{{{ + RET_PRUEBA = -2 # Espera el mismo retorno que el de la prueba. + # XXX todos los campos de limitación en este caso son multiplicadores para + # los valores del caso de prueba. # Clave tarea = ForeignKey('TareaPrueba', notNone=True, cascade=True) orden = IntCol(notNone=True) pk = DatabaseIndex(tarea, orden, unique=True) - # Campos - multipl_tiempo_cpu = FloatCol(notNone=True, default=1.0) - - def ejecutar(self): pass # TODO def __repr__(self): - return 'ComandoPrueba(tarea=%s, orden=%s, comando=%s, descripcion=%s, ' \ - 'retorno=%s, multipl_tiempo_cpu=%s, terminar_si_falla=%s, ' \ - 'rechazar_si_falla=%s)' \ - % (srepr(self.tarea), self.orden, self.comando, self.descripcion, - self.retorno, self.multipl_tiempo_cpu, self.terminar_si_falla, - self.rechazar_si_falla) + return super(ComandoFuente, self).__repr__('tarea=%s, orden=%s' + % (self.tarea.shortrepr(), self.orden)) + + def shortrepr(self): + return '%s:%s (%s)' % (self.tarea.shortrepr(), self.orden, self.comando) #}}} class Enunciado(SQLObject): #{{{ -- 2.43.0