]> git.llucax.com Git - software/sercom.git/blob - sercom/subcontrollers/curso/grupo/__init__.py
Enlace de Volver desde Entregas a Ejercicio.
[software/sercom.git] / sercom / subcontrollers / curso / grupo / __init__.py
1 # vim: set et sw=4 sts=4 encoding=utf-8 foldmethod=marker :
2
3 #{{{ Imports
4 import cherrypy
5 from turbogears import controllers, expose, redirect
6 from turbogears import validate, flash, error_handler
7 from turbogears import validators as V
8 from turbogears import widgets as W
9 from turbogears import identity
10 from turbogears import paginate, url
11 from docutils.core import publish_parts
12 from sercom.subcontrollers import validate as val
13 from sercom.model import Curso, AlumnoInscripto, Docente, DocenteInscripto, Grupo, Alumno, Miembro
14 from sqlobject import *
15 from sqlobject.dberrors import *
16
17 from sercom.widgets import *
18
19 import logging
20
21 log = logging.getLogger('sercom.curso.grupo.admin')
22
23 #}}}
24
25 #{{{ Configuración
26 cls = Grupo
27 name = 'grupo'
28 namepl = 'grupos'
29 #}}}
30
31 #{{{ Validación
32 def validate_get(id):
33     return val.validate_get(cls, name, id)
34
35 def validate_set(id, data):
36     return val.validate_set(cls, name, id, data)
37
38 def validate_new(data):
39     return val.validate_new(cls, name, data)
40
41 def validate_del(id):
42     return val.validate_del(cls, name, id)
43 #}}}
44
45 #{{{ Formulario
46 def get_docentes():
47     return [(fk1.id, fk1.shortrepr()) for fk1 in Docente.select()]
48
49 def get_docentes_inscriptos(id):
50     return [(fk1.id, fk1.shortrepr()) for fk1 in DocenteInscripto.select(DocenteInscripto.q.cursoID==id)]
51
52 ajax = u"""
53     function alumnos_agregar_a_la_lista(texto, lista)
54     {
55         t = MochiKit.DOM.getElement(texto);
56
57         curso = MochiKit.DOM.getElement('form_cursoID');
58         if (!curso) {
59             alert("No deberias ver esto, y quiere decir que tu form esta roto.\\nTe falta un combo de curso");
60             return;
61         }
62         if (curso.value <= 0) {
63             alert('Debes seleccionar un curso primero');
64             return;
65         }
66         url = "/curso/grupo/get_inscripto?cursoid="+curso.value+"&padron="+t.value;
67         t.value = "";
68         return url;
69     }
70
71     function err (err)
72     {
73         alert("The metadata for MochiKit.Async could not be fetched :(");
74     }
75
76     function procesar(result)
77     {
78         l = MochiKit.DOM.getElement('form_responsable_info');
79         if (result.error)
80             l.innerHTML = result.msg;
81         else
82             l.innerHTML = result.msg.value;
83     }
84
85     function buscar_alumno()
86     {
87         /* Obtengo el padron ingresado */
88         p = MochiKit.DOM.getElement('form_responsable');
89         padron = p.value;
90         if (padron == '') {
91             return;
92         }
93         /* Obtengo el curso */
94         l = MochiKit.DOM.getElement('form_cursoID');
95         cursoid = l.value;
96         if (cursoid <= 0) {
97             alert('Debe seleccionar un curso');
98             return;
99         }
100         url = "/curso/grupo/get_inscripto?cursoid="+cursoid+'&padron='+padron;
101         var d = loadJSONDoc(url);
102         d.addCallbacks(procesar, err);
103     }
104
105     function prepare()
106     {
107         connect('form_responsable', 'onblur', buscar_alumno);
108     }
109
110     function doSubmit()
111     {
112         /* TODO : Validar datos y evitar el submit si no esta completo */
113
114         /* Selecciono todos los miembros si no, no llegan al controllere*/
115         l = MochiKit.DOM.getElement('form_miembros');
116         for (i=0; i<l.options.length; i++) {
117             l.options[i].selected = true;
118         }
119         return true; // Dejo hacer el submit
120     }
121
122     MochiKit.DOM.addLoadEvent(prepare)
123
124 """
125
126 class GrupoForm(W.TableForm):
127     class Fields(W.WidgetsList):
128         cursoID = W.HiddenField(validator=V.Int)
129         nombre = W.TextField(label=_(u'Nombre'), validator=V.UnicodeString(not_empty=True,strip=True))
130         responsable = CustomTextField(label=_(u'Responsable'), validator=V.UnicodeString(), attrs=dict(size='8'))
131         miembros = AjaxMultiSelect(label=_(u'Miembros'), validator=V.Int(), on_add="alumnos_agregar_a_la_lista")
132         tutores = W.MultipleSelectField(label=_(u'Tutores'), validator=V.Int)
133
134     fields = Fields()
135     javascript = [W.JSSource("MochiKit.DOM.focusOnLoad('form_nombre');"), W.JSSource(ajax)]
136     form_attrs = dict(onsubmit='return doSubmit()')
137
138 form = GrupoForm()
139
140 def get_gruposA(cursoID):
141     return [(0, u'---')] + [(g.id, g.shortrepr()) for g in Grupo.select(Grupo.q.cursoID==cursoID)]
142
143 def get_gruposB(cursoID):
144     return [(0, u'Nuevo Grupo')] + [(g.id, g.shortrepr()) for g in Grupo.select(Grupo.q.cursoID==cursoID)]
145
146 ajaxadmin = u"""
147     function err (err)
148     {
149         alert("The metadata for MochiKit.Async could not be fetched :(");
150     }
151
152     function doSubmit()
153     {
154         /* TODO : Validar datos y evitar el submit si no esta completo */
155
156         /* Selecciono todos los miembros si no, no llegan al controllere*/
157         l = MochiKit.DOM.getElement('form_grupos_to');
158         for (i=0; i<l.options.length; i++) {
159             l.options[i].selected = true;
160         }
161         /* Selecciono todos los miembros si no, no llegan al controllere*/
162         l = MochiKit.DOM.getElement('form_grupos_from');
163         for (i=0; i<l.options.length; i++) {
164             l.options[i].selected = true;
165         }
166
167         return true; // Dejo hacer el submit
168     }
169
170     function initWidgets(disabled) {
171         if ( disabled ) {
172             MochiKit.DOM.getElement('form_listaGrupoA').selectedIndex = 0;
173         }
174         MochiKit.DOM.getElement('form_listaGrupoB').selectedIndex = 0;
175         MochiKit.DOM.getElement('form_grupos_to').options.length = 0;
176         MochiKit.DOM.getElement('form_grupos_from').options.length = 0;
177         MochiKit.DOM.getElement('form_listaGrupoB').disabled = disabled;
178         MochiKit.DOM.getElement('form_grupos_to').disabled = disabled;
179         MochiKit.DOM.getElement('form_grupos_from').disabled = disabled;
180         MochiKit.DOM.getElement('form_tutoresA').disabled = true;
181         MochiKit.DOM.getElement('form_tutoresB').disabled = true;
182         MochiKit.DOM.getElement('form_responsableA').disabled = true;
183         MochiKit.DOM.getElement('form_responsableB').disabled = true;
184     }
185
186     function onListaAChange() {
187         lista = MochiKit.DOM.getElement('form_listaGrupoA');
188         if ( lista.selectedIndex != '0' ) {
189             initWidgets(false);
190         } else {
191             initWidgets(true);
192             return;
193         }
194         // carga el grupo en el multiple select
195         grupoA = MochiKit.DOM.getElement('form_grupos_from');
196         id = lista.options[lista.selectedIndex].value
197         cargarGrupo(id, grupoA);
198     }
199
200     function onListaBChange() {
201         lista = MochiKit.DOM.getElement('form_listaGrupoB');
202         listaA =  MochiKit.DOM.getElement('form_listaGrupoA');
203         MochiKit.DOM.getElement('form_grupos_to').options.length = 0;
204         if ( lista.selectedIndex == 0 ) {
205             return;
206         }
207         if ( lista.selectedIndex != '0' ) {
208             if ( lista.selectedIndex == listaA.selectedIndex ) {
209                 window.alert('Debe seleccionar 2 grupos distintos');
210                 MochiKit.DOM.getElement('form_grupos_to').options.length = 0;
211                 return;
212             }
213             grupoB = MochiKit.DOM.getElement('form_grupos_to');
214             id = lista.options[lista.selectedIndex].value
215             cargarGrupo(id, grupoB);
216         }
217     }
218     
219     function makeOption(option) {
220         return OPTION({"value": option.value}, option.text);
221     }
222
223     function cargarGrupo(grupoid, lista) {
224         var result = loadJSONDoc('/curso/grupo/get_alumnos?grupoid='+id);
225         result.addCallbacks(partial(cargarLista, lista), err)
226     }
227
228     function err (err)
229     {
230         alert("The metadata for MochiKit.Async could not be fetched :(");
231     }
232
233     function cargarLista(lista, result) {
234         var alumnos = result.msg;
235         if (result.error) {
236             window.alert(result.msg);
237             return;
238         }
239         for (i in alumnos) {
240             id = alumnos[i].id;
241             label = alumnos[i].label;
242             MochiKit.DOM.appendChildNodes(lista, OPTION({"value":id}, label))
243         }
244         ActualizarResponsables();
245     }
246
247     function ActualizarResponsables()
248     {
249         replaceChildNodes('form_responsableA', '');
250         replaceChildNodes('form_responsableB', '');
251         appendChildNodes('form_responsableA', map(makeOption, $('form_grupos_from').options));
252         appendChildNodes('form_responsableB', map(makeOption, $('form_grupos_to').options));
253
254         if (getElement('form_grupos_from').options.length == 0) {
255             getElement('form_tutoresA').disabled = true;
256             getElement('form_responsableA').disabled = true;
257         } else {
258             getElement('form_tutoresA').disabled = false;
259             getElement('form_responsableA').disabled = false;
260         }
261         if (getElement('form_grupos_to').options.length == 0) {
262             getElement('form_tutoresB').disabled = true;
263             getElement('form_responsableB').disabled = true;
264         } else {
265             getElement('form_tutoresB').disabled = false;
266             getElement('form_responsableB').disabled = false;
267         }
268     }
269 """
270
271 class GrupoAdminForm(W.TableForm):
272     class Fields(W.WidgetsList):
273         cursoID = W.HiddenField()
274         listaGrupoA = W.SingleSelectField(label=_(u'Grupo A'), attrs = dict(onChange='onListaAChange()'), validator = V.Int(not_empty=True))
275         listaGrupoB = W.SingleSelectField(label=_(u'Grupo B'), attrs = dict(onChange='onListaBChange()'), validator = V.Int(not_empty=True))
276         grupos = AjaxDosListasSelect(label=_(u'Grupos'),title_from=u"Grupo A", size=8, move_signal="ActualizarResponsables();", title_to=u"Grupo B", validator=V.Int(not_empty=True))
277         responsableA = W.SingleSelectField(label=_(u'Responsable A'), validator = V.Int())
278         responsableB = W.SingleSelectField(label=_(u'Responsable B'), validator = V.Int())
279         tutoresA = W.MultipleSelectField(label=_(u'Tutores A'), validator = V.Int(not_empty=True))
280         tutoresB = W.MultipleSelectField(label=_(u'Tutores B'), validator = V.Int(not_empty=True))
281
282     fields = Fields()
283     javascript = [W.JSSource("MochiKit.DOM.focusOnLoad('listaGrupoA');"), W.JSSource(ajaxadmin)]
284     form_attrs = dict(onsubmit='return doSubmit();')
285
286 formadmin = GrupoAdminForm()
287
288 #}}}
289
290 #{{{ Controlador
291 class GrupoController(controllers.Controller, identity.SecureResource):
292     """Basic model admin interface"""
293     require = identity.has_permission('admin')
294
295     @expose()
296     def default(self, tg_errors=None):
297         """handle non exist urls"""
298         raise redirect(tg.url('/curso/list'))
299
300     @expose()
301     def index(self):
302         raise redirect(tg.url('/curso/list'))
303
304     @expose(template='kid:%s.templates.list' % __name__)
305     @validate(validators=dict(curso=V.Int))
306     @paginate('records')
307     def list(self, curso):
308         """List records in model"""
309         r = cls.selectBy(cursoID=curso)
310         return dict(records=r, name=name, namepl=namepl, cursoID=curso)
311
312     @expose(template='kid:%s.templates.new' % __name__)
313     @validate(validators=dict(curso=V.Int))
314     def new(self, curso, **kw):
315         """Create new records in model"""
316         kw['cursoID'] = curso # FIXME esto está roto porque los widgets son stateless
317         options = dict(tutores=get_docentes_inscriptos(curso))
318         return dict(name=name, namepl=namepl, form=form, options=options, values=kw)
319
320     @validate(form=form)
321     @error_handler(new)
322     @expose()
323     def create(self, **kw):
324         """Save or create record to model"""
325         resp = kw['responsable']
326         try:
327             # Busco el alumno inscripto
328             resp = AlumnoInscripto.selectBy(cursoID=kw['cursoID'],
329                 alumno=Alumno.byPadron(kw['responsable'])).getOne()
330         except SQLObjectNotFound:
331             resp = None
332         kw['responsable'] = resp
333
334         r = validate_new(kw)
335         flash(_(u'Se creó un nuevo %s.') % name)
336         raise redirect('list/%d' % int(kw['cursoID']))
337
338     @expose(template='kid:%s.templates.edit' % __name__)
339     def edit(self, id, **kw):
340         """Edit record in model"""
341         r = validate_get(id)
342         # TODO : No encontre mejor forma de pasar cosas al form
343         # de manera comoda y facil de formatear segun lo que espera la UI (que
344         # en este caso es super particular). Ese EmptyClass no se si hay algo estandar
345         # en python que usar, puse {} y [] pero cuando quiero hacer values.id = XX explota.
346         options = dict(tutores=get_docentes_inscriptos(r.curso.id))
347         class EmptyClass:
348             pass
349         values = EmptyClass()
350         values.id = r.id
351         values.cursoID = r.cursoID
352         values.nombre = r.nombre
353         # TODO : Ver como llenar la lista primero :S
354         if r.responsable:
355             values.responsable = r.responsable.alumno.padron
356         values.miembros = [{"id":i.alumno.id, "label":i.alumno.alumno.nombre} for i in filter(lambda x: x.baja is None, r.miembros)]
357         values.tutores = [a.docenteID for a in r.tutores]
358         return dict(name=name, namepl=namepl, record=values, options=options, form=form)
359
360     @validate(form=form)
361     @error_handler(edit)
362     @expose()
363     def update(self, id, **kw):
364         """Save or create record to model"""
365         responsable = kw['responsable']
366         curso = kw['cursoID']
367         resp = kw['responsable']
368         try:
369             # Busco el alumno inscripto
370             resp = AlumnoInscripto.selectBy(cursoID=kw['cursoID'],
371                 alumno=Alumno.byPadron(kw['responsable'])).getOne()
372         except SQLObjectNotFound:
373             resp = None
374         kw['responsable'] = resp
375         r = validate_set(id, kw)
376         flash(_(u'El %s fue actualizado.') % name)
377         raise redirect('../list/%d' % r.curso.id)
378
379     @expose(template='kid:%s.templates.show' % __name__)
380     def show(self,id, **kw):
381         """Show record in model"""
382         r = validate_get(id)
383         return dict(name=name, namepl=namepl, record=r)
384
385     @expose()
386     def delete(self, cursoID, id):
387         """Destroy record in model"""
388         validate_del(id)
389         flash(_(u'El %s fue eliminado permanentemente.') % name)
390         raise redirect('../../list/%d' % int(cursoID))
391
392     @expose('json')
393     def get_inscripto(self, cursoid, padron):
394         msg = u''
395         error=False
396         try:
397             # Busco el alumno inscripto
398             alumno = AlumnoInscripto.selectBy(curso=cursoid, alumno=Alumno.byUsuario(padron)).getOne()
399             msg = {}
400             msg['id'] = alumno.id
401             msg['value'] = alumno.alumno.nombre
402         except SQLObjectNotFound:
403             msg = 'No existe el alumno %s en el curso seleccionado.' % padron
404             error=True
405         except Exception, (inst):
406             msg = u"""Se ha producido un error inesperado al buscar el registro:\n      %s""" % str(inst)
407             error = True
408         return dict(msg=msg, error=error)
409
410     @expose('json')
411     def get_alumnos(self, grupoid):
412         msg = u''
413         error=False
414         try:
415             # Busco los alumnos del grupo
416             grupo = Grupo.get(int(grupoid))
417             miembros = Miembro.selectBy(baja=None, grupo=grupo)
418             print miembros
419             integrantes = []
420             for m in miembros:
421                 msg = {}
422                 alumnoInscripto = AlumnoInscripto.get(m.alumno.id)
423                 msg['id'] = alumnoInscripto.id
424                 msg['label'] = alumnoInscripto.shortrepr()
425                 integrantes.append(msg)
426         except Exception, (inst):
427             msg = u"""Se ha producido un error inesperado al buscar el registro:\n      %s""" % str(inst)
428             error = True
429             integrantes = []
430             integrantes.append(msg)
431         return dict(msg=integrantes, error=error)
432
433     @expose(template='kid:%s.templates.admin' % __name__)
434     def admin(self, cursoID, **kw):
435         """Create new records in model"""
436         options = dict(
437             listaGrupoA=get_gruposA(cursoID),
438             listaGrupoB=get_gruposB(cursoID),
439             tutoresA=get_docentes_inscriptos(cursoID),
440             tutoresB=get_docentes_inscriptos(cursoID),
441         )
442         kw['cursoID'] = cursoID
443         return dict(name=name, namepl=namepl, options=options, form=formadmin, values=kw, cursoID=int(cursoID))
444
445     @validate(form=formadmin)
446     @error_handler(admin)
447     @expose()
448     def adminupdate(self, **kw):
449         """Save or create record to model"""
450         cursoID = int(kw['cursoID'])
451         log.debug(kw)
452         grupoAId = kw['listaGrupoA']
453         grupoBId = kw['listaGrupoB']
454         miembrosA = kw.get('grupos_from', [])
455         miembrosB = kw.get('grupos_to', [])
456         responsableA = kw['responsableA']
457         responsableB = kw['responsableB']
458         tutoresA = kw.get('tutoresA', [])
459         tutoresB = kw.get('tutoresB', [])
460
461         # por las dudas de que no sea una lista
462         if not isinstance(miembrosA, list):
463             miembrosA = [miembrosA]
464         if not isinstance(miembrosB, list):
465             miembrosB = [miembrosB]
466         if not isinstance(tutoresA, list):
467             tutoresA = [tutoresA]
468         if not isinstance(tutoresB, list):
469             tutoresB = [tutoresB]
470
471
472         """ levanto los grupos originales """
473         grupoAorig = validate_get(int(grupoAId))
474         log.debug(miembrosA)
475         log.debug(Miembro.selectBy(grupo=grupoAorig, baja=None))
476         """ Si el grupo A quedo vacio deberia eliminarlo (primero
477             genero los otros para que no elimine los alumnos)"""
478         for mA in Miembro.selectBy(grupo=grupoAorig, baja=None):
479             if str(mA.alumno.id) not in miembrosA:
480                 grupoAorig.remove_miembro(mA.alumno.id)
481
482         try:
483             grupoA = validate_get(grupoAId)
484             for a in miembrosA:
485                 try:
486                     grupoA.add_miembro(a, baja=None)
487                 except DuplicateEntryError:
488                     continue
489         except Exception, e:
490             log.debug(e)
491             flash(_(u'Error A %s.' % e))
492             raise redirect(url('/curso/grupo/list' % int(cursoID)))
493         # seteo el reponsable del grupo
494         if responsableA and int(responsableA) != 0:
495             grupoA.responsable = AlumnoInscripto.get(int(responsableA))
496
497         for t in tutoresA:
498             try:
499                 grupoA.add_tutor(int(t))
500             except:
501                 #TODO ver por que no anda el duplicate error, por ahora cacheo silencioso
502                 pass
503
504
505         #Elimino el grupo si quedo vacio
506         if len(miembrosA) == 0:
507             try:
508                 validate_del(grupoAId)
509             except:
510                 pass
511
512         # si se selecciono un grupo nuevo
513         if int(grupoBId) == 0:
514             # creo un grupo nuevo
515             nuevosMiembros = []
516             for m in miembrosB:
517                 nuevosMiembros.append(AlumnoInscripto.get(int(m)))
518             nuevosTutores = []
519             for t in tutoresB:
520                 nuevosTutores.append(DocenteInscripto.get(t))
521             #Creo el nuevo grupo
522             Grupo(miembros = nuevosMiembros, tutores = nuevosTutores, cursoID=cursoID, nombre='NuevoGrupo'+str(cursoID))
523         else:
524             grupoBorig = validate_get(int(grupoBId))
525             log.debug(miembrosB)
526             b = list(Miembro.selectBy(grupo=grupoBorig, baja=None))
527             log.debug(b)
528             #borro todos y los vuelvo a agregar
529             for mB in Miembro.selectBy(grupo=grupoBorig, baja=None):
530                 if str(mB.alumno.id) not in miembrosB:
531                     grupoBorig.remove_miembro(mB.alumno.id)
532             try:
533                 grupoB = validate_get(grupoBId)
534                 for b in miembrosB:
535                     try:
536                         grupoB.add_miembro(b, baja=None)
537                     except DuplicateEntryError:
538                         continue
539             except Exception, e:
540                 log.debug(e)
541                 flash(_(u'Error B %s.' % e))
542                 raise redirect(url('/curso/grupo/list/%d' % int(cursoID)))
543             # seteo el reponsable del grupo
544             if responsableB and int(responsableB) != 0:
545                 grupoB.responsable = AlumnoInscripto.get(int(responsableB))
546
547             #Elimino el grupo si quedo vacio
548             if len(miembrosB) == 0:
549                 try:
550                     validate_del(grupoBId)
551                 except:
552                     pass
553
554             for t in tutoresB:
555                 try:
556                     grupoB.add_tutor(int(t))
557                 except:
558                     #TODO ver por que no anda el duplicate error, por ahora cahceo silencioso
559                     pass
560         flash(_(u'Los grupos fueron actualizado.'))
561         raise redirect(url('/curso/grupo/list/%d' % int(cursoID)))
562 #}}}
563