From 7e7d0933f5c3cf2dd9d852280a0e4116422cafa1 Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Mon, 16 Mar 2015 12:08:13 +0100 Subject: [PATCH] [gen] Added field dict, similar to List but that stores dicts in the database, with a set of keys given in method field.keys. --- fields/__init__.py | 11 +++-- fields/dict.py | 98 ++++++++++++++++++++++++++++++++++++++++++ fields/list.py | 8 ++-- gen/__init__.py | 1 + gen/descriptors.py | 1 + gen/mixins/__init__.py | 10 ++--- gen/ui/appy.css | 2 +- 7 files changed, 117 insertions(+), 14 deletions(-) create mode 100644 fields/dict.py diff --git a/fields/__init__.py b/fields/__init__.py index ac5f33d..e1e4b49 100644 --- a/fields/__init__.py +++ b/fields/__init__.py @@ -25,6 +25,9 @@ from group import Group from search import Search from page import Page +# In this file, names "list" and "dict" refer to sub-modules. To use Python +# builtin types, use __builtins__['list'] and __builtins__['dict'] + # ------------------------------------------------------------------------------ class Field: '''Basic abstract class for defining any field.''' @@ -396,7 +399,7 @@ class Field: # We must initialise the corresponding back reference self.back.klass = klass self.back.init(self.back.attribute, self.klass, appName) - if self.type == "List": + if self.type in ('List', 'Dict'): for subName, subField in self.fields: fullName = '%s_%s' % (name, subName) subField.init(fullName, klass, appName) @@ -490,7 +493,7 @@ class Field: def formatMapping(self, mapping): '''Creates a dict of mappings, one entry by label type (label, descr, help).''' - if isinstance(mapping, dict): + if isinstance(mapping, __builtins__['dict']): # Is it a dict like {'label':..., 'descr':...}, or is it directly a # dict with a mapping? for k, v in mapping.iteritems(): @@ -504,7 +507,7 @@ class Field: mapping[labelType] = None # No mapping for this value. return mapping else: - # Mapping is a method that must be applied to any i18n message. + # Mapping is a method that must be applied to any i18n message return {'label':mapping, 'descr':mapping, 'help':mapping} def formatLayouts(self, layouts): @@ -532,7 +535,7 @@ class Field: layouts = copy.deepcopy(layouts) if 'edit' not in layouts: defEditLayout = self.computeDefaultLayouts() - if type(defEditLayout) == dict: + if isinstance(defEditLayout, __builtins__['dict']): defEditLayout = defEditLayout['edit'] layouts['edit'] = defEditLayout # We have now a dict of layouts in p_layouts. Ensure now that a Table diff --git a/fields/dict.py b/fields/dict.py new file mode 100644 index 0000000..f010e70 --- /dev/null +++ b/fields/dict.py @@ -0,0 +1,98 @@ +# ------------------------------------------------------------------------------ +# This file is part of Appy, a framework for building applications in the Python +# language. Copyright (C) 2007 Gaetan Delannay + +# Appy is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. + +# Appy is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with +# Appy. If not, see . + +# ------------------------------------------------------------------------------ +from appy import Object +from list import List +from appy.px import Px +from appy.gen.layout import Table + +# ------------------------------------------------------------------------------ +class Dict(List): + '''A Dict is stored as a dict of Object instances [Object]~. Keys are fixed + and are given by a method specified in parameter "keys". Values are + Object instances, whose attributes are determined by parameter "fields" + that, similarly to the List field, determines sub-data for every entry in + the dict. This field is build on top of the List field.''' + + # PX for rendering a single row + pxRow = Px(''' + + :row[1] + :field.pxRender + ''') + + # PX for rendering the dict (shared between pxView and pxEdit) + pxTable = Px(''' + + + + + + + + :field.pxRow +
::_(info[1].labelId)
''') + + def __init__(self, keys, fields, validator=None, multiplicity=(0,1), + default=None, show=True, page='main', group=None, layouts=None, + move=0, specificReadPermission=False, + specificWritePermission=False, width='', height=None, + maxChars=None, colspan=1, master=None, masterValue=None, + focus=False, historized=False, mapping=None, label=None, + subLayouts=Table('frv', width=None), widths=None, view=None, + xml=None): + List.__init__(self, fields, validator, multiplicity, default, show, page, + group, layouts, move, specificReadPermission, + specificWritePermission, width, height, maxChars, colspan, + master, masterValue, focus, historized, mapping, label, + subLayouts, widths, view, xml) + # Method in "keys" must return a list of tuples (key, title): "key" + # determines the key that will be used to store the entry in the + # database, while "title" will get the text that will be shown in the ui + # while encoding/viewing this entry. + self.keys = keys + + def getFormattedValue(self, obj, value, layoutType='view', + showChanges=False, language=None): + '''Formats the dict value as a list of values''' + res = [] + for key, title in self.keys(obj.appy()): + if key in value: + res.append(value[key]) + else: + # There is no value for this key in the database p_value + res.append(None) + return res + + def getStorableValue(self, obj, value): + '''Gets p_value in a form that can be stored in the database''' + res = {} + values = List.getStorableValue(self, obj, value) + i = -1 + for key, title in self.keys(obj.appy()): + i += 1 + res[key] = values[i] + return res +# ------------------------------------------------------------------------------ diff --git a/fields/list.py b/fields/list.py index 0ef5561..33f31cd 100644 --- a/fields/list.py +++ b/fields/list.py @@ -123,14 +123,14 @@ class List(Field): return res def getRequestValue(self, obj, requestName=None): - '''Concatenates the list from distinct form elements in the request.''' + '''Concatenates the list from distinct form elements in the request''' request = obj.REQUEST name = requestName or self.name # A List may be into another List (?) prefix = name + '*' + self.fields[0][0] + '*' res = {} for key in request.keys(): if not key.startswith(prefix): continue - # I have found a row. Gets its index + # I have found a row. Gets its index. row = Object() if '_' in key: key = key[:key.index('_')] rowIndex = int(key.split('*')[-1]) @@ -140,7 +140,7 @@ class List(Field): v = subField.getRequestValue(obj, requestName=keyName) setattr(row, subName, v) res[rowIndex] = row - # Produce a sorted list. + # Produce a sorted list keys = res.keys() keys.sort() res = [res[key] for key in keys] @@ -153,7 +153,7 @@ class List(Field): return res def getStorableValue(self, obj, value): - '''Gets p_value in a form that can be stored in the database.''' + '''Gets p_value in a form that can be stored in the database''' res = [] for v in value: sv = Object() diff --git a/gen/__init__.py b/gen/__init__.py index f8a8c0c..698dbc7 100644 --- a/gen/__init__.py +++ b/gen/__init__.py @@ -29,6 +29,7 @@ from appy.fields.float import Float from appy.fields.info import Info from appy.fields.integer import Integer from appy.fields.list import List +from appy.fields.dict import Dict from appy.fields.pod import Pod from appy.fields.ref import Ref, autoref from appy.fields.string import String, Selection diff --git a/gen/descriptors.py b/gen/descriptors.py index d99a059..a1a73d1 100644 --- a/gen/descriptors.py +++ b/gen/descriptors.py @@ -319,6 +319,7 @@ class FieldDescriptor: self.i18n('%s_descr' % label, ' ') if field.hasHelp: self.i18n('%s_help' % label, ' ') + walkDict = walkList # Same i18n labels for a dict def walkCalendar(self): # Add i18n-specific messages diff --git a/gen/mixins/__init__.py b/gen/mixins/__init__.py index 7f1713a..b87162e 100644 --- a/gen/mixins/__init__.py +++ b/gen/mixins/__init__.py @@ -256,12 +256,12 @@ class BaseMixin: return dbFolder, path def view(self): - '''Returns the view PX.''' + '''Returns the view PX''' obj = self.appy() return obj.pxView({'obj': obj, 'tool': obj.tool}) def edit(self): - '''Returns the edit PX.''' + '''Returns the edit PX''' obj = self.appy() return obj.pxEdit({'obj': obj, 'tool': obj.tool}) @@ -347,8 +347,8 @@ class BaseMixin: setattr(errors, field.name, message) else: setattr(values, field.name, field.getStorableValue(self, value)) - # Validate sub-fields within Lists - if field.type != 'List': continue + # Validate sub-fields within Lists/Dicts + if field.type not in ('List', 'Dict'): continue i = -1 for row in value: i += 1 @@ -710,7 +710,7 @@ class BaseMixin: field = self.getAppyType(name) if field.type == 'Pod': return if '*' not in name: return field.getValue(self) - # The field is an inner field from a List. + # The field is an inner field from a List/Dict listName, name, i = name.split('*') listType = self.getAppyType(listName) return listType.getInnerValue(self, outerValue, name, int(i)) diff --git a/gen/ui/appy.css b/gen/ui/appy.css index 4c8e8c9..a174024 100644 --- a/gen/ui/appy.css +++ b/gen/ui/appy.css @@ -124,7 +124,7 @@ td.search { padding-top: 8px } .compact th, .compact td { padding: 0px 3px 0px 3px } .grid th { font-style: italic; font-weight: normal; border-bottom: 5px solid #fdfdfd; padding: 3px 5px 0 5px } -.grid td { padding: 3px 3px 0 3px } +.grid td { padding: 0 3px } .timeline { font-size: 85%; color: #555555 } .timeline td { text-align: center; padding: 1px } .timeline th { padding: 1px }