Estructura básica del proyecto con el modelo de datos teóricamente terminado.
--- /dev/null
+sercom-so
+
+This is a TurboGears (http://www.turbogears.org) project. It can be
+started by running the start-sercom.py script.
\ No newline at end of file
--- /dev/null
+[global]
+# This is where all of your settings go for your development environment
+# Settings that are the same for both development and production
+# (such as template engine, encodings, etc.) all go in
+# sercom/config/app.cfg
+
+# DATABASE
+
+# pick the form for your database
+# sqlobject.dburi="postgres://username@hostname/databasename"
+# sqlobject.dburi="mysql://username:password@hostname:port/databasename"
+# sqlobject.dburi="sqlite:///file_name_and_path"
+
+# If you have sqlite, here's a simple default to get you started
+# in development
+sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite"
+
+
+# if you are using a database or table type without transactions
+# (MySQL default, for example), you should turn off transactions
+# by prepending notrans_ on the uri
+# sqlobject.dburi="notrans_mysql://username:password@hostname:port/databasename"
+
+# for Windows users, sqlite URIs look like:
+# sqlobject.dburi="sqlite:///drive_letter:/path/to/file"
+
+# SERVER
+
+# Some server parameters that you may want to tweak
+# server.socket_port=8080
+
+# Enable the debug output at the end on pages.
+# log_debug_info_filter.on = False
+
+server.environment="development"
+autoreload.package="sercom"
+
+# session_filter.on = True
+
+# Set to True if you'd like to abort execution if a controller gets an
+# unexpected parameter. False by default
+tg.strict_parameters = True
+
+# LOGGING
+# Logging configuration generally follows the style of the standard
+# Python logging module configuration. Note that when specifying
+# log format messages, you need to use *() for formatting variables.
+# Deployment independent log configuration is in sercom/config/log.cfg
+[logging]
+
+[[loggers]]
+[[[sercom]]]
+level='DEBUG'
+qualname='sercom'
+handlers=['debug_out']
+
+[[[allinfo]]]
+level='INFO'
+handlers=['debug_out']
+
+[[[access]]]
+level='INFO'
+qualname='turbogears.access'
+handlers=['access_out']
+propagate=0
--- /dev/null
+[global]
+# This is where all of your settings go for your production environment.
+# You'll copy this file over to your production server and provide it
+# as a command-line option to your start script.
+# Settings that are the same for both development and production
+# (such as template engine, encodings, etc.) all go in
+# sercom/config/app.cfg
+
+# DATABASE
+
+# pick the form for your database
+# sqlobject.dburi="postgres://username@hostname/databasename"
+# sqlobject.dburi="mysql://username:password@hostname:port/databasename"
+# sqlobject.dburi="sqlite:///file_name_and_path"
+
+# If you have sqlite, here's a simple default to get you started
+# in development
+sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite"
+
+
+# if you are using a database or table type without transactions
+# (MySQL default, for example), you should turn off transactions
+# by prepending notrans_ on the uri
+# sqlobject.dburi="notrans_mysql://username:password@hostname:port/databasename"
+
+# for Windows users, sqlite URIs look like:
+# sqlobject.dburi="sqlite:///drive_letter:/path/to/file"
+
+
+# SERVER
+
+server.environment="production"
+
+# Sets the number of threads the server uses
+# server.thread_pool = 1
+
+# if this is part of a larger site, you can set the path
+# to the TurboGears instance here
+# server.webpath=""
+
+# session_filter.on = True
+
+# Set to True if you'd like to abort execution if a controller gets an
+# unexpected parameter. False by default
+# tg.strict_parameters = False
+
+# Set the following to True if you are deploying your app using mod_proxy,
+# mod_rewrite or any other mechanism that forwards requests to your app.
+# base_url_filter.on = False
+# base_url_filter.use_x_forwarded_host = False
+
+# LOGGING
+# Logging configuration generally follows the style of the standard
+# Python logging module configuration. Note that when specifying
+# log format messages, you need to use *() for formatting variables.
+# Deployment independent log configuration is in sercom/config/log.cfg
+[logging]
+
+[[handlers]]
+
+[[[access_out]]]
+# set the filename as the first argument below
+args="('server.log',)"
+class='FileHandler'
+level='INFO'
+formatter='message_only'
+
+[[loggers]]
+[[[sercom]]]
+level='ERROR'
+qualname='sercom'
+handlers=['error_out']
+
+[[[access]]]
+level='INFO'
+qualname='turbogears.access'
+handlers=['access_out']
+propagate=0
--- /dev/null
+# You can place test-specific configuration options here (like test db uri, etc)
+
+sqlobject.dburi = "sqlite:///:memory:"
+
--- /dev/null
+
+CREATE TABLE curso (
+ id INTEGER PRIMARY KEY,
+ anio INT NOT NULL,
+ cuatrimestre INT NOT NULL,
+ numero INT NOT NULL,
+ descripcion VARCHAR(255)
+);
+CREATE UNIQUE INDEX curso_pk ON curso (anio, cuatrimestre, numero);
+
+CREATE TABLE usuario (
+ id INTEGER PRIMARY KEY,
+ child_name VARCHAR(255),
+ usuario VARCHAR(10) NOT NULL UNIQUE,
+ contrasenia VARCHAR(255),
+ nombre VARCHAR(255) NOT NULL,
+ email VARCHAR(255),
+ telefono VARCHAR(255),
+ creado TIMESTAMP NOT NULL,
+ observaciones TEXT,
+ activo TINYINT NOT NULL
+);
+
+CREATE TABLE docente (
+ id INTEGER PRIMARY KEY,
+ nombrado TINYINT NOT NULL
+);
+
+CREATE TABLE alumno (
+ id INTEGER PRIMARY KEY,
+ nota DECIMAL(3, 1)
+);
+
+CREATE TABLE tarea (
+ id INTEGER PRIMARY KEY,
+ child_name VARCHAR(255),
+ nombre VARCHAR(30) NOT NULL UNIQUE,
+ descripcion VARCHAR(255)
+);
+
+CREATE TABLE dependencia (
+ padre_id INTEGER NOT NULL CONSTRAINT tarea_id_exists REFERENCES tarea(id),
+ hijo_id INTEGER NOT NULL CONSTRAINT tarea_id_exists REFERENCES tarea(id),
+ orden INT,
+ PRIMARY KEY (padre_id, hijo_id)
+);
+
+CREATE TABLE enunciado (
+ id INTEGER PRIMARY KEY,
+ nombre VARCHAR(60) NOT NULL UNIQUE,
+ descripcion VARCHAR(255),
+ docente_id INT CONSTRAINT docente_id_exists REFERENCES docente(id),
+ creado TIMESTAMP NOT NULL
+);
+
+CREATE TABLE caso_de_prueba (
+ id INTEGER PRIMARY KEY,
+ enunciado_id INT CONSTRAINT enunciado_id_exists REFERENCES enunciado(id),
+ nombre VARCHAR(40) NOT NULL,
+ parametros TEXT NOT NULL,
+ retorno INT,
+ tiempo_cpu FLOAT,
+ descripcion VARCHAR(255)
+);
+CREATE UNIQUE INDEX caso_de_prueba_pk ON caso_de_prueba (enunciado_id, nombre);
+
+CREATE TABLE ejercicio (
+ id INTEGER PRIMARY KEY,
+ curso_id INT NOT NULL CONSTRAINT curso_id_exists REFERENCES curso(id),
+ numero INT NOT NULL,
+ enunciado_id INT NOT NULL CONSTRAINT enunciado_id_exists REFERENCES enunciado(id),
+ grupal TINYINT NOT NULL
+);
+CREATE UNIQUE INDEX ejercicio_pk ON ejercicio (curso_id, numero);
+
+CREATE TABLE instancia_de_entrega (
+ id INTEGER PRIMARY KEY,
+ ejercicio_id INT NOT NULL CONSTRAINT ejercicio_id_exists REFERENCES ejercicio(id),
+ numero INT NOT NULL,
+ inicio TIMESTAMP NOT NULL,
+ fin TIMESTAMP NOT NULL,
+ procesada TINYINT NOT NULL,
+ observaciones TEXT,
+ activo TINYINT NOT NULL
+);
+
+CREATE TABLE instancia_tarea (
+ instancia_id INTEGER NOT NULL CONSTRAINT instancia_id_exists REFERENCES instancia_de_entrega(id),
+ tarea_id INTEGER NOT NULL CONSTRAINT tarea_id_exists REFERENCES tarea(id),
+ orden INT,
+ PRIMARY KEY (instancia_id, tarea_id)
+);
+
+CREATE TABLE docente_inscripto (
+ id INTEGER PRIMARY KEY,
+ curso_id INT NOT NULL CONSTRAINT curso_id_exists REFERENCES curso(id),
+ docente_id INT NOT NULL CONSTRAINT docente_id_exists REFERENCES docente(id),
+ corrige TINYINT NOT NULL,
+ observaciones TEXT
+);
+CREATE UNIQUE INDEX docente_inscripto_pk ON docente_inscripto (curso_id, docente_id);
+
+CREATE TABLE entregador (
+ id INTEGER PRIMARY KEY,
+ child_name VARCHAR(255),
+ nota DECIMAL(3, 1),
+ nota_cursada DECIMAL(3, 1),
+ observaciones TEXT,
+ activo TINYINT NOT NULL
+);
+
+CREATE TABLE grupo (
+ id INTEGER PRIMARY KEY,
+ curso_id INT NOT NULL CONSTRAINT curso_id_exists REFERENCES curso(id),
+ nombre VARCHAR(20) NOT NULL,
+ responsable_id INT CONSTRAINT responsable_id_exists REFERENCES alumno_inscripto(id)
+);
+
+CREATE TABLE alumno_inscripto (
+ id INTEGER PRIMARY KEY,
+ curso_id INT NOT NULL CONSTRAINT curso_id_exists REFERENCES curso(id),
+ alumno_id INT NOT NULL CONSTRAINT alumno_id_exists REFERENCES alumno(id),
+ condicional TINYINT NOT NULL,
+ tutor_id INT CONSTRAINT tutor_id_exists REFERENCES docente_inscripto(id)
+);
+CREATE UNIQUE INDEX alumno_inscripto_pk ON alumno_inscripto (curso_id, alumno_id);
+
+CREATE TABLE tutor (
+ id INTEGER PRIMARY KEY,
+ grupo_id INT NOT NULL CONSTRAINT grupo_id_exists REFERENCES grupo(id),
+ docente_id INT NOT NULL CONSTRAINT docente_id_exists REFERENCES docente_inscripto(id),
+ alta TIMESTAMP NOT NULL,
+ baja TIMESTAMP
+);
+CREATE UNIQUE INDEX tutor_pk ON tutor (grupo_id, docente_id);
+
+CREATE TABLE miembro (
+ id INTEGER PRIMARY KEY,
+ grupo_id INT NOT NULL CONSTRAINT grupo_id_exists REFERENCES grupo(id),
+ alumno_id INT NOT NULL CONSTRAINT alumno_id_exists REFERENCES alumno_inscripto(id),
+ nota DECIMAL(3, 1),
+ alta TIMESTAMP NOT NULL,
+ baja TIMESTAMP
+);
+CREATE UNIQUE INDEX miembro_pk ON miembro (grupo_id, alumno_id);
+
+CREATE TABLE entrega (
+ id INTEGER PRIMARY KEY,
+ instancia_id INT NOT NULL CONSTRAINT instancia_id_exists REFERENCES instancia_de_entrega(id),
+ entregador_id INT CONSTRAINT entregador_id_exists REFERENCES entregador(id),
+ fecha TIMESTAMP NOT NULL,
+ correcta TINYINT NOT NULL,
+ observaciones TEXT
+);
+CREATE UNIQUE INDEX entrega_pk ON entrega (instancia_id, entregador_id, fecha);
+
+CREATE TABLE correccion (
+ id INTEGER PRIMARY KEY,
+ instancia_id INT NOT NULL CONSTRAINT instancia_id_exists REFERENCES instancia_de_entrega(id),
+ entregador_id INT NOT NULL CONSTRAINT entregador_id_exists REFERENCES entregador(id),
+ entrega_id INT NOT NULL CONSTRAINT entrega_id_exists REFERENCES entrega(id),
+ corrector_id INT NOT NULL CONSTRAINT corrector_id_exists REFERENCES docente_inscripto(id),
+ asignado TIMESTAMP NOT NULL,
+ corregido TIMESTAMP,
+ nota DECIMAL(3, 1),
+ observaciones TEXT
+);
+CREATE UNIQUE INDEX correccion_pk ON correccion (instancia_id, entregador_id);
+
+CREATE TABLE tarea_ejecutada (
+ id INTEGER PRIMARY KEY,
+ child_name VARCHAR(255),
+ tarea_id INT NOT NULL CONSTRAINT tarea_id_exists REFERENCES tarea(id),
+ entrega_id INT NOT NULL CONSTRAINT entrega_id_exists REFERENCES entrega(id),
+ inicio TIMESTAMP NOT NULL,
+ fin TIMESTAMP,
+ exito INT,
+ observaciones TEXT
+);
+CREATE UNIQUE INDEX tarea_ejecutada_pk ON tarea_ejecutada (tarea_id, entrega_id);
+
+CREATE TABLE prueba (
+ id INTEGER PRIMARY KEY,
+ tarea_ejecutada_id INT NOT NULL CONSTRAINT tarea_ejecutada_id_exists REFERENCES tarea_ejecutada(id),
+ caso_de_prueba_id INT NOT NULL CONSTRAINT caso_de_prueba_id_exists REFERENCES caso_de_prueba(id),
+ inicio TIMESTAMP NOT NULL,
+ fin TIMESTAMP,
+ pasada INT,
+ observaciones TEXT
+);
+CREATE UNIQUE INDEX prueba_pk ON prueba (tarea_ejecutada_id, caso_de_prueba_id);
+
+CREATE TABLE visita (
+ id INTEGER PRIMARY KEY,
+ visit_key VARCHAR(40) NOT NULL UNIQUE,
+ created TIMESTAMP NOT NULL,
+ expiry TIMESTAMP
+);
+
+CREATE TABLE visita_usuario (
+ id INTEGER PRIMARY KEY,
+ visit_key VARCHAR(40) NOT NULL UNIQUE,
+ user_id INT CONSTRAINT usuario_id_exists REFERENCES usuario(id)
+);
+
+CREATE TABLE rol (
+ id INTEGER PRIMARY KEY,
+ nombre VARCHAR(255) NOT NULL UNIQUE,
+ descripcion VARCHAR(255),
+ creado TIMESTAMP NOT NULL,
+ permisos TEXT NOT NULL
+);
+
+CREATE TABLE rol_usuario (
+ rol_id INT NOT NULL,
+ usuario_id INT NOT NULL
+);
+
--- /dev/null
+c = Curso(anio=2007, cuatrimestre=1, numero=1, descripcion=u'Martes')
+
+d = Docente(usuario=u'luca', nombre='Leandro Lucarella')
+d.password = 'luca'
+
+a = Alumno(usuario='77891', nombre='Tito Puente')
+a.password = '77891'
+
+r = Rol(nombre='admin', permisos=(entregar_tp, admin))
+d.addRol(r)
+
+r = Rol(nombre='alumno', permisos=(entregar_tp,))
+a.addRol(r)
+
+t1 = Tarea(nombre='compilar')
+t2 = Tarea(nombre='probar')
+t2.dependencias = (t1,)
+t3 = Tarea(nombre=u'configurar detector de copias')
+t4 = Tarea(nombre=u'detectar copias')
+t4.dependencias = (t3, t2)
+
+e1 = Enunciado(nombre=u'Un enunciado', autor=d, descripcion=u'Ejercicio reeee jodido')
+e2 = Enunciado(nombre=u'Otro enunciado', autor=d, descripcion=u'Ejercicio facilongo')
+e3 = Enunciado(nombre=u'Más enunciados', descripcion=u'Ejercicio anónimo')
+
+cp1 = e1.add_caso_de_prueba(u'Sin parámetros', retorno=0, descripcion=u'Un caso')
+cp2 = e1.add_caso_de_prueba(u'2 parámetross', retorno=0, parametros=('--test', '-c'))
+
+ej1 = c.add_ejercicio(1, e1, grupal=True)
+ej2 = c.add_ejercicio(2, e2)
+
+ide = ej1.add_instancia(1, datetime(2007, 1, 25), datetime(2007, 1, 31, 20),
+ observaciones='Entrega fea', activo=False)
+
+di = c.add_docente(d, corrige=True, observaciones=u'Tipo Pulenta')
+
+ai1 = c.add_alumno(a)
+ai2 = c.add_alumno(Alumno(usuario='83525', nombre=u'Pepe Lui'), tutor=di)
+
+g1 = c.add_grupo(5)
+g2 = c.add_grupo(8, responsable=ai2)
+
+g2.add_alumno(ai1)
+g2.add_alumno(ai2)
+g2.add_docente(di)
+
+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)
+
+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)
+
+__connection__.hub.commit()
--- /dev/null
+[global]
+# The settings in this file should not vary depending on the deployment
+# environment. dev.cfg and prod.cfg are the locations for
+# the different deployment settings. Settings in this file will
+# be overridden by settings in those other files.
+
+# The commented out values below are the defaults
+
+# VIEW
+
+# which view (template engine) to use if one is not specified in the
+# template name
+# tg.defaultview = "kid"
+
+# The following kid settings determine the settings used by the kid serializer.
+
+# One of (html|html-strict|xhtml|xhtml-strict|xml|json)
+# kid.outputformat="html"
+
+# kid.encoding="utf-8"
+
+# The sitetemplate is used for overall styling of a site that
+# includes multiple TurboGears applications
+# tg.sitetemplate="<packagename.templates.templatename>"
+
+# Allow every exposed function to be called as json,
+# tg.allow_json = False
+
+# List of Widgets to include on every page.
+# for exemple ['turbogears.mochikit']
+# tg.include_widgets = []
+
+# Set to True if the scheduler should be started
+# tg.scheduler = False
+
+# VISIT TRACKING
+# Each visit to your application will be assigned a unique visit ID tracked via
+# a cookie sent to the visitor's browser.
+# --------------
+
+# Enable Visit tracking
+visit.on = True
+
+# Number of minutes a visit may be idle before it expires.
+# visit.timeout=20
+
+# The name of the cookie to transmit to the visitor's browser.
+# visit.cookie.name="tg-visit"
+
+# Domain name to specify when setting the cookie (must begin with . according to
+# RFC 2109). The default (None) should work for most cases and will default to
+# the machine to which the request was made. NOTE: localhost is NEVER a valid
+# value and will NOT WORK.
+# visit.cookie.domain=None
+
+# Specific path for the cookie
+# visit.cookie.path="/"
+
+# The name of the VisitManager plugin to use for visitor tracking.
+visit.manager = "sqlobject"
+
+# Database class to use for visit tracking
+visit.soprovider.model = "sercom.model.Visita"
+
+# IDENTITY
+# General configuration of the TurboGears Identity management module
+# --------
+
+# Switch to turn on or off the Identity management module
+identity.on = True
+
+# [REQUIRED] URL to which CherryPy will internally redirect when an access
+# control check fails. If Identity management is turned on, a value for this
+# option must be specified.
+identity.failure_url = "/login"
+
+# identity.provider='sqlobject'
+
+# The names of the fields on the login form containing the visitor's user ID
+# and password. In addition, the submit button is specified simply so its
+# existence may be stripped out prior to passing the form data to the target
+# controller.
+identity.form.user_name = "login_user"
+identity.form.password = "login_password"
+identity.form.submit = "login_submit"
+
+# What sources should the identity provider consider when determining the
+# identity associated with a request? Comma separated list of identity sources.
+# Valid sources: form, visit, http_auth
+# identity.source="form,http_auth,visit"
+
+# SqlObjectIdentityProvider
+# Configuration options for the default IdentityProvider
+# -------------------------
+
+# The classes you wish to use for your Identity model. Remember to not use reserved
+# SQL keywords for class names (at least unless you specify a different table
+# name using sqlmeta).
+identity.soprovider.model.user = "sercom.model.Usuario"
+identity.soprovider.model.group = "sercom.model.Rol"
+identity.soprovider.model.permission = "sercom.model.Permiso"
+identity.soprovider.model.visit = "sercom.model.VisitaUsuario"
+
+# The password encryption algorithm used when comparing passwords against what's
+# stored in the database. Valid values are 'md5' or 'sha1'. If you do not
+# specify an encryption algorithm, passwords are expected to be clear text.
+# 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
+# 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"]
+
+[/static]
+static_filter.on = True
+static_filter.dir = "%(top_level_dir)s/static"
+
+[/favicon.ico]
+static_filter.on = True
+static_filter.file = "%(top_level_dir)s/static/images/favicon.ico"
+
--- /dev/null
+# LOGGING
+# Logging is often deployment specific, but some handlers and
+# formatters can be defined here.
+
+[logging]
+[[formatters]]
+[[[message_only]]]
+format='*(message)s'
+
+[[[full_content]]]
+format='*(asctime)s *(name)s *(levelname)s *(message)s'
+
+[[handlers]]
+[[[debug_out]]]
+class='StreamHandler'
+level='DEBUG'
+args='(sys.stdout,)'
+formatter='full_content'
+
+[[[access_out]]]
+class='StreamHandler'
+level='INFO'
+args='(sys.stdout,)'
+formatter='message_only'
+
+[[[error_out]]]
+class='StreamHandler'
+level='ERROR'
+args='(sys.stdout,)'
--- /dev/null
+# vim: set et sw=4 sts=4 encoding=utf-8 :
+
+from turbogears import controllers, expose
+from turbogears import widgets as w, validators
+from turbogears import identity, redirect
+from cherrypy import request, response
+from model import *
+# from sercom import json
+
+import logging
+log = logging.getLogger("sercom.controllers")
+
+class Root(controllers.RootController):
+
+ @expose(template='.templates.welcome')
+ @identity.require(identity.has_permission('entregar'))
+ def index(self):
+ import time
+ log.debug('Happy TurboGears Controller Responding For Duty')
+ return dict(now=time.ctime())
+
+ @expose(template='.templates.login')
+ def login(self, forward_url=None, previous_url=None, tg_errors=None, *args,
+ **kw):
+
+ if tg_errors:
+ flash(_(u'Hubo un error en el formulario!'))
+
+ if not identity.current.anonymous \
+ and identity.was_login_attempted() \
+ and not identity.get_identity_errors():
+ raise redirect(forward_url)
+
+ forward_url = None
+ previous_url = request.path
+
+ if identity.was_login_attempted():
+ msg = _(u'Las credenciales proporcionadas no son correctas o no '
+ 'le dan acceso al recurso solicitado.')
+ elif identity.get_identity_errors():
+ msg = _(u'Debe proveer sus credenciales antes de acceder a este '
+ 'recurso.')
+ else:
+ msg = _(u'Por favor ingrese.')
+ forward_url = request.headers.get('Referer', '/')
+
+ fields = [
+ w.TextField(name='login_user', label=_(u'Usuario'),
+ validator=validators.NotEmpty()),
+ w.PasswordField(name='login_password', label=_(u'Contraseña'),
+ validator=validators.NotEmpty())
+ ]
+ if forward_url:
+ fields.append(w.HiddenField(name='forward_url'))
+ fields.extend([w.HiddenField(name=name) for name in request.params
+ if name not in ('login_user', 'login_password', 'login_submit',
+ 'forward_url')])
+
+ submit = w.SubmitButton(name='login_submit')
+
+ login_form = w.TableForm(fields=fields, action=previous_url,
+ submit_text=_(u'Ingresar'), submit=submit)
+
+ values = dict(forward_url=forward_url)
+ values.update(request.params)
+
+ response.status=403
+ return dict(login_form=login_form, form_data=values, message=msg,
+ logging_in=True)
+
+ @expose()
+ def logout(self):
+ identity.current.logout()
+ raise redirect('/')
+
--- /dev/null
+# A JSON-based API(view) for your app.
+# Most rules would look like:
+# @jsonify.when("isinstance(obj, YourClass)")
+# def jsonify_yourclass(obj):
+# return [obj.val1, obj.val2]
+# @jsonify can convert your objects to following types:
+# lists, dicts, numbers and strings
+
+from turbojson.jsonify import jsonify
+
+from turbojson.jsonify import jsonify_sqlobject
+from sercom.model import User, Group, Permission
+
+@jsonify.when('isinstance(obj, Group)')
+def jsonify_group(obj):
+ result = jsonify_sqlobject( obj )
+ result["users"] = [u.user_name for u in obj.users]
+ result["permissions"] = [p.permission_name for p in obj.permissions]
+ return result
+
+@jsonify.when('isinstance(obj, User)')
+def jsonify_user(obj):
+ result = jsonify_sqlobject( obj )
+ del result['password']
+ result["groups"] = [g.group_name for g in obj.groups]
+ result["permissions"] = [p.permission_name for p in obj.permissions]
+ return result
+
+@jsonify.when('isinstance(obj, Permission)')
+def jsonify_permission(obj):
+ result = jsonify_sqlobject( obj )
+ result["groups"] = [g.group_name for g in obj.groups]
+ return result
--- /dev/null
+# vim: set et sw=4 sts=4 encoding=utf-8 :
+
+from datetime import datetime
+from turbogears.database import PackageHub
+from sqlobject import *
+from sqlobject.sqlbuilder import *
+from sqlobject.inheritance import InheritableSQLObject
+from sqlobject.col import PickleValidator
+from turbogears import identity
+
+hub = PackageHub("sercom")
+__connection__ = hub
+
+__all__ = ('Curso', 'Usuario', 'Docente', 'Alumno', 'Tarea', 'CasoDePrueba')
+
+#{{{ Custom Columns
+
+class TupleValidator(PickleValidator):
+ """
+ Validator for tuple types. A tuple type is simply a pickle type
+ that validates that the represented type is a tuple.
+ """
+
+ def to_python(self, value, state):
+ value = super(TupleValidator, self).to_python(value, state)
+ if value is None:
+ return None
+ if isinstance(value, tuple):
+ return value
+ raise validators.Invalid("expected a tuple in the TupleCol '%s', got %s %r instead" % \
+ (self.name, type(value), value), value, state)
+
+ def from_python(self, value, state):
+ if value is None:
+ return None
+ if not isinstance(value, tuple):
+ raise validators.Invalid("expected a tuple in the TupleCol '%s', got %s %r instead" % \
+ (self.name, type(value), value), value, state)
+ return super(TupleValidator, self).from_python(value, state)
+
+class SOTupleCol(SOPickleCol):
+
+ def __init__(self, **kw):
+ super(SOTupleCol, self).__init__(**kw)
+
+ def createValidators(self):
+ return [TupleValidator(name=self.name)] + \
+ super(SOPickleCol, self).createValidators()
+
+class TupleCol(PickleCol):
+ baseClass = SOTupleCol
+
+#}}}
+
+#{{{ 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
+
+dependencia_t = table.dependencia
+
+#}}}
+
+#{{{ Clases
+
+def srepr(obj): #{{{
+ if obj is not None:
+ return obj.shortrepr()
+ return obj
+#}}}
+
+class ByObject(object): #{{{
+ @classmethod
+ def by(cls, **kw):
+ try:
+ return cls.selectBy(**kw)[0]
+ except IndexError:
+ raise SQLObjectNotFound, "The object %s with columns %s does not exist" % (cls.__name__, kw)
+#}}}
+
+class Curso(SQLObject, ByObject): #{{{
+ # Clave
+ anio = IntCol(notNone=True)
+ cuatrimestre = IntCol(notNone=True)
+ numero = IntCol(notNone=True)
+ pk = DatabaseIndex(anio, cuatrimestre, numero, unique=True)
+ # Campos
+ descripcion = UnicodeCol(length=255, default=None)
+ # Joins
+ docentes = MultipleJoin('DocenteInscripto')
+ alumnos = MultipleJoin('AlumnoInscripto')
+ grupos = MultipleJoin('Grupo')
+ ejercicios = MultipleJoin('Ejercicio', orderBy='numero')
+
+ def add_docente(self, docente, **opts):
+ return DocenteInscripto(cursoID=self.id, docenteID=docente.id, **opts)
+
+ def add_alumno(self, alumno, tutor=None, **opts):
+ tutor_id = tutor and tutor.id
+ return AlumnoInscripto(cursoID=self.id, alumnoID=alumno.id,
+ tutorID=tutor_id, **opts)
+
+ def add_grupo(self, nombre, responsable=None, **opts):
+ resp_id = responsable and responsable.id
+ return Grupo(cursoID=self.id, nombre=unicode(nombre),
+ responsableID=resp_id, **opts)
+
+ def add_ejercicio(self, numero, enunciado, **opts):
+ return Ejercicio(cursoID=self.id, numero=numero,
+ enunciadoID=enunciado.id, **opts)
+
+ def __repr__(self):
+ return 'Curso(id=%s, anio=%s, cuatrimestre=%s, numero=%s, ' \
+ 'descripcion=%s)' \
+ % (self.id, self.anio, self.cuatrimestre, self.numero,
+ self.descripcion)
+
+ def shortrepr(self):
+ return '%s.%s.%s' \
+ % (self.anio, self.cuatrimestre, self.numero, self.descripcion)
+#}}}
+
+class Usuario(InheritableSQLObject, ByObject): #{{{
+ # Clave (para docentes puede ser un nombre de usuario arbitrario)
+ usuario = UnicodeCol(length=10, alternateID=True)
+ # Campos
+ contrasenia = UnicodeCol(length=255, default=None)
+ nombre = UnicodeCol(length=255, notNone=True)
+ email = UnicodeCol(length=255, default=None)
+ telefono = UnicodeCol(length=255, default=None)
+ creado = DateTimeCol(notNone=True, default=DateTimeCol.now)
+ observaciones = UnicodeCol(default=None)
+ activo = BoolCol(notNone=True, default=True)
+ # Joins
+ grupos = RelatedJoin('Grupo')
+ roles = RelatedJoin('Rol')
+
+ def _get_user_name(self): # para identity
+ return self.usuario
+
+ @classmethod
+ 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)
+ return user
+
+ def _get_groups(self): # para identity
+ return self.roles
+
+ def _get_permissions(self): # para identity
+ perms = set()
+ for g in self.groups:
+ perms.update(g.permisos)
+ return perms
+
+ def _set_password(self, cleartext_password): # para identity
+ self.contrasenia = identity.encrypt_password(cleartext_password)
+
+ def _get_password(self): # para identity
+ return self.contrasenia
+
+ def __repr__(self):
+ raise NotImplementedError, 'Clase abstracta!'
+
+ def shortrepr(self):
+ return '%s (%s)' % (self.usuario, self.nombre)
+#}}}
+
+class Docente(Usuario): #{{{
+ _inheritable = False
+ # Campos
+ nombrado = BoolCol(notNone=True, default=True)
+ # Joins
+ enunciados = MultipleJoin('Enunciado')
+ inscripciones = MultipleJoin('DocenteInscripto')
+
+ def add_entrega(self, instancia, **opts):
+ return Entrega(instanciaID=instancia.id, **opts)
+
+ def add_enunciado(self, nombre, **opts):
+ return Enunciado(autorID=self.id, nombre=nombre, **opts)
+
+ def __repr__(self):
+ return 'Docente(id=%s, usuario=%s, nombre=%s, password=%s, email=%s, ' \
+ 'telefono=%s, activo=%s, creado=%s, observaciones=%s)' \
+ % (self.id, self.usuario, self.nombre, self.password,
+ self.email, self.telefono, self.activo, self.creado,
+ self.observaciones)
+#}}}
+
+class Alumno(Usuario): #{{{
+ _inheritable = False
+ # Campos
+ nota = DecimalCol(size=3, precision=1, default=None)
+ # Joins
+ inscripciones = MultipleJoin('AlumnoInscripto')
+
+ def _get_padron(self): # alias para poder referirse al alumno por padron
+ return self.usuario
+
+ def _set_padron(self, padron):
+ self.usuario = padron
+
+ def __repr__(self):
+ return 'Alumno(id=%s, padron=%s, nombre=%s, password=%s, email=%s, ' \
+ 'telefono=%s, activo=%s, creado=%s, observaciones=%s)' \
+ % (self.id, self.padron, self.nombre, self.password, self.email,
+ self.telefono, self.activo, self.creado, self.observaciones)
+#}}}
+
+class Tarea(InheritableSQLObject, ByObject): #{{{
+ # Clave
+ nombre = UnicodeCol(length=30, alternateID=True)
+ # Campos
+ descripcion = UnicodeCol(length=255, default=None)
+ # Joins
+
+ 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 __repr__(self):
+ return 'Tarea(id=%s, nombre=%s, descripcion=%s)' \
+ % (self.id, self.nombre, self.descripcion)
+
+ def shortrepr(self):
+ return self.nombre
+#}}}
+
+class Enunciado(SQLObject, ByObject): #{{{
+ # Clave
+ nombre = UnicodeCol(length=60, alternateID=True)
+ # Campos
+ descripcion = UnicodeCol(length=255, default=None)
+ autor = ForeignKey('Docente', default=None, dbName='docente_id')
+ creado = DateTimeCol(notNone=True, default=DateTimeCol.now)
+ # Joins
+ ejercicios = MultipleJoin('Ejercicio')
+ casos_de_prueba = MultipleJoin('CasoDePrueba')
+
+ def add_caso_de_prueba(self, nombre, **opts):
+ return CasoDePrueba(enunciadoID=self.id, nombre=nombre, **opts)
+
+ def __repr__(self):
+ return 'Enunciado(id=%s, autor=%s, nombre=%s, descripcion=%s, ' \
+ 'creado=%s)' \
+ % (self.id, srepr(self.autor), self.nombre, self.descripcion, \
+ self.creado)
+
+ def shortrepr(self):
+ return self.nombre
+#}}}
+
+class CasoDePrueba(SQLObject): #{{{
+ # Clave
+ enunciado = ForeignKey('Enunciado')
+ 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 = TupleCol(notNone=True, default=())
+ retorno = IntCol(default=None)
+ tiempo_cpu = FloatCol(default=None)
+ descripcion = UnicodeCol(length=255, default=None)
+ # Joins
+ pruebas = MultipleJoin('Prueba')
+
+ def __repr__(self):
+ return 'CasoDePrueba(enunciado=%s, nombre=%s, parametros=%s, ' \
+ 'retorno=%s, tiempo_cpu=%s, descripcion=%s)' \
+ % (self.enunciado.shortrepr(), self.nombre, self.parametros,
+ self.retorno, self.tiempo_cpu, self.descripcion)
+
+ def shortrepr(self):
+ return '%s:%s' % (self.enunciado.shortrepr(), self.nombre)
+#}}}
+
+class Ejercicio(SQLObject, ByObject): #{{{
+ # Clave
+ curso = ForeignKey('Curso', notNone=True)
+ numero = IntCol(notNone=True)
+ pk = DatabaseIndex(curso, numero, unique=True)
+ # Campos
+ enunciado = ForeignKey('Enunciado', notNone=True)
+ grupal = BoolCol(notNone=True, default=False)
+ # Joins
+ instancias = MultipleJoin('InstanciaDeEntrega')
+
+ def add_instancia(self, numero, inicio, fin, **opts):
+ return InstanciaDeEntrega(ejercicioID=self.id, numero=numero,
+ inicio=inicio, fin=fin, **opts)
+
+ def __repr__(self):
+ return 'Ejercicio(id=%s, curso=%s, numero=%s, enunciado=%s, ' \
+ 'grupal=%s)' \
+ % (self.id, self.curso.shortrepr(), self.numero,
+ self.enunciado.shortrepr(), self.grupal)
+
+ def shortrepr(self):
+ return '(%s, %s, %s)' \
+ % (self.curso.shortrepr(), self.nombre, \
+ self.enunciado.shortrepr())
+#}}}
+
+class InstanciaDeEntrega(SQLObject, ByObject): #{{{
+ # Clave
+ ejercicio = ForeignKey('Ejercicio', notNone=True)
+ numero = IntCol(notNone=True)
+ # Campos
+ inicio = DateTimeCol(notNone=True)
+ fin = DateTimeCol(notNone=True)
+ procesada = BoolCol(notNone=True, default=False)
+ observaciones = UnicodeCol(default=None)
+ activo = BoolCol(notNone=True, default=True)
+ # Joins
+ entregas = MultipleJoin('Entrega', joinColumn='instancia_id')
+ correcciones = MultipleJoin('Correccion', joinColumn='instancia_id')
+ casos_de_prueba = RelatedJoin('CasoDePrueba') # TODO CasoInstancia -> private
+
+ 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
+ ),
+ 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)' \
+ % (self.id, self.numero, self.inicio, self.fin,
+ self.procesada, self.observaciones, self.activo)
+
+ def shortrepr(self):
+ return self.numero
+#}}}
+
+class DocenteInscripto(SQLObject, ByObject): #{{{
+ # Clave
+ curso = ForeignKey('Curso', notNone=True)
+ docente = ForeignKey('Docente', notNone=True)
+ pk = DatabaseIndex(curso, docente, unique=True)
+ # Campos
+ corrige = BoolCol(notNone=True, default=True)
+ observaciones = UnicodeCol(default=None)
+ # Joins
+ alumnos = MultipleJoin('AlumnoInscripto', joinColumn='tutor_id')
+ tutorias = MultipleJoin('Tutor', joinColumn='docente_id')
+ entregas = MultipleJoin('Entrega', joinColumn='instancia_id')
+ correcciones = MultipleJoin('Correccion', joinColumn='corrector_id')
+
+ def add_correccion(self, entrega, **opts):
+ return Correccion(correctorID=self.id, instanciaID=entrega.instancia.id,
+ entregadorID=entrega.entregador.id, entregaID=entrega.id, **opts)
+
+ def __repr__(self):
+ return 'DocenteInscripto(id=%s, docente=%s, corrige=%s, ' \
+ 'observaciones=%s' \
+ % (self.id, self.docente.shortrepr(), self.corrige,
+ self.observaciones)
+
+ def shortrepr(self):
+ return self.docente.shortrepr()
+#}}}
+
+class Entregador(InheritableSQLObject, ByObject): #{{{
+ # Campos
+ nota = DecimalCol(size=3, precision=1, default=None)
+ nota_cursada = DecimalCol(size=3, precision=1, default=None)
+ observaciones = UnicodeCol(default=None)
+ activo = BoolCol(notNone=True, default=True)
+ # Joins
+ entregas = MultipleJoin('Entrega')
+ correcciones = MultipleJoin('Correccion')
+
+ def add_entrega(self, instancia, **opts):
+ return Entrega(entregadorID=self.id, instanciaID=instancia.id, **opts)
+
+ def __repr__(self):
+ raise NotImplementedError, 'Clase abstracta!'
+#}}}
+
+class Grupo(Entregador): #{{{
+ _inheritable = False
+ # Clave
+ curso = ForeignKey('Curso', notNone=True)
+ nombre = UnicodeCol(length=20, notNone=True)
+ # Campos
+ responsable = ForeignKey('AlumnoInscripto', default=None)
+ # Joins
+ miembros = MultipleJoin('Miembro')
+ tutores = MultipleJoin('Tutor')
+
+ def add_alumno(self, alumno, **opts):
+ return Miembro(grupoID=self.id, alumnoID=alumno.id, **opts)
+
+ def add_docente(self, docente, **opts):
+ return Tutor(grupoID=self.id, docenteID=docente.id, **opts)
+
+ def __repr__(self):
+ return 'Grupo(id=%s, nombre=%s, responsable=%s, nota=%s, ' \
+ 'nota_cursada=%s, observaciones=%s, activo=%s)' \
+ % (self.id, self.nombre, srepr(self.responsable), self.nota,
+ self.nota_cursada, self.observaciones, self.activo)
+
+ def shortrepr(self):
+ return 'grupo:' + self.nombre
+#}}}
+
+class AlumnoInscripto(Entregador): #{{{
+ _inheritable = False
+ # Clave
+ curso = ForeignKey('Curso', notNone=True)
+ alumno = ForeignKey('Alumno', notNone=True)
+ pk = DatabaseIndex(curso, alumno, unique=True)
+ # Campos
+ condicional = BoolCol(notNone=True, default=False)
+ tutor = ForeignKey('DocenteInscripto', default=None)
+ # Joins
+ responsabilidades = MultipleJoin('Grupo', joinColumn='responsable_id')
+ membresias = MultipleJoin('Miembro', joinColumn='alumno_id')
+ entregas = MultipleJoin('Entrega', joinColumn='alumno_id')
+ correcciones = MultipleJoin('Correccion', joinColumn='alumno_id')
+
+ def __repr__(self):
+ return 'AlumnoInscripto(id=%s, alumno=%s, condicional=%s, nota=%s, ' \
+ 'nota_cursada=%s, tutor=%s, observaciones=%s, activo=%s)' \
+ % (self.id, self.alumno.shortrepr(), self.condicional,
+ self.nota, self.nota_cursada, srepr(self.tutor),
+ self.observaciones, self.activo)
+
+ def shortrepr(self):
+ return self.alumno.shortrepr()
+#}}}
+
+class Tutor(SQLObject, ByObject): #{{{
+ # Clave
+ grupo = ForeignKey('Grupo', notNone=True)
+ docente = ForeignKey('DocenteInscripto', notNone=True)
+ pk = DatabaseIndex(grupo, docente, unique=True)
+ # Campos
+ alta = DateTimeCol(notNone=True, default=DateTimeCol.now)
+ baja = DateTimeCol(default=None)
+
+ def __repr__(self):
+ return 'Tutor(docente=%s, grupo=%s, alta=%s, baja=%s)' \
+ % (self.docente.shortrepr(), self.grupo.shortrepr(),
+ self.alta, self.baja)
+
+ def shortrepr(self):
+ return '%s-%s' % (self.docente.shortrepr(), self.grupo.shortrepr())
+#}}}
+
+class Miembro(SQLObject, ByObject): #{{{
+ # Clave
+ grupo = ForeignKey('Grupo', notNone=True)
+ alumno = ForeignKey('AlumnoInscripto', notNone=True)
+ pk = DatabaseIndex(grupo, alumno, unique=True)
+ # Campos
+ nota = DecimalCol(size=3, precision=1, default=None)
+ alta = DateTimeCol(notNone=True, default=DateTimeCol.now)
+ baja = DateTimeCol(default=None)
+
+ def __repr__(self):
+ return 'Miembro(alumno=%s, grupo=%s, nota=%s, alta=%s, baja=%s)' \
+ % (self.alumno.shortrepr(), self.grupo.shortrepr(),
+ self.nota, self.alta, self.baja)
+
+ def shortrepr(self):
+ return '%s-%s' % (self.alumno.shortrepr(), self.grupo.shortrepr())
+#}}}
+
+class Entrega(SQLObject, ByObject): #{{{
+ # Clave
+ instancia = ForeignKey('InstanciaDeEntrega', notNone=True)
+ entregador = ForeignKey('Entregador', default=None) # 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)
+ # Joins
+ tareas = MultipleJoin('TareaEjecutada')
+ # Para generar código
+ codigo_dict = r'0123456789abcdefghijklmnopqrstuvwxyz_.,*@#+'
+ codigo_format = r'%m%d%H%M%S'
+
+ def add_tarea_ejecutada(self, tarea, **opts):
+ return TareaEjecutada(entregaID=self.id, tareaID=tarea.id, **opts)
+
+ def _get_codigo(self):
+ if not hasattr(self, '_codigo'): # cache
+ n = long(self.fecha.strftime(Entrega.codigo_format))
+ d = Entrega.codigo_dict
+ l = len(d)
+ res = ''
+ while n:
+ res += d[n % l]
+ n /= l
+ self._codigo = res
+ return self._codigo
+
+ def _set_fecha(self, fecha):
+ self._SO_set_fecha(fecha)
+ if hasattr(self, '_codigo'): del self._codigo # bye, bye cache!
+
+ def __repr__(self):
+ return 'Entrega(instancia=%s, entregador=%s, codigo=%s, fecha=%s, ' \
+ 'correcta=%s, observaciones=%s)' \
+ % (self.instancia.shortrepr(), srepr(self.entregador),
+ self.codigo, self.fecha, self.correcta, self.observaciones)
+
+ def shortrepr(self):
+ return '%s-%s-%s' % (self.instancia.shortrepr(), srepr(self.entregador),
+ self.codigo)
+#}}}
+
+class Correccion(SQLObject, ByObject): #{{{
+ # Clave
+ instancia = ForeignKey('InstanciaDeEntrega', notNone=True)
+ entregador = ForeignKey('Entregador', notNone=True) # Docente no tiene
+ pk = DatabaseIndex(instancia, entregador, unique=True)
+ # Campos
+ entrega = ForeignKey('Entrega', notNone=True)
+ corrector = ForeignKey('DocenteInscripto', notNone=True)
+ asignado = DateTimeCol(notNone=True, default=DateTimeCol.now)
+ corregido = DateTimeCol(default=None)
+ nota = DecimalCol(size=3, precision=1, default=None)
+ observaciones = UnicodeCol(default=None)
+
+ def __repr__(self):
+ return 'Correccion(instancia=%s, entregador=%s, entrega=%s, ' \
+ 'corrector=%s, asignado=%s, corregido=%s, nota=%s, ' \
+ 'observaciones=%s)' \
+ % (self.instancia.shortrepr(), self.entregador.shortrepr(),
+ self.entrega.shortrepr(), self.corrector, self.asignado,
+ self.corregido, self.nota, self.observaciones)
+
+ def shortrepr(self):
+ return '%s,%s' % (self.entrega.shortrepr(), self.corrector.shortrepr())
+#}}}
+
+class TareaEjecutada(InheritableSQLObject, ByObject): #{{{
+ # Clave
+ tarea = ForeignKey('Tarea', notNone=True)
+ entrega = ForeignKey('Entrega', notNone=True)
+ pk = DatabaseIndex(tarea, entrega, unique=True)
+ # 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, **opts):
+ return Prueba(tarea_ejecutadaID=self.id,
+ caso_de_pruebaID=caso_de_prueba.id, **opts)
+
+ def __repr__(self):
+ return 'TareaEjecutada(tarea=%s, entrega=%s, inicio=%s, fin=%s, ' \
+ 'exito=%s, observaciones=%s)' \
+ % (self.tarea.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 Prueba(SQLObject): #{{{
+ # Clave
+ tarea_ejecutada = ForeignKey('TareaEjecutada', notNone=True)
+ caso_de_prueba = ForeignKey('CasoDePrueba', notNone=True)
+ pk = DatabaseIndex(tarea_ejecutada, 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)
+
+ 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)
+
+ def shortrepr(self):
+ return '%s:%s' % (self.tarea_ejecutada.shortrepr(),
+ self.caso_de_prueba.shortrerp())
+#}}}
+
+#{{{ Específico de Identity
+
+class Visita(SQLObject): #{{{
+ visit_key = StringCol(length=40, alternateID=True,
+ alternateMethodName="by_visit_key")
+ created = DateTimeCol(notNone=True, default=datetime.now)
+ expiry = DateTimeCol()
+
+ @classmethod
+ def lookup_visit(cls, visit_key):
+ try:
+ return cls.by_visit_key(visit_key)
+ except SQLObjectNotFound:
+ return None
+#}}}
+
+class VisitaUsuario(SQLObject): #{{{
+ # Clave
+ visit_key = StringCol(length=40, alternateID=True,
+ alternateMethodName="by_visit_key")
+ # Campos
+ user_id = IntCol() # Negrada de identity
+#}}}
+
+
+class Rol(SQLObject): #{{{
+ # Clave
+ nombre = UnicodeCol(length=255, alternateID=True,
+ alternateMethodName="by_group_name")
+ # Campos
+ descripcion = UnicodeCol(length=255, default=None)
+ creado = DateTimeCol(notNone=True, default=datetime.now)
+ permisos = TupleCol(notNone=True)
+ # Joins
+ usuarios = RelatedJoin('Usuario')
+#}}}
+
+# No es un SQLObject porque no tiene sentido agregar/sacar permisos, están
+# hardcodeados en el código
+class Permiso(object): #{{{
+ def __init__(self, nombre, descripcion):
+ self.nombre = nombre
+ self.descripcion = descripcion
+
+ @classmethod
+ def createTable(cls, ifNotExists): # para identity
+ pass
+
+ @property
+ def permission_name(self): # para identity
+ return self.nombre
+
+ def __repr__(self):
+ return self.nombre
+#}}}
+
+# TODO ejemplos
+entregar_tp = Permiso(u'entregar', u'Permite entregar trabajos prácticos')
+admin = Permiso(u'admin', u'Permite hacer ABMs arbitrarios')
+
+#}}} Identity
+
+#}}} Clases
+
--- /dev/null
+# Release information about sercom-so
+
+version = "1.0"
+
+# description = "Your plan to rule the world"
+# long_description = "More description about your plan"
+# author = "Your Name Here"
+# email = "YourEmail@YourDomain"
+# copyright = "Vintage 2006 - a good year indeed"
+
+# if it's open source, you might want to specify these
+# url = "http://yourcool.site/"
+# download_url = "http://yourcool.site/download"
+# license = "MIT"
--- /dev/null
+/*\r
+ * Quick mash-up of CSS for the TG quick start page.\r
+ */\r
+\r
+html, body, th, td {\r
+ color: black;\r
+ background-color: #ddd;\r
+ font: x-small "Lucida Grande", "Lucida Sans Unicode", geneva, verdana, sans-serif;\r
+ margin: 0;\r
+ padding: 0;\r
+}\r
+\r
+#header {\r
+ height: 80px;\r
+ width: 777px;\r
+ background: blue URL('../images/header_inner.png') no-repeat;\r
+ border-left: 1px solid #aaa;\r
+ border-right: 1px solid #aaa;\r
+ margin: 0 auto 0 auto;\r
+}\r
+\r
+a.link, a, a.active {\r
+ color: #369;\r
+}\r
+\r
+\r
+#main_content {\r
+ color: black;\r
+ font-size: 127%;\r
+ background-color: white;\r
+ width: 757px;\r
+ margin: 0 auto 0 auto;\r
+ border-left: 1px solid #aaa;\r
+ border-right: 1px solid #aaa;\r
+ padding: 10px;\r
+}\r
+\r
+#sidebar {\r
+ border: 1px solid #aaa;\r
+ background-color: #eee;\r
+ margin: 0.5em;\r
+ padding: 1em;\r
+ float: right;\r
+ width: 200px;\r
+ font-size: 88%;\r
+}\r
+\r
+#sidebar h2 {\r
+ margin-top: 0;\r
+}\r
+\r
+#sidebar ul {\r
+ margin-left: 1.5em;\r
+ padding-left: 0;\r
+}\r
+\r
+h1,h2,h3,h4,h5,h6,#getting_started_steps {\r
+ font-family: "Century Schoolbook L", Georgia, serif;\r
+ font-weight: bold;\r
+}\r
+\r
+h2 {\r
+ font-size: 150%;\r
+}\r
+\r
+#getting_started_steps a {\r
+ text-decoration: none;\r
+}\r
+\r
+#getting_started_steps a:hover {\r
+ text-decoration: underline;\r
+}\r
+\r
+#getting_started_steps li {\r
+ font-size: 80%;\r
+ margin-bottom: 0.5em;\r
+}\r
+\r
+#getting_started_steps h2 {\r
+ font-size: 120%;\r
+}\r
+\r
+#getting_started_steps p {\r
+ font: 100% "Lucida Grande", "Lucida Sans Unicode", geneva, verdana, sans-serif;\r
+}\r
+\r
+#footer {\r
+ border: 1px solid #aaa;\r
+ border-top: 0px none;\r
+ color: #999;\r
+ background-color: white;\r
+ padding: 10px;\r
+ font-size: 80%;\r
+ text-align: center;\r
+ width: 757px;\r
+ margin: 0 auto 1em auto;\r
+}\r
+\r
+.code {\r
+ font-family: monospace;\r
+}\r
+\r
+span.code {\r
+ font-weight: bold;\r
+ background: #eee;\r
+}\r
+\r
+#status_block {\r
+ margin: 0 auto 0.5em auto;\r
+ padding: 15px 10px 15px 55px;\r
+ background: #cec URL('../images/ok.png') left center no-repeat;\r
+ border: 1px solid #9c9;\r
+ width: 450px;\r
+ font-size: 120%;\r
+ font-weight: bolder;\r
+}\r
+\r
+.notice {\r
+ margin: 0.5em auto 0.5em auto;\r
+ padding: 15px 10px 15px 55px;\r
+ width: 450px;\r
+ background: #eef URL('../images/info.png') left center no-repeat;\r
+ border: 1px solid #cce;\r
+}\r
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://purl.org/kid/ns#">
+
+<head>
+ <meta content="text/html; charset=UTF-8"
+ http-equiv="content-type" py:replace="''"/>
+ <title>Login</title>
+ <style type="text/css">
+ #loginBox
+ {
+ width: 30%;
+ margin: auto;
+ margin-top: 10%;
+ padding-left: 10%;
+ padding-right: 10%;
+ padding-top: 5%;
+ padding-bottom: 5%;
+ font-family: verdana;
+ font-size: 10px;
+ background-color: #eee;
+ border: 2px solid #ccc;
+ }
+
+ #loginBox h1
+ {
+ font-size: 42px;
+ font-family: "Trebuchet MS";
+ margin: 0;
+ color: #ddd;
+ }
+
+ #loginBox p
+ {
+ position: relative;
+ top: -1.5em;
+ padding-left: 4em;
+ font-size: 12px;
+ margin: 0;
+ color: #666;
+ }
+
+ #loginBox table
+ {
+ table-layout: fixed;
+ border-spacing: 0;
+ width: 100%;
+ }
+
+ #loginBox td.label
+ {
+ width: 33%;
+ text-align: right;
+ }
+
+ #loginBox td.field
+ {
+ width: 66%;
+ }
+
+ #loginBox td.field input
+ {
+ width: 100%;
+ }
+
+ #loginBox td.buttons
+ {
+ text-align: right;
+ }
+
+ </style>
+</head>
+
+<body>
+ <div id="loginBox">
+ <h1>Identificación</h1>
+ <p py:content="message">Mensaje de error</p>
+ <p py:content="login_form(value=form_data)">Formulario de login</p>
+ </div>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<?python import sitetemplate ?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" py:extends="sitetemplate">
+
+<head py:match="item.tag=='{http://www.w3.org/1999/xhtml}head'" py:attrs="item.items()">
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+ <title py:replace="''">Sercom</title>
+ <meta py:replace="item[:]"/>
+ <style type="text/css">
+ #pageLogin
+ {
+ font-size: 10px;
+ font-family: verdana;
+ text-align: right;
+ }
+ </style>
+ <style type="text/css" media="screen">
+@import "/static/css/style.css";
+</style>
+</head>
+
+<body py:match="item.tag=='{http://www.w3.org/1999/xhtml}body'" py:attrs="item.items()">
+ <div py:if="tg.config('identity.on',False) and not 'logging_in' in locals()"
+ id="pageLogin">
+ <span py:if="tg.identity.anonymous">
+ <a href="/login">Login</a>
+ </span>
+ <span py:if="not tg.identity.anonymous">
+ Bienvenido ${tg.identity.user.nombre}.
+ <a href="/logout">Logout</a>
+ </span>
+ </div>
+ <div id="header"> </div>
+ <div id="main_content">
+ <div py:if="tg_flash" class="flash" py:content="tg_flash"></div>
+
+ <div py:replace="[item.text]+item[:]"/>
+
+ <!-- End of main_content -->
+ </div>
+<div id="footer"> <img src="/static/images/under_the_hood_blue.png" alt="TurboGears under the hood" />
+ <p>TurboGears is a open source front-to-back web development
+ framework written in Python</p>
+ <p>Copyright © 2006 Kevin Dangoor</p>
+</div>
+</body>
+
+</html>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
+ py:extends="'master.kid'">
+<head>
+<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
+<title>Welcome to TurboGears</title>
+</head>
+<body>
+
+ <div id="status_block">Your application is now running</div>
+ <div id="sidebar">
+ <h2>Learn more</h2>
+ Learn more about TurboGears and take part in its
+ development
+ <ul class="links">
+ <li><a href="http://www.turbogears.org">Official website</a></li>
+ <li><a href="http://docs.turbogears.org">Documentation</a></li>
+ <li><a href="http://trac.turbogears.org/turbogears/">Trac
+ (bugs/suggestions)</a></li>
+ <li><a href="http://groups.google.com/group/turbogears"> Mailing list</a> </li>
+ </ul>
+ <span py:replace="now">now</span>
+ </div>
+ <div id="getting_started">
+ <ol id="getting_started_steps">
+ <li class="getting_started">
+ <h3>Model</h3>
+ <p> <a href="http://docs.turbogears.org/1.0/GettingStarted/DefineDatabase">Design models</a> in the <span class="code">model.py</span>.<br/>
+ Edit <span class="code">dev.cfg</span> to <a href="http://docs.turbogears.org/1.0/GettingStarted/UseDatabase">use a different backend</a>, or start with a pre-configured SQLite database. <br/>
+ Use script <span class="code">tg-admin sql create</span> to create the database tables.</p>
+ </li>
+ <li class="getting_started">
+ <h3>View</h3>
+ <p> Edit <a href="http://docs.turbogears.org/1.0/GettingStarted/Kid">html-like templates</a> in the <span class="code">/templates</span> folder;<br/>
+ Put all <a href="http://docs.turbogears.org/1.0/StaticFiles">static contents</a> in the <span class="code">/static</span> folder. </p>
+ </li>
+ <li class="getting_started">
+ <h3>Controller</h3>
+ <p> Edit <span class="code"> controllers.py</span> and <a href="http://docs.turbogears.org/1.0/GettingStarted/CherryPy">build your
+ website structure</a> with the simplicity of Python objects. <br/>
+ TurboGears will automatically reload itself when you modify your project. </p>
+ </li>
+ </ol>
+ <div class="notice"> If you create something cool, please <a href="http://groups.google.com/group/turbogears">let people know</a>, and consider contributing something back to the <a href="http://groups.google.com/group/turbogears">community</a>.</div>
+ </div>
+ <!-- End of getting_started -->
+</body>
+</html>
--- /dev/null
+import turbogears
+from nose import with_setup
+from turbogears import testutil
+from sercom.controllers import Root
+import cherrypy
+
+def teardown_func():
+ """Tests for apps using identity need to stop CP/TG after each test to
+ stop the VisitManager thread. See http://trac.turbogears.org/turbogears/ticket/1217
+ for details.
+ """
+ turbogears.startup.stopTurboGears()
+
+cherrypy.root = Root()
+
+def test_method():
+ "the index method should return a string called now"
+ import types
+ result = testutil.call(cherrypy.root.index)
+ assert type(result["now"]) == types.StringType
+test_method = with_setup(teardown=teardown_func)(test_method)
+
+def test_indextitle():
+ "The indexpage should have the right title"
+ testutil.createRequest("/")
+ assert "<TITLE>Welcome to TurboGears</TITLE>" in cherrypy.response.body[0]
+test_indextitle = with_setup(teardown=teardown_func)(test_indextitle)
+
+def test_logintitle():
+ "login page should have the right title"
+ testutil.createRequest("/login")
+ assert "<TITLE>Login</TITLE>" in cherrypy.response.body[0]
+test_logintitle = with_setup(teardown=teardown_func)(test_logintitle)
--- /dev/null
+# If your project uses a database, you can set up database tests
+# similar to what you see below. Be sure to set the db_uri to
+# an appropriate uri for your testing database. sqlite is a good
+# choice for testing, because you can use an in-memory database
+# which is very fast.
+
+from turbogears import testutil, database
+# from sercom.model import YourDataClass, User
+
+# database.set_db_uri("sqlite:///:memory:")
+
+# class TestUser(testutil.DBTest):
+# def get_model(self):
+# return User
+# def test_creation(self):
+# "Object creation should set the name"
+# obj = User(user_name = "creosote",
+# email_address = "spam@python.not",
+# display_name = "Mr Creosote",
+# password = "Wafer-thin Mint")
+# assert obj.display_name == "Mr Creosote"
+
--- /dev/null
+Metadata-Version: 1.0
+Name: sercom-so
+Version: 1.0
+Summary: UNKNOWN
+Home-page: UNKNOWN
+Author: UNKNOWN
+Author-email: UNKNOWN
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Framework :: TurboGears
--- /dev/null
+README.txt
+setup.py
+start-sercom.py
+sercom/__init__.py
+sercom/controllers.py
+sercom/json.py
+sercom/model.py
+sercom/release.py
+sercom/config/__init__.py
+sercom/templates/__init__.py
+sercom/tests/__init__.py
+sercom/tests/test_controllers.py
+sercom/tests/test_model.py
+sercom_so.egg-info/PKG-INFO
+sercom_so.egg-info/SOURCES.txt
+sercom_so.egg-info/dependency_links.txt
+sercom_so.egg-info/not-zip-safe
+sercom_so.egg-info/paster_plugins.txt
+sercom_so.egg-info/requires.txt
+sercom_so.egg-info/sqlobject.txt
+sercom_so.egg-info/top_level.txt
--- /dev/null
+TurboGears
+PasteScript
--- /dev/null
+TurboGears >= 1.0
\ No newline at end of file
--- /dev/null
+db_module=sercom.model
+history_dir=$base/sercom/sqlobject-history
--- /dev/null
+from setuptools import setup, find_packages
+from turbogears.finddata import find_package_data
+
+import os
+execfile(os.path.join("sercom", "release.py"))
+
+setup(
+ name="sercom-so",
+ version=version,
+
+ # uncomment the following lines if you fill them out in release.py
+ #description=description,
+ #author=author,
+ #author_email=email,
+ #url=url,
+ #download_url=download_url,
+ #license=license,
+
+ install_requires = [
+ "TurboGears >= 1.0",
+ ],
+ scripts = ["start-sercom.py"],
+ zip_safe=False,
+ packages=find_packages(),
+ package_data = find_package_data(where='sercom',
+ package='sercom'),
+ keywords = [
+ # Use keywords if you'll be adding your package to the
+ # Python Cheeseshop
+
+ # if this has widgets, uncomment the next line
+ # 'turbogears.widgets',
+
+ # if this has a tg-admin command, uncomment the next line
+ # 'turbogears.command',
+
+ # if this has identity providers, uncomment the next line
+ # 'turbogears.identity.provider',
+
+ # If this is a template plugin, uncomment the next line
+ # 'python.templating.engines',
+
+ # If this is a full application, uncomment the next line
+ # 'turbogears.app',
+ ],
+ classifiers = [
+ 'Development Status :: 3 - Alpha',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Framework :: TurboGears',
+ # if this is an application that you'll distribute through
+ # the Cheeseshop, uncomment the next line
+ # 'Framework :: TurboGears :: Applications',
+
+ # if this is a package that includes widgets that you'll distribute
+ # through the Cheeseshop, uncomment the next line
+ # 'Framework :: TurboGears :: Widgets',
+ ],
+ test_suite = 'nose.collector',
+ )
+
--- /dev/null
+#!/usr/bin/python
+import pkg_resources
+pkg_resources.require("TurboGears")
+
+from turbogears import update_config, start_server
+import cherrypy
+cherrypy.lowercase_api = True
+from os.path import *
+import sys
+
+# first look on the command line for a desired config file,
+# if it's not on the command line, then
+# look for setup.py in this directory. If it's not there, this script is
+# probably installed
+if len(sys.argv) > 1:
+ update_config(configfile=sys.argv[1],
+ modulename="sercom.config")
+elif exists(join(dirname(__file__), "setup.py")):
+ update_config(configfile="dev.cfg",modulename="sercom.config")
+else:
+ update_config(configfile="prod.cfg",modulename="sercom.config")
+
+from sercom.controllers import Root
+
+start_server(Root())