]> git.llucax.com Git - z.facultad/75.52/sercom.git/blob - sercom/model.py
Cambio cosmético de nombre de campo de la DB.
[z.facultad/75.52/sercom.git] / sercom / model.py
1 # vim: set et sw=4 sts=4 encoding=utf-8 :
2
3 from datetime import datetime
4 from turbogears.database import PackageHub
5 from sqlobject import *
6 from sqlobject.sqlbuilder import *
7 from sqlobject.inheritance import InheritableSQLObject
8 from sqlobject.col import PickleValidator
9 from turbogears import identity
10
11 hub = PackageHub("sercom")
12 __connection__ = hub
13
14 __all__ = ('Curso', 'Usuario', 'Docente', 'Alumno', 'Tarea', 'CasoDePrueba')
15
16 #{{{ Custom Columns
17
18 class TupleValidator(PickleValidator):
19     """
20     Validator for tuple types.  A tuple type is simply a pickle type
21     that validates that the represented type is a tuple.
22     """
23
24     def to_python(self, value, state):
25         value = super(TupleValidator, self).to_python(value, state)
26         if value is None:
27             return None
28         if isinstance(value, tuple):
29             return value
30         raise validators.Invalid("expected a tuple in the TupleCol '%s', got %s %r instead" % \
31             (self.name, type(value), value), value, state)
32
33     def from_python(self, value, state):
34         if value is None:
35             return None
36         if not isinstance(value, tuple):
37             raise validators.Invalid("expected a tuple in the TupleCol '%s', got %s %r instead" % \
38                 (self.name, type(value), value), value, state)
39         return super(TupleValidator, self).from_python(value, state)
40
41 class SOTupleCol(SOPickleCol):
42
43     def __init__(self, **kw):
44         super(SOTupleCol, self).__init__(**kw)
45
46     def createValidators(self):
47         return [TupleValidator(name=self.name)] + \
48             super(SOPickleCol, self).createValidators()
49
50 class TupleCol(PickleCol):
51     baseClass = SOTupleCol
52
53 #}}}
54
55 #{{{ Tablas intermedias
56
57
58 # BUG en SQLObject, SQLExpression no tiene cálculo de hash pero se usa como
59 # key de un dict. Workarround hasta que lo arreglen.
60 SQLExpression.__hash__ = lambda self: hash(str(self))
61
62 instancia_tarea_t = table.instancia_tarea
63
64 dependencia_t = table.dependencia
65
66 #}}}
67
68 #{{{ Clases
69
70 def srepr(obj): #{{{
71     if obj is not None:
72         return obj.shortrepr()
73     return obj
74 #}}}
75
76 class ByObject(object): #{{{
77     @classmethod
78     def by(cls, **kw):
79         try:
80             return cls.selectBy(**kw)[0]
81         except IndexError:
82             raise SQLObjectNotFound, "The object %s with columns %s does not exist" % (cls.__name__, kw)
83 #}}}
84
85 class Curso(SQLObject, ByObject): #{{{
86     # Clave
87     anio            = IntCol(notNone=True)
88     cuatrimestre    = IntCol(notNone=True)
89     numero          = IntCol(notNone=True)
90     pk              = DatabaseIndex(anio, cuatrimestre, numero, unique=True)
91     # Campos
92     descripcion     = UnicodeCol(length=255, default=None)
93     # Joins
94     docentes        = MultipleJoin('DocenteInscripto')
95     alumnos         = MultipleJoin('AlumnoInscripto')
96     grupos          = MultipleJoin('Grupo')
97     ejercicios      = MultipleJoin('Ejercicio', orderBy='numero')
98
99     def add_docente(self, docente, **opts):
100         return DocenteInscripto(cursoID=self.id, docenteID=docente.id, **opts)
101
102     def add_alumno(self, alumno, tutor=None, **opts):
103         tutor_id = tutor and tutor.id
104         return AlumnoInscripto(cursoID=self.id, alumnoID=alumno.id,
105             tutorID=tutor_id, **opts)
106
107     def add_grupo(self, nombre, responsable=None, **opts):
108         resp_id = responsable and responsable.id
109         return Grupo(cursoID=self.id, nombre=unicode(nombre),
110             responsableID=resp_id, **opts)
111
112     def add_ejercicio(self, numero, enunciado, **opts):
113         return Ejercicio(cursoID=self.id, numero=numero,
114             enunciadoID=enunciado.id, **opts)
115
116     def __repr__(self):
117         return 'Curso(id=%s, anio=%s, cuatrimestre=%s, numero=%s, ' \
118             'descripcion=%s)' \
119                 % (self.id, self.anio, self.cuatrimestre, self.numero,
120                     self.descripcion)
121
122     def shortrepr(self):
123         return '%s.%s.%s' \
124             % (self.anio, self.cuatrimestre, self.numero, self.descripcion)
125 #}}}
126
127 class Usuario(InheritableSQLObject, ByObject): #{{{
128     # Clave (para docentes puede ser un nombre de usuario arbitrario)
129     usuario         = UnicodeCol(length=10, alternateID=True)
130     # Campos
131     contrasenia     = UnicodeCol(length=255, default=None)
132     nombre          = UnicodeCol(length=255, notNone=True)
133     email           = UnicodeCol(length=255, default=None)
134     telefono        = UnicodeCol(length=255, default=None)
135     creado          = DateTimeCol(notNone=True, default=DateTimeCol.now)
136     observaciones   = UnicodeCol(default=None)
137     activo          = BoolCol(notNone=True, default=True)
138     # Joins
139     grupos          = RelatedJoin('Grupo')
140     roles           = RelatedJoin('Rol')
141
142     def _get_user_name(self): # para identity
143         return self.usuario
144
145     @classmethod
146     def by_user_name(cls, user_name): # para identity
147         user = cls.byUsuario(user_name)
148         if not user.activo:
149             raise SQLObjectNotFound, "The object %s with user_name %s is " \
150                 "not active" % (cls.__name__, user_name)
151         return user
152
153     def _get_groups(self): # para identity
154         return self.roles
155
156     def _get_permissions(self): # para identity
157         perms = set()
158         for g in self.groups:
159             perms.update(g.permisos)
160         return perms
161
162     def _set_password(self, cleartext_password): # para identity
163         self.contrasenia = identity.encrypt_password(cleartext_password)
164
165     def _get_password(self): # para identity
166         return self.contrasenia
167
168     def __repr__(self):
169         raise NotImplementedError, 'Clase abstracta!'
170
171     def shortrepr(self):
172         return '%s (%s)' % (self.usuario, self.nombre)
173 #}}}
174
175 class Docente(Usuario): #{{{
176     _inheritable = False
177     # Campos
178     nombrado        = BoolCol(notNone=True, default=True)
179     # Joins
180     enunciados      = MultipleJoin('Enunciado', joinColumn='autor_id')
181     inscripciones   = MultipleJoin('DocenteInscripto')
182
183     def add_entrega(self, instancia, **opts):
184         return Entrega(instanciaID=instancia.id, **opts)
185
186     def add_enunciado(self, nombre, **opts):
187         return Enunciado(autorID=self.id, nombre=nombre, **opts)
188
189     def __repr__(self):
190         return 'Docente(id=%s, usuario=%s, nombre=%s, password=%s, email=%s, ' \
191             'telefono=%s, activo=%s, creado=%s, observaciones=%s)' \
192                 % (self.id, self.usuario, self.nombre, self.password,
193                     self.email, self.telefono, self.activo, self.creado,
194                     self.observaciones)
195 #}}}
196
197 class Alumno(Usuario): #{{{
198     _inheritable = False
199     # Campos
200     nota            = DecimalCol(size=3, precision=1, default=None)
201     # Joins
202     inscripciones   = MultipleJoin('AlumnoInscripto')
203
204     def _get_padron(self): # alias para poder referirse al alumno por padron
205         return self.usuario
206
207     def _set_padron(self, padron):
208         self.usuario = padron
209
210     def __repr__(self):
211         return 'Alumno(id=%s, padron=%s, nombre=%s, password=%s, email=%s, ' \
212             'telefono=%s, activo=%s, creado=%s, observaciones=%s)' \
213                 % (self.id, self.padron, self.nombre, self.password, self.email,
214                     self.telefono, self.activo, self.creado, self.observaciones)
215 #}}}
216
217 class Tarea(InheritableSQLObject, ByObject): #{{{
218     # Clave
219     nombre          = UnicodeCol(length=30, alternateID=True)
220     # Campos
221     descripcion     = UnicodeCol(length=255, default=None)
222     # Joins
223
224     def _get_dependencias(self):
225         OtherTarea = Alias(Tarea, 'other_tarea')
226         self.__dependencias = tuple(Tarea.select(
227             AND(
228                 Tarea.q.id == dependencia_t.hijo_id,
229                 OtherTarea.q.id == dependencia_t.padre_id,
230                 self.id == dependencia_t.padre_id,
231             ),
232             clauseTables=(dependencia_t,),
233             orderBy=dependencia_t.orden,
234         ))
235         return self.__dependencias
236
237     def _set_dependencias(self, dependencias):
238         orden = {}
239         for i, t in enumerate(dependencias):
240             orden[t.id] = i
241         new = frozenset([t.id for t in dependencias])
242         old = frozenset([t.id for t in self.dependencias])
243         dependencias = dict([(t.id, t) for t in dependencias])
244         for tid in old - new: # eliminadas
245             self._connection.query(str(Delete(dependencia_t, where=AND(
246                 dependencia_t.padre_id == self.id,
247                 dependencia_t.hijo_id == tid))))
248         for tid in new - old: # creadas
249             self._connection.query(str(Insert(dependencia_t, values=dict(
250                 padre_id=self.id, hijo_id=tid, orden=orden[tid]
251             ))))
252         for tid in new & old: # actualizados
253             self._connection.query(str(Update(dependencia_t,
254                 values=dict(orden=orden[tid]), where=AND(
255                     dependencia_t.padre_id == self.id,
256                     dependencia_t.hijo_id == tid,
257                 ))))
258
259     def __repr__(self):
260         return 'Tarea(id=%s, nombre=%s, descripcion=%s)' \
261                 % (self.id, self.nombre, self.descripcion)
262
263     def shortrepr(self):
264         return self.nombre
265 #}}}
266
267 class Enunciado(SQLObject, ByObject): #{{{
268     # Clave
269     nombre          = UnicodeCol(length=60, alternateID=True)
270     # Campos
271     descripcion     = UnicodeCol(length=255, default=None)
272     autor           = ForeignKey('Docente', default=None)
273     creado          = DateTimeCol(notNone=True, default=DateTimeCol.now)
274     # Joins
275     ejercicios      = MultipleJoin('Ejercicio')
276     casos_de_prueba = MultipleJoin('CasoDePrueba')
277
278     def add_caso_de_prueba(self, nombre, **opts):
279         return CasoDePrueba(enunciadoID=self.id, nombre=nombre, **opts)
280
281     def __repr__(self):
282         return 'Enunciado(id=%s, autor=%s, nombre=%s, descripcion=%s, ' \
283             'creado=%s)' \
284                 % (self.id, srepr(self.autor), self.nombre, self.descripcion, \
285                     self.creado)
286
287     def shortrepr(self):
288         return self.nombre
289 #}}}
290
291 class CasoDePrueba(SQLObject): #{{{
292     # Clave
293     enunciado       = ForeignKey('Enunciado')
294     nombre          = UnicodeCol(length=40, notNone=True)
295     pk              = DatabaseIndex(enunciado, nombre, unique=True)
296     # Campos
297 #    privado         = IntCol(default=None) TODO iria en instancia_de_entrega_caso_de_prueba
298     parametros      = TupleCol(notNone=True, default=())
299     retorno         = IntCol(default=None)
300     tiempo_cpu      = FloatCol(default=None)
301     descripcion     = UnicodeCol(length=255, default=None)
302     # Joins
303     pruebas         = MultipleJoin('Prueba')
304
305     def __repr__(self):
306         return 'CasoDePrueba(enunciado=%s, nombre=%s, parametros=%s, ' \
307             'retorno=%s, tiempo_cpu=%s, descripcion=%s)' \
308                 % (self.enunciado.shortrepr(), self.nombre, self.parametros,
309                     self.retorno, self.tiempo_cpu, self.descripcion)
310
311     def shortrepr(self):
312         return '%s:%s' % (self.enunciado.shortrepr(), self.nombre)
313 #}}}
314
315 class Ejercicio(SQLObject, ByObject): #{{{
316     # Clave
317     curso           = ForeignKey('Curso', notNone=True)
318     numero          = IntCol(notNone=True)
319     pk              = DatabaseIndex(curso, numero, unique=True)
320     # Campos
321     enunciado       = ForeignKey('Enunciado', notNone=True)
322     grupal          = BoolCol(notNone=True, default=False)
323     # Joins
324     instancias      = MultipleJoin('InstanciaDeEntrega')
325
326     def add_instancia(self, numero, inicio, fin, **opts):
327         return InstanciaDeEntrega(ejercicioID=self.id, numero=numero,
328             inicio=inicio, fin=fin, **opts)
329
330     def __repr__(self):
331         return 'Ejercicio(id=%s, curso=%s, numero=%s, enunciado=%s, ' \
332             'grupal=%s)' \
333                 % (self.id, self.curso.shortrepr(), self.numero,
334                     self.enunciado.shortrepr(), self.grupal)
335
336     def shortrepr(self):
337         return '(%s, %s, %s)' \
338             % (self.curso.shortrepr(), self.nombre, \
339                 self.enunciado.shortrepr())
340 #}}}
341
342 class InstanciaDeEntrega(SQLObject, ByObject): #{{{
343     # Clave
344     ejercicio       = ForeignKey('Ejercicio', notNone=True)
345     numero          = IntCol(notNone=True)
346     # Campos
347     inicio          = DateTimeCol(notNone=True)
348     fin             = DateTimeCol(notNone=True)
349     procesada       = BoolCol(notNone=True, default=False)
350     observaciones   = UnicodeCol(default=None)
351     activo          = BoolCol(notNone=True, default=True)
352     # Joins
353     entregas        = MultipleJoin('Entrega', joinColumn='instancia_id')
354     correcciones    = MultipleJoin('Correccion', joinColumn='instancia_id')
355     casos_de_prueba = RelatedJoin('CasoDePrueba') # TODO CasoInstancia -> private
356
357     def _get_tareas(self):
358         self.__tareas = tuple(Tarea.select(
359             AND(
360                 Tarea.q.id == instancia_tarea_t.tarea_id,
361                 InstanciaDeEntrega.q.id == instancia_tarea_t.instancia_id
362             ),
363             clauseTables=(instancia_tarea_t, InstanciaDeEntrega.sqlmeta.table),
364             orderBy=instancia_tarea_t.orden,
365         ))
366         return self.__tareas
367
368     def _set_tareas(self, tareas):
369         orden = {}
370         for i, t in enumerate(tareas):
371             orden[t.id] = i
372         new = frozenset([t.id for t in tareas])
373         old = frozenset([t.id for t in self.tareas])
374         tareas = dict([(t.id, t) for t in tareas])
375         for tid in old - new: # eliminadas
376             self._connection.query(str(Delete(instancia_tarea_t, where=AND(
377                 instancia_tarea_t.instancia_id == self.id,
378                 instancia_tarea_t.tarea_id == tid))))
379         for tid in new - old: # creadas
380             self._connection.query(str(Insert(instancia_tarea_t, values=dict(
381                 instancia_id=self.id, tarea_id=tid, orden=orden[tid]
382             ))))
383         for tid in new & old: # actualizados
384             self._connection.query(str(Update(instancia_tarea_t,
385                 values=dict(orden=orden[tid]), where=AND(
386                     instancia_tarea_t.instancia_id == self.id,
387                     instancia_tarea_t.tarea_id == tid,
388                 ))))
389
390     def __repr__(self):
391         return 'InstanciaDeEntrega(id=%s, numero=%s, inicio=%s, fin=%s, ' \
392             'procesada=%s, observaciones=%s, activo=%s)' \
393                 % (self.id, self.numero, self.inicio, self.fin,
394                     self.procesada, self.observaciones, self.activo)
395
396     def shortrepr(self):
397         return self.numero
398 #}}}
399
400 class DocenteInscripto(SQLObject, ByObject): #{{{
401     # Clave
402     curso           = ForeignKey('Curso', notNone=True)
403     docente         = ForeignKey('Docente', notNone=True)
404     pk              = DatabaseIndex(curso, docente, unique=True)
405     # Campos
406     corrige         = BoolCol(notNone=True, default=True)
407     observaciones   = UnicodeCol(default=None)
408     # Joins
409     alumnos         = MultipleJoin('AlumnoInscripto', joinColumn='tutor_id')
410     tutorias        = MultipleJoin('Tutor', joinColumn='docente_id')
411     entregas        = MultipleJoin('Entrega', joinColumn='instancia_id')
412     correcciones    = MultipleJoin('Correccion', joinColumn='corrector_id')
413
414     def add_correccion(self, entrega, **opts):
415         return Correccion(correctorID=self.id, instanciaID=entrega.instancia.id,
416             entregadorID=entrega.entregador.id, entregaID=entrega.id, **opts)
417
418     def __repr__(self):
419         return 'DocenteInscripto(id=%s, docente=%s, corrige=%s, ' \
420             'observaciones=%s' \
421                 % (self.id, self.docente.shortrepr(), self.corrige,
422                     self.observaciones)
423
424     def shortrepr(self):
425         return self.docente.shortrepr()
426 #}}}
427
428 class Entregador(InheritableSQLObject, ByObject): #{{{
429     # Campos
430     nota            = DecimalCol(size=3, precision=1, default=None)
431     nota_cursada    = DecimalCol(size=3, precision=1, default=None)
432     observaciones   = UnicodeCol(default=None)
433     activo          = BoolCol(notNone=True, default=True)
434     # Joins
435     entregas        = MultipleJoin('Entrega')
436     correcciones    = MultipleJoin('Correccion')
437
438     def add_entrega(self, instancia, **opts):
439         return Entrega(entregadorID=self.id, instanciaID=instancia.id, **opts)
440
441     def __repr__(self):
442         raise NotImplementedError, 'Clase abstracta!'
443 #}}}
444
445 class Grupo(Entregador): #{{{
446     _inheritable = False
447     # Clave
448     curso           = ForeignKey('Curso', notNone=True)
449     nombre          = UnicodeCol(length=20, notNone=True)
450     # Campos
451     responsable     = ForeignKey('AlumnoInscripto', default=None)
452     # Joins
453     miembros        = MultipleJoin('Miembro')
454     tutores         = MultipleJoin('Tutor')
455
456     def add_alumno(self, alumno, **opts):
457         return Miembro(grupoID=self.id, alumnoID=alumno.id, **opts)
458
459     def add_docente(self, docente, **opts):
460         return Tutor(grupoID=self.id, docenteID=docente.id, **opts)
461
462     def __repr__(self):
463         return 'Grupo(id=%s, nombre=%s, responsable=%s, nota=%s, ' \
464             'nota_cursada=%s, observaciones=%s, activo=%s)' \
465                 % (self.id, self.nombre, srepr(self.responsable), self.nota,
466                     self.nota_cursada, self.observaciones, self.activo)
467
468     def shortrepr(self):
469         return 'grupo:' + self.nombre
470 #}}}
471
472 class AlumnoInscripto(Entregador): #{{{
473     _inheritable = False
474     # Clave
475     curso               = ForeignKey('Curso', notNone=True)
476     alumno              = ForeignKey('Alumno', notNone=True)
477     pk                  = DatabaseIndex(curso, alumno, unique=True)
478     # Campos
479     condicional         = BoolCol(notNone=True, default=False)
480     tutor               = ForeignKey('DocenteInscripto', default=None)
481     # Joins
482     responsabilidades   = MultipleJoin('Grupo', joinColumn='responsable_id')
483     membresias          = MultipleJoin('Miembro', joinColumn='alumno_id')
484     entregas            = MultipleJoin('Entrega', joinColumn='alumno_id')
485     correcciones        = MultipleJoin('Correccion', joinColumn='alumno_id')
486
487     def __repr__(self):
488         return 'AlumnoInscripto(id=%s, alumno=%s, condicional=%s, nota=%s, ' \
489             'nota_cursada=%s, tutor=%s, observaciones=%s, activo=%s)' \
490                 % (self.id, self.alumno.shortrepr(), self.condicional,
491                     self.nota, self.nota_cursada, srepr(self.tutor),
492                     self.observaciones, self.activo)
493
494     def shortrepr(self):
495         return self.alumno.shortrepr()
496 #}}}
497
498 class Tutor(SQLObject, ByObject): #{{{
499     # Clave
500     grupo           = ForeignKey('Grupo', notNone=True)
501     docente         = ForeignKey('DocenteInscripto', notNone=True)
502     pk              = DatabaseIndex(grupo, docente, unique=True)
503     # Campos
504     alta            = DateTimeCol(notNone=True, default=DateTimeCol.now)
505     baja            = DateTimeCol(default=None)
506
507     def __repr__(self):
508         return 'Tutor(docente=%s, grupo=%s, alta=%s, baja=%s)' \
509                 % (self.docente.shortrepr(), self.grupo.shortrepr(),
510                     self.alta, self.baja)
511
512     def shortrepr(self):
513         return '%s-%s' % (self.docente.shortrepr(), self.grupo.shortrepr())
514 #}}}
515
516 class Miembro(SQLObject, ByObject): #{{{
517     # Clave
518     grupo           = ForeignKey('Grupo', notNone=True)
519     alumno          = ForeignKey('AlumnoInscripto', notNone=True)
520     pk              = DatabaseIndex(grupo, alumno, unique=True)
521     # Campos
522     nota            = DecimalCol(size=3, precision=1, default=None)
523     alta            = DateTimeCol(notNone=True, default=DateTimeCol.now)
524     baja            = DateTimeCol(default=None)
525
526     def __repr__(self):
527         return 'Miembro(alumno=%s, grupo=%s, nota=%s, alta=%s, baja=%s)' \
528                 % (self.alumno.shortrepr(), self.grupo.shortrepr(),
529                     self.nota, self.alta, self.baja)
530
531     def shortrepr(self):
532         return '%s-%s' % (self.alumno.shortrepr(), self.grupo.shortrepr())
533 #}}}
534
535 class Entrega(SQLObject, ByObject): #{{{
536     # Clave
537     instancia       = ForeignKey('InstanciaDeEntrega', notNone=True)
538     entregador      = ForeignKey('Entregador', default=None) # Si es None era un Docente
539     fecha           = DateTimeCol(notNone=True, default=DateTimeCol.now)
540     pk              = DatabaseIndex(instancia, entregador, fecha, unique=True)
541     # Campos
542     correcta        = BoolCol(notNone=True, default=False)
543     observaciones   = UnicodeCol(default=None)
544     # Joins
545     tareas          = MultipleJoin('TareaEjecutada')
546     # Para generar código
547     codigo_dict     = r'0123456789abcdefghijklmnopqrstuvwxyz_.,*@#+'
548     codigo_format   = r'%m%d%H%M%S'
549
550     def add_tarea_ejecutada(self, tarea, **opts):
551         return TareaEjecutada(entregaID=self.id, tareaID=tarea.id, **opts)
552
553     def _get_codigo(self):
554         if not hasattr(self, '_codigo'): # cache
555             n = long(self.fecha.strftime(Entrega.codigo_format))
556             d = Entrega.codigo_dict
557             l = len(d)
558             res = ''
559             while n:
560                     res += d[n % l]
561                     n /= l
562             self._codigo = res
563         return self._codigo
564
565     def _set_fecha(self, fecha):
566         self._SO_set_fecha(fecha)
567         if hasattr(self, '_codigo'): del self._codigo # bye, bye cache!
568
569     def __repr__(self):
570         return 'Entrega(instancia=%s, entregador=%s, codigo=%s, fecha=%s, ' \
571             'correcta=%s, observaciones=%s)' \
572                 % (self.instancia.shortrepr(), srepr(self.entregador),
573                     self.codigo, self.fecha, self.correcta, self.observaciones)
574
575     def shortrepr(self):
576         return '%s-%s-%s' % (self.instancia.shortrepr(), srepr(self.entregador),
577             self.codigo)
578 #}}}
579
580 class Correccion(SQLObject, ByObject): #{{{
581     # Clave
582     instancia       = ForeignKey('InstanciaDeEntrega', notNone=True)
583     entregador      = ForeignKey('Entregador', notNone=True) # Docente no tiene
584     pk              = DatabaseIndex(instancia, entregador, unique=True)
585     # Campos
586     entrega         = ForeignKey('Entrega', notNone=True)
587     corrector       = ForeignKey('DocenteInscripto', notNone=True)
588     asignado        = DateTimeCol(notNone=True, default=DateTimeCol.now)
589     corregido       = DateTimeCol(default=None)
590     nota            = DecimalCol(size=3, precision=1, default=None)
591     observaciones   = UnicodeCol(default=None)
592
593     def __repr__(self):
594         return 'Correccion(instancia=%s, entregador=%s, entrega=%s, ' \
595             'corrector=%s, asignado=%s, corregido=%s, nota=%s, ' \
596             'observaciones=%s)' \
597                 % (self.instancia.shortrepr(), self.entregador.shortrepr(),
598                     self.entrega.shortrepr(), self.corrector, self.asignado,
599                     self.corregido, self.nota, self.observaciones)
600
601     def shortrepr(self):
602         return '%s,%s' % (self.entrega.shortrepr(), self.corrector.shortrepr())
603 #}}}
604
605 class TareaEjecutada(InheritableSQLObject, ByObject): #{{{
606     # Clave
607     tarea           = ForeignKey('Tarea', notNone=True)
608     entrega         = ForeignKey('Entrega', notNone=True)
609     pk              = DatabaseIndex(tarea, entrega, unique=True)
610     # Campos
611     inicio          = DateTimeCol(notNone=True, default=DateTimeCol.now)
612     fin             = DateTimeCol(default=None)
613     exito           = IntCol(default=None)
614     observaciones   = UnicodeCol(default=None)
615     # Joins
616     pruebas         = MultipleJoin('Prueba')
617
618     def add_prueba(self, caso_de_prueba, **opts):
619         return Prueba(tarea_ejecutadaID=self.id,
620             caso_de_pruebaID=caso_de_prueba.id, **opts)
621
622     def __repr__(self):
623         return 'TareaEjecutada(tarea=%s, entrega=%s, inicio=%s, fin=%s, ' \
624             'exito=%s, observaciones=%s)' \
625                 % (self.tarea.shortrepr(), self.entrega.shortrepr(),
626                     self.inicio, self.fin, self.exito, self.observaciones)
627
628     def shortrepr(self):
629         return '%s-%s' % (self.tarea.shortrepr(), self.entrega.shortrepr())
630 #}}}
631
632 class Prueba(SQLObject): #{{{
633     # Clave
634     tarea_ejecutada = ForeignKey('TareaEjecutada', notNone=True)
635     caso_de_prueba  = ForeignKey('CasoDePrueba', notNone=True)
636     pk              = DatabaseIndex(tarea_ejecutada, caso_de_prueba, unique=True)
637     # Campos
638     inicio          = DateTimeCol(notNone=True, default=DateTimeCol.now)
639     fin             = DateTimeCol(default=None)
640     pasada          = IntCol(default=None)
641     observaciones   = UnicodeCol(default=None)
642
643     def __repr__(self):
644         return 'Prueba(tarea_ejecutada=%s, caso_de_prueba=%s, inicio=%s, ' \
645             'fin=%s, pasada=%s, observaciones=%s)' \
646                 % (self.tarea_ejecutada.shortrepr(),
647                     self.caso_de_prueba.shortrepr(), self.inicio, self.fin,
648                     self.pasada, self.observaciones)
649
650     def shortrepr(self):
651         return '%s:%s' % (self.tarea_ejecutada.shortrepr(),
652             self.caso_de_prueba.shortrerp())
653 #}}}
654
655 #{{{ Específico de Identity
656
657 class Visita(SQLObject): #{{{
658     visit_key   = StringCol(length=40, alternateID=True,
659                     alternateMethodName="by_visit_key")
660     created     = DateTimeCol(notNone=True, default=datetime.now)
661     expiry      = DateTimeCol()
662
663     @classmethod
664     def lookup_visit(cls, visit_key):
665         try:
666             return cls.by_visit_key(visit_key)
667         except SQLObjectNotFound:
668             return None
669 #}}}
670
671 class VisitaUsuario(SQLObject): #{{{
672     # Clave
673     visit_key   = StringCol(length=40, alternateID=True,
674                           alternateMethodName="by_visit_key")
675     # Campos
676     user_id     = IntCol() # Negrada de identity
677 #}}}
678
679
680 class Rol(SQLObject): #{{{
681     # Clave
682     nombre      = UnicodeCol(length=255, alternateID=True,
683                     alternateMethodName="by_group_name")
684     # Campos
685     descripcion = UnicodeCol(length=255, default=None)
686     creado      = DateTimeCol(notNone=True, default=datetime.now)
687     permisos    = TupleCol(notNone=True)
688     # Joins
689     usuarios    = RelatedJoin('Usuario')
690 #}}}
691
692 # No es un SQLObject porque no tiene sentido agregar/sacar permisos, están
693 # hardcodeados en el código
694 class Permiso(object): #{{{
695     def __init__(self, nombre, descripcion):
696         self.nombre = nombre
697         self.descripcion = descripcion
698
699     @classmethod
700     def createTable(cls, ifNotExists): # para identity
701         pass
702
703     @property
704     def permission_name(self): # para identity
705         return self.nombre
706
707     def __repr__(self):
708         return self.nombre
709 #}}}
710
711 # TODO ejemplos
712 entregar_tp = Permiso(u'entregar', u'Permite entregar trabajos prácticos')
713 admin = Permiso(u'admin', u'Permite hacer ABMs arbitrarios')
714
715 #}}} Identity
716
717 #}}} Clases
718