]> git.llucax.com Git - software/sercom.git/commitdiff
Convertir parametros de CasoDePrueba en un campo de string con validador especial.
authorLeandro Lucarella <llucax@gmail.com>
Thu, 22 Feb 2007 14:15:56 +0000 (14:15 +0000)
committerLeandro Lucarella <llucax@gmail.com>
Thu, 22 Feb 2007 14:15:56 +0000 (14:15 +0000)
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.

doc/schema/schema.sql
doc/testdata.py
sercom/model.py
sercom/subcontrollers/caso_de_prueba/__init__.py
sercom/subcontrollers/caso_de_prueba/templates/list.kid
sercom/subcontrollers/caso_de_prueba/templates/show.kid
sercom/validators.py [new file with mode: 0644]

index c34f8f8c67c23b7df39af77b06b239276e918103..3cc2db88aa6893972ab84b1d358336d6c0d58bf9 100644 (file)
@@ -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)
index 10fc64bc3ef7ae0a8dcfe21e32ac52331f5015c5..b98618b5496bc91b70fca875081f3c2259978633 100644 (file)
@@ -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
index 06c132d933e95f48be23e0454a2425020aa6563c..dff54aee99ed9c7ac70cec7bafbed1193559ae8c 100644 (file)
@@ -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,
index a65f41ec753ed4a15588c4002b270430e3f1dc70..62435c676ed3a7be83fe34973be1e3566bf85b5b 100644 (file)
@@ -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):
index c00f57c5d4612ed3b4f436b1b63fea4b028988a4..62b9653c1ce6fceb54228ff954ffab4a0cf83f01 100644 (file)
@@ -25,7 +25,7 @@
                 href="${tg.url('/enunciado/show/%d' % record.enunciado.id)}"><span
                     py:replace="tg.summarize(record.enunciado.shortrepr(), 30)">enunciado</span></a></td>
         <td><span py:replace="tg.summarize(record.descripcion, 30)">descripción</span></td>
-        <td><span py:if="record.parametros" py:replace="tg.summarize(' '.join([repr(p) for p in record.parametros]), 30)">--parametros</span></td>
+        <td><span py:if="record.parametros" py:replace="tg.summarize(params2str(record.parametros), 30)">--parámetros</span></td>
         <td><span py:replace="record.retorno">retorno</span></td>
         <td><span py:replace="record.tiempo_cpu">tiempo de cpu</span></td>
         <td><a href="${tg.url('/caso_de_prueba/edit/%d' % record.id)}">Editar</a>
index bafecc1fe35ac14e27c2403b7b73d89ba5308792..70f5060a1352e733487c9e161fb357dbd9998aa6 100644 (file)
         <th>Descripción:</th>
        <td><span py:replace="XML(record.desc)">descripcion</span></td>
     </tr>
+    <tr>
+        <th>Parámetros:</th>
+       <td>
+            <span py:if="record.parametros" py:replace="params2str(record.parametros)">--parámetros</span>:
+       </td>
+    </tr>
     <tr>
         <th>Código de retorno:</th>
        <td><span py:replace="record.retorno">retorno</span></td>
         <th>Tiempo de CPU:</th>
        <td><span py:replace="record.tiempo_cpu">tiempo_cpu</span></td>
     </tr>
-    <tr>
-        <th>Parámetros:</th>
-       <td>
-               <span py:for="p in record.parametros">
-                       <span py:replace="p">parámetro</span>
-               </span>
-       </td>
-    </tr>
 </table>
 
 <br/>
diff --git a/sercom/validators.py b/sercom/validators.py
new file mode 100644 (file)
index 0000000..4779006
--- /dev/null
@@ -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)
+