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