def test_permissions(self): data = pd.RestrictedMemData( [pd.ColumnSpec(c, pd.String()) for c in ('x', 'y', 'z')], access_rights=pd.AccessRights(('x', (None, pd.Permission.ALL)), ('y', (None, pd.Permission.VIEW)), ('z', ((), pd.Permission.ALL))), ) row = pp.PresentedRow(( pp.Field('x'), pp.Field('y'), pp.Field('z'), pp.Field('zz', virtual=True, computer=pp.computer(lambda r, z: '-' + z + '-')), ), data, None, new=True) assert row.permitted('x', pd.Permission.VIEW) assert row.permitted('x', True) assert row.permitted('y', pd.Permission.VIEW) assert not row.permitted('y', pd.Permission.UPDATE) assert not row.permitted('z', pd.Permission.VIEW) protected_row = row.protected() with pytest.raises(protected_row.ProtectionError): protected_row['z'].value() with pytest.raises(protected_row.ProtectionError): protected_row['zz'].value() # Cover special cases for a non-permitted field in various methods. assert row.get('z', secure=True) is None assert row.display('z') == '' assert row.enumerate('z') == [] assert not row.editable('z')
def test_validation_order(self): # This test reproduces a previously existing bug in computer input # validation dependencies. fields = ( pp.Field('a', type=pd.String(not_null=True)), pp.Field('b', type=pd.String(not_null=True, maxlen=1), computer=pp.computer(lambda r, a: a[0].upper(), validate=True)), pp.Field('c', type=pd.String(enumerator=pd.FixedEnumerator(range(10)), not_null=True), computer=pp.computer(lambda r, b: str(ord(b) % 10), validate=True)), ) row = self._row(fields, new=True) row.validate('a', 'foo') assert row['b'].value() == 'F' assert row['c'].value() == '0' # Set 'b' to an invalid value (violates maxlen=1). row.validate('b', 'xxx') def cb(): # This used to fail when the computer for 'c' was not called # due to invalid value of 'b' (when 'b' validity was not refreshed # correctly). assert row['c'].value() == '6' row.register_callback(row.CALL_CHANGE, 'c', cb) row.validate('a', 'bar')
def test_completer(self): completer = self._enumerator( ('x', ), data=(('Apple', ), ('Bananas', ), ('Basil', ), ('Bacardi', ), ('Cinamon', )), ) fields = ( pp.Field('a', type=pd.Integer(not_null=True)), pp.Field('x0', type=pd.String(enumerator=pd.FixedEnumerator(('a', 'b', 'c')))), pp.Field('x1', type=pd.String(), completer=('yes', 'no', 'maybe')), pp.Field('x2', type=pd.String(), completer=completer, runtime_filter=pp.computer(lambda r, a: pd.NE( 'x', pd.sval('Bacardi')) if a == 1 else None)), ) row = self._row(fields, new=True) assert not row.has_completer('a') assert row.has_completer('x0') assert row.has_completer('x1', static=True) assert row.has_completer('x2') assert not row.has_completer('x2', static=True) assert row.completions('a') == [] assert row.completions('x0') == ['a', 'b', 'c'] assert row.completions('x1') == ['maybe', 'no', 'yes'] assert row.completions('x1', prefix='y') == ['yes'] assert row.completions('x2') == ('Apple', 'Bananas', 'Basil', 'Bacardi', 'Cinamon') assert row.completions('x2', prefix='ba') == ('Bananas', 'Basil', 'Bacardi') row['a'] = 1 assert row.completions('x2', prefix='ba') == ('Bananas', 'Basil')
class Specification(pp.Specification): table = 'grid_test' fields = ( pp.Field('id', type=pd.Integer()), pp.Field('name', type=pd.String()), pp.Field('price', type=pd.Float(precision=2)), pp.Field('flag', type=pd.Boolean()), )
def test_prefill_untouched(self): row = self._row(( pp.Field('a', type=pd.Integer()), pp.Field('b', type=pd.Integer()), )) prefill = dict(a=5, b=8) row.set_row(None, reset=True, prefill=prefill) assert prefill == dict(a=5, b=8)
def test_sorting(self): fields = ( pp.Field('a', 'Field A'), pp.Field('b', 'Field B'), pp.Field('c', 'Field C'), pp.Field('d', 'Field D'), ) unsorted = (fields[2], fields[0], fields[1], fields[3]) assert tuple(sorted(unsorted)) == fields
def test_filename(self): row = self._row(( pp.Field('x', default='aaa'), pp.Field('y', filename='x'), pp.Field('z', filename=lambda r: 'file_%s.pdf' % r['x'].value()), ), new=True) assert row.filename('x') == None assert row.filename('y') == 'aaa' assert row.filename('z') == 'file_aaa.pdf'
def test_set_transaction(self): row = self._row((pp.Field('x'), pp.Field('y'))) assert row.transaction() == None # TODO: This whole test file tries to avoid using a real database connection, # so we can't create a real transaction here. Using 'x' is invalid for # real use but it stil verifies that set_transaction works as long as # set_transaction doesn't validate its argument. transaction = 'x' # pd.DBTransactionDefault(pytis.config.dbconnection) row.set_transaction(transaction) assert row.transaction() == transaction
class Fruits(pytis.presentation.Specification): fields = ( pp.Field('id'), pp.Field('title'), pp.Field('code', type=pd.Integer()), pp.Field('tropical', type=pd.Boolean()), ) data_cls = pd.MemData data = (('apl', 'Apple', 123, False), ('ban', 'Banana', 234, True), ('str', 'Strawberry', 234, False), ('org', 'Orange', 456, True))
def test_attachment_storage(self): storage = pytis.presentation.AttachmentStorage() row = self._row(( pp.Field('a', type=pd.String()), pp.Field('b', type=pd.String(), attachment_storage=storage), pp.Field('c', type=pd.String(), attachment_storage=lambda row: storage), )) assert row.attachment_storage('a') is None assert row.attachment_storage('b') == storage assert row.attachment_storage('c') == storage
def test_cbcomputer_display(self): enumerator = self._enumerator(('id', 'title'), data=(('1', 'First'), ('2', 'Second'))) fields = ( pp.Field('a', type=pd.String(enumerator=enumerator), display='title'), pp.Field('b', computer=pp.CbComputer('a', 'title')), ) row = self._row(fields) row['a'] = '1' assert row.display('a') == 'First' assert row.display('b') == ''
def test_display_functions(self): enumerator = self._enumerator( ('id', 'title', 'letter'), data=(('1', 'First', 'A'), ('2', 'Second', 'B'))) fields = ( pp.Field('a'), pp.Field('b', type=pd.String(enumerator=enumerator), display=lambda x: '-' + x + '-'), pp.Field('c', type=pd.String(enumerator=enumerator), display=lambda row: row['title'].value().lower()), ) row = self._row(fields, None, new=True, prefill=dict(b='1', c='2')) assert row.display('b') == '-1-' assert row.display('c') == 'second'
class Spec(wiking.Specification): table = 'cms_access_log_data' fields = [ pp.Field(_id) for _id in ('log_id', 'timestamp', 'uri', 'uid', 'modname', 'action', 'ip_address', 'user_agent', 'referer') ]
class Spec(wiking.Specification): table = 'cms_session_log_data' fields = [ pp.Field(_id) for _id in ('log_id', 'session_id', 'uid', 'login', 'success', 'start_time', 'ip_address', 'user_agent', 'referer') ]
def test_binary_computer(self): a = '\x04\x00\x00\x05\x00\x19\x00' b = '\x00\x05\x04\xa4\xbb\x10\x00' row = self._row(( pp.Field('x', type=pd.String()), pp.Field( 'data', type=pd.Binary(not_null=True), computer=pp.computer( lambda r, x: x == 'a' and a or x == 'b' and b or None)), )) assert row['data'].value() is None row['x'] = pd.sval('a') assert row['data'].value() == a assert isinstance(row['data'].value(), pd.Binary.Data) row['x'] = pd.sval('b') assert row['data'].value() == b
def test_inline_display(self): enumerator = self._enumerator( ('id', 'title', 'letter'), data=(('1', 'First', 'A'), ('2', 'Second', 'B'))) fields = ( pp.Field('a', type=pd.String(enumerator=enumerator), display='title', inline_display='b', null_display='-'), pp.Field('b', type=pd.String()), ) row = self._row(fields, new=True, prefill=dict(a='1', b='FIRST')) assert row.display('a') == 'FIRST' assert row.display('a', export=lambda x: x.value().lower()) == 'first' row['b'] = None assert row.display('a') == '-'
def test_unique(self): data = pd.MemData( (pd.ColumnSpec('a', pd.String(not_null=True, unique=True)), ), data=(('1', ), ('2', ), ('3', )), ) row = pp.PresentedRow((pp.Field('a'), ), data, None, new=True) assert row.validate('a', '1') is not None assert row.validate('a', '4') is None
def test_str(self): class BigString(pd.String, pd.Big): pass row = self._row(( pp.Field('x', type=pd.Integer(not_null=True)), pp.Field('y', type=pd.Integer(), default=88), pp.Field('passwd', type=pd.Password()), pp.Field('data', type=BigString()), ), new=True, prefill=dict(x=1, y=3, passwd='secret', data=1024 * 'x')) assert unistr( row ) == '<PresentedRow: x=1, y=3, passwd=***, data=<BigString 1 kB>>'
def test_editable_always_bool(self): row = self._row(( pp.Field('a', type=pd.String()), pp.Field('b', type=pd.Integer()), pp.Field('c', editable=pp.computer(lambda r, a, b: a or b)), )) for a, b, editable in ( (None, None, False), ('', None, False), (None, 0, False), ('', 0, False), (None, 1, True), ('', 5, True), ('x', None, True), ('y', 8, True), ): row['a'] = a row['b'] = b assert row.editable('c') is editable
def test_validation_cache(self): # This test reproduces a previously existing bug in validation result caching. enumerator = self._enumerator(('id', 'title'), data=(('1', 'First'), ('2', 'Second'))) row = self._row((pp.Field('a', type=pd.String(not_null=True, enumerator=enumerator)), )) assert not row.validate('a', '3') is None data = enumerator._data # There is currently no need to make this public elsewhere. data.insert(pd.Row( (('id', pd.sval('3')), ('title', pd.sval('Third'))))) assert row.validate('a', '3') == None
def setUp(self): self.longMessage = True key = pd.ColumnSpec('a', pd.Integer()) self._columns = (key, pd.ColumnSpec('b', pd.Integer()), pd.ColumnSpec('c', pd.Integer()), pd.ColumnSpec('d', pd.Integer()), pd.ColumnSpec('r', pd.IntegerRange())) self._data = pd.Data(self._columns, key) def twice(row): # Just try if it is possible to access the original row. row.original_row()['c'].value() c = row['c'].value() return c is not None and c * 2 or None def sum(row): b, c = (row['b'].value(), row['c'].value()) if b is None or c is None: return 0 return b + c def inc(row): sum = row['sum'].value() return sum is not None and sum + 1 or None def gt5(row): return row['sum'].value() > 5 self._fields = ( pp.Field('a'), pp.Field('b'), pp.Field('c', default=lambda: 5), pp.Field('d', editable=pp.Computer(gt5, depends=('sum', )), computer=pp.Computer(twice, depends=('c', ))), pp.Field('e', type=pd.Integer(), virtual=True, default=88), pp.Field('sum', type=pd.Integer(), virtual=True, editable=pp.Editable.NEVER, computer=pp.Computer(sum, depends=('b', 'c'))), pp.Field('inc', type=pd.Integer(), virtual=True, editable=pp.Editable.NEVER, computer=pp.Computer(inc, depends=('sum', ))), pp.Field('r'), )
def test_recursive_computer_validation(self): fields = ( pp.Field('a', type=pd.Integer(not_null=True), computer=pp.computer(lambda r, b: 2 * b, validate=True)), pp.Field( 'b', type=pd.Integer(not_null=True, enumerator=pd.FixedEnumerator(range(101))), runtime_filter=pp.computer(lambda r, a: lambda x: x % a == 0, validate=True)), ) # The computer for 'a' is called to compute the initial value and will lead to recursion # because it requires validation of 'b' which needs the value of 'a'... with pytest.raises(RuntimeError): self._row(fields, new=True) class Specification(pp.Specification): pass Specification.fields = fields # ViewSpec initialization should detect the cyclic dependency. with pytest.raises(AssertionError): Specification().view_spec() class Specification2(Specification): def _customize_fields(self, fields): fields.modify('b', runtime_filter=pp.computer( lambda r, a: lambda x: x % a == 0, validate=True, novalidate=('a', ))) row = self._row(Specification2().view_spec().fields(), new=True) # 'a' is None so runtime_filter will try to compute x % None (because 'a' is not validated). with pytest.raises(TypeError): row.enumerate('b')
class Spec(Specification): table = 'cms_session' fields = [pp.Field(_id) for _id in ('session_id', 'uid', 'session_key', 'last_access')]
def test_fields(self): fields = (pp.Field('x'), pp.Field('y')) row = self._row(fields) assert row.fields() == fields
def test_keys(self): row = self._row((pp.Field('x'), pp.Field('y'))) assert sorted(row.keys()) == ['x', 'y']
def test_resolver(self): row = self._row((pp.Field('x'), pp.Field('y'))) assert row.resolver() == pytis.config.resolver
def _mega_row(self, new=False, row=None, singleline=False, **prefill): # TODO: This all-in-one row was historically used for all tests # but it proved to be problematic. You can not see the definition # of the fields from paticular tests so it is hard to guess the test # logic. Morover the specification is getting too complicated # and hard to maintain usable in all situations. Thus new tests should # Define their own fields directly just for their purpose. class BigString(pd.String, pd.Big): pass class SpecialEnumerator(pd.FixedEnumerator): # This class is overriden just to allow definition of runtime_filter # and runtime_arguments for the same field (which is only important # to improve test coverage) def values(self, a=None): # Accepts argument a as returned by runtime_arguments. return super(SpecialEnumerator, self).values() fields = ( pp.Field('a', type=pd.Integer(not_null=True)), pp.Field('b', type=pd.Integer(not_null=True, enumerator=SpecialEnumerator(range(101))), runtime_filter=pp.computer( lambda r, a: lambda x: x % a == 0, validate=True), runtime_arguments=pp.computer(lambda r, a: dict(a=a))), pp.Field('c', type=pd.Integer(not_null=True), default=lambda: 5), pp.Field('d', type=pd.Integer(), editable=pp.computer(lambda r, total: total > 5 if total else False), computer=pp.computer(lambda r, c: c * 2, validate=True)), pp.Field('fruit', type=pd.String(), codebook='Fruits', display='title', null_display='none'), pp.Field('fruit_code', virtual=True, computer=pp.CbComputer('fruit', 'code')), pp.Field('range', type=pd.IntegerRange(), visible=pp.computer(lambda r, a: a != 0)), pp.Field('total', type=pd.Integer(), virtual=True, editable=pp.Editable.NEVER, computer=pp.computer(lambda r, b, c: b + c, validate=True, fallback=0)), pp.Field('half_total', type=pd.Integer(), virtual=True, editable=pp.Editable.NEVER, computer=pp.computer(lambda r, total: total // 2 if total is not None else None)), pp.Field('x', type=pd.Integer(), virtual=True, default=88), pp.Field('password', type=pd.Password(), virtual=True), pp.Field('big', type=BigString(), virtual=True), pp.Field('array', type=pd.Array, inner_type=pd.String, codebook='Fruits', display='title', virtual=True), ) class Fruits(pytis.presentation.Specification): fields = ( pp.Field('id'), pp.Field('title'), pp.Field('code', type=pd.Integer()), pp.Field('tropical', type=pd.Boolean()), ) data_cls = pd.MemData data = (('apl', 'Apple', 123, False), ('ban', 'Banana', 234, True), ('str', 'Strawberry', 234, False), ('org', 'Orange', 456, True)) class TestResolver(pytis.util.Resolver): _specifications = {'Fruits': Fruits} def _get_object_by_name(self, name): try: return self._specifications[name] except KeyError: return super(TestResolver, self)._get_object_by_name(name) pytis.config.resolver = TestResolver() return self._row(fields, row=row, new=new, singleline=singleline, prefill=prefill)
def test_data(self): row = self._row((pp.Field('x'), pp.Field('y'))) assert isinstance(row.data(), pd.MemData)
def test_invalid_codebook_field_type(self): row = self._row((pp.Field('a', codebook='InvalidName'), )) assert isinstance(row.type('a'), pytis.data.String)
def test_virtual_field_type_class(self): row = self._row((pp.Field('a', type=pytis.data.Integer), )) assert isinstance(row.type('a'), pytis.data.Integer)