From: Leandro Lucarella Date: Thu, 22 Feb 2007 14:15:56 +0000 (+0000) Subject: Convertir parametros de CasoDePrueba en un campo de string con validador especial. X-Git-Tag: pre-merge~127 X-Git-Url: https://git.llucax.com/software/sercom.git/commitdiff_plain/e6d1e37559beee0064df00956fda8b7e6c9d68b2?ds=sidebyside Convertir parametros de CasoDePrueba en un campo de string con validador especial. Guardar los parámetros como una tupla puede ser cómodo pero al usar Pickle, la base queda ilegible e inusable desde algo que no sea Python. Por lo tanto, y porque es más natural ingresar los parámetros como un string al estilo bash, se creó un nuevo tipo de columna de SQLObject que valida/convierte el string almacenado en una lista y viceversa, permitiendo usar string o listas indistintamente, pero siempre almacenándose como string. IMPORTANTE: Hay que regenerar la DB y tiene un "bug", al editar, los datos de la DB se "renderizan" como una lista en el formulario, hay que ver una forma de renderizarlo como en el list/show. --- diff --git a/doc/schema/schema.sql b/doc/schema/schema.sql index c34f8f8..3cc2db8 100644 --- a/doc/schema/schema.sql +++ b/doc/schema/schema.sql @@ -64,7 +64,7 @@ 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, + parametros VARCHAR(255) NOT NULL, retorno INT, tiempo_cpu FLOAT, descripcion VARCHAR(255) diff --git a/doc/testdata.py b/doc/testdata.py index 10fc64b..b98618b 100644 --- a/doc/testdata.py +++ b/doc/testdata.py @@ -21,7 +21,7 @@ e3 = d.add_enunciado(u'Más enunciados', u'Ejercicio anónimo') c = Curso(2007, 1, 1, u'Martes', [d], [e1, e2]) 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')) +cp2 = e1.add_caso_de_prueba(u'2 parámetross', retorno=0, parametros='--test -c') ej1 = c.ejercicios[0] ej1.grupal = True diff --git a/sercom/model.py b/sercom/model.py index 06c132d..dff54ae 100644 --- a/sercom/model.py +++ b/sercom/model.py @@ -5,9 +5,11 @@ from turbogears.database import PackageHub from sqlobject import * from sqlobject.sqlbuilder import * from sqlobject.inheritance import InheritableSQLObject -from sqlobject.col import PickleValidator +from sqlobject.col import PickleValidator, UnicodeStringValidator from turbogears import identity from turbogears.identity import encrypt_password as encryptpw +from sercom.validators import params_to_list, ParseError +from formencode import Invalid hub = PackageHub("sercom") __connection__ = hub @@ -21,21 +23,19 @@ 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" % \ + raise 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" % \ + raise 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) @@ -47,11 +47,48 @@ class SOTupleCol(SOPickleCol): class TupleCol(PickleCol): baseClass = SOTupleCol +class ParamsValidator(UnicodeStringValidator): + def to_python(self, value, state): + if isinstance(value, basestring): + value = super(ParamsValidator, self).to_python(value, state) + try: + value = params_to_list(value) + except ParseError, e: + raise Invalid("invalid parameters in the ParamsCol '%s', parse " + "error: %s" % (self.name, e), value, state) + elif not isinstance(value, (list, tuple)): + raise Invalid("expected a tuple, list or valid string in the " + "ParamsCol '%s', got %s %r instead" + % (self.name, type(value), value), value, state) + return value + def from_python(self, value, state): + if isinstance(value, (list, tuple)): + value = ' '.join([repr(p) for p in value]) + elif isinstance(value, basestring): + value = super(ParamsValidator, self).to_python(value, state) + try: + params_to_list(value) + except ParseError, e: + raise Invalid("invalid parameters in the ParamsCol '%s', parse " + "error: %s" % (self.name, e), value, state) + else: + raise Invalid("expected a tuple, list or valid string in the " + "ParamsCol '%s', got %s %r instead" + % (self.name, type(value), value), value, state) + return value + +class SOParamsCol(SOUnicodeCol): + def createValidators(self): + return [ParamsValidator(db_encoding=self.dbEncoding, name=self.name)] \ + + super(SOParamsCol, self).createValidators() + +class ParamsCol(UnicodeCol): + baseClass = SOParamsCol + #}}} #{{{ 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)) @@ -364,14 +401,14 @@ class CasoDePrueba(SQLObject): #{{{ 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=()) + parametros = ParamsCol(length=255) retorno = IntCol(default=None) tiempo_cpu = FloatCol(default=None) descripcion = UnicodeCol(length=255, default=None) # Joins pruebas = MultipleJoin('Prueba') - def __init__(self, enunciado=None, nombre=None, parametros=(), + def __init__(self, enunciado=None, nombre=None, parametros=None, retorno=None, tiempo_cpu=None, descripcion=None, **kargs): SQLObject.__init__(self, enunciadoID=enunciado and enunciado.id, nombre=nombre, parametros=parametros, retorno=retorno, diff --git a/sercom/subcontrollers/caso_de_prueba/__init__.py b/sercom/subcontrollers/caso_de_prueba/__init__.py index a65f41e..62435c6 100644 --- a/sercom/subcontrollers/caso_de_prueba/__init__.py +++ b/sercom/subcontrollers/caso_de_prueba/__init__.py @@ -10,6 +10,7 @@ from turbogears import paginate from docutils.core import publish_parts from sercom.subcontrollers import validate as val from sercom.model import CasoDePrueba, Enunciado +from sercom.validators import ParamsValidator #}}} #{{{ Configuración @@ -64,6 +65,8 @@ class CasoDePruebaForm(W.TableForm): W.TextField(name='descripcion', label=_(u'Descripción'), validator=V.UnicodeString(not_empty=False, max=255, strip=True)), + W.TextField(name='parametros', label=_(u'Parámetros'), + validator=ParamsValidator(not_empty=False, strip=True)), W.TextField(name='retorno', label=_(u'Código de retorno'), validator=V.Int(not_empty=False, strip=True)), W.TextField(name='tiempo_cpu', label=_(u'Tiempo de CPU'), @@ -75,6 +78,10 @@ form = CasoDePruebaForm() #}}} #{{{ Controlador + +def params2str(params): + return ' '.join([repr(p)[1:] for p in params]) + class CasoDePruebaController(controllers.Controller, identity.SecureResource): """Basic model admin interface""" require = identity.has_permission('admin') @@ -97,7 +104,8 @@ class CasoDePruebaController(controllers.Controller, identity.SecureResource): r = cls.select() else: r = cls.selectBy(enunciadoID=enunciado) - return dict(records=r, name=name, namepl=namepl, parcial=enunciado) + return dict(records=r, name=name, namepl=namepl, parcial=enunciado, + params2str=params2str) @expose(template='kid:%s.templates.new' % __name__) def new(self, **kw): @@ -117,7 +125,8 @@ class CasoDePruebaController(controllers.Controller, identity.SecureResource): def edit(self, id, **kw): """Edit record in model""" r = validate_get(id) - return dict(name=name, namepl=namepl, record=r, form=form) + return dict(name=name, namepl=namepl, record=r, form=form, + params2str=params2str) @validate(form=form) @error_handler(edit) @@ -136,7 +145,7 @@ class CasoDePruebaController(controllers.Controller, identity.SecureResource): r.desc = '' else: r.desc = publish_parts(r.descripcion, writer_name='html')['html_body'] - return dict(name=name, namepl=namepl, record=r) + return dict(name=name, namepl=namepl, record=r, params2str=params2str) @expose() def delete(self, id): diff --git a/sercom/subcontrollers/caso_de_prueba/templates/list.kid b/sercom/subcontrollers/caso_de_prueba/templates/list.kid index c00f57c..62b9653 100644 --- a/sercom/subcontrollers/caso_de_prueba/templates/list.kid +++ b/sercom/subcontrollers/caso_de_prueba/templates/list.kid @@ -25,7 +25,7 @@ href="${tg.url('/enunciado/show/%d' % record.enunciado.id)}">enunciado descripción - --parametros + --parámetros retorno tiempo de cpu Editar diff --git a/sercom/subcontrollers/caso_de_prueba/templates/show.kid b/sercom/subcontrollers/caso_de_prueba/templates/show.kid index bafecc1..70f5060 100644 --- a/sercom/subcontrollers/caso_de_prueba/templates/show.kid +++ b/sercom/subcontrollers/caso_de_prueba/templates/show.kid @@ -22,6 +22,12 @@ Descripción: descripcion + + Parámetros: + + --parámetros: + + Código de retorno: retorno @@ -30,14 +36,6 @@ Tiempo de CPU: tiempo_cpu - - Parámetros: - - - parámetro - - -
diff --git a/sercom/validators.py b/sercom/validators.py new file mode 100644 index 0000000..4779006 --- /dev/null +++ b/sercom/validators.py @@ -0,0 +1,88 @@ +# vim: set et sw=4 sts=4 encoding=utf-8 : + +from turbogears.validators import * + +class ParseError(ValueError): pass + +def params_to_list(params): + r"""Parsea un string de forma similar al bash, separando por espacios y + teniendo en cuenta comillas simples y dobles para agrupar. Para poner + comillas se puede usar el \ como caracter de escape (\' y \") y también + interpreta \n y \t. Devuelve una lista con los parámetros encontrados. + >>> param2seq('--prueba') + ['--prueba'] + >>> params_to_list('--prueba larga "con espacios"') + ['--prueba', 'larga', 'con espacios'] + >>> params_to_list(u'''"con enter\\nentre 'comillas \\"dobles\\"'" --unicode''') + [u'con enter\nentre \'comillas "dobles"\'', u'--unicode'] + >>> params_to_list('"archivo\\tseparado\\tpor\\ttabs" -h') + ['archivo\tseparado\tpor\ttabs', '-h'] + """ + # Constantes + SEP, TOKEN, DQUOTE, SQUOTE = ' ', None, '"', "'" + seq = [] + buff = '' + escape = False + state = SEP + if not params: return seq + for c in params: + # Es un caracter escapado + if escape: + if c == 'n': + buff += '\n' + elif c == 't': + buff += '\t' + else: + buff += c + escape = False + continue + # Es una secuencia de escape + if c == '\\': + escape = True + continue + # Si está buscando espacios + if state == SEP: + if c == SEP: + continue + else: + state = TOKEN # Encontró + if state == TOKEN: + if c == DQUOTE: + state = DQUOTE + continue + if c == SQUOTE: + state = SQUOTE + continue + if c == SEP: + state = SEP + seq.append(buff) + buff = '' + continue + buff += c + continue + if state == DQUOTE: + if c == DQUOTE: + state = TOKEN + continue + buff += c + continue + if state == SQUOTE: + if c == SQUOTE: + state = TOKEN + continue + buff += c + continue + raise ParseError, 'Invalid syntax' + if state == DQUOTE or state == SQUOTE: + raise ParseError, 'Missing closing quote %s' % state + if buff: + seq.append(buff) + return seq + +class ParamsValidator(UnicodeString): + def validate_python(self, value, state): + try: + params_to_list(value) + except ParseError, e: + raise Invalid(str(e), value, state) +