diff --git a/controllers/plugin_dbui.py b/controllers/plugin_dbui.py index 8c8baaf30ac6cd1e44936dbca3407562acdb5b9c..085e96d942bdf075bf27681907e9575c8c79435d 100644 --- a/controllers/plugin_dbui.py +++ b/controllers/plugin_dbui.py @@ -119,7 +119,8 @@ def get_api(): """ from gluon.contrib import simplejson as json - + dbui = local_import('plugin_dbui') + di = {} dbui = local_import('plugin_dbui') @@ -141,7 +142,7 @@ def get_api(): # the stores configuration for each table storeCfgs = {} for table in db: - storeCfgs[table._tablename] = cvtSvc.to_jsonstore(table) + storeCfgs[table._tablename] = dbui.to_jsonstore(table) # fill the javascript template app = request.application diff --git a/models/plugin_dbui.py b/models/plugin_dbui.py index 18b0c717aaeea1d26297cf8a913e6ff24c541898..7c27a738eca5d44df74c7c9f55b030042ec78a5d 100644 --- a/models/plugin_dbui.py +++ b/models/plugin_dbui.py @@ -44,27 +44,25 @@ plugins = PluginManager('dbui', # Start common services dbSvc = dbui.DbSvc(globals()) -cvtSvc = dbui.CvtSvc(globals()) directSvc = dbui.DirectSvc(globals()) # activate the debug mode # this session variable is set by the control index or debug dbSvc.debug = session.debug -cvtSvc.debug = session.debug directSvc.debug = session.debug # register method available on the client side @directSvc.register def getForm(tablename): - return cvtSvc.to_form_panel(db[tablename]) + return dbui.to_formPanel(db[tablename]) @directSvc.register def getGrid(tablename): - return cvtSvc.to_grid_panel(db[tablename]) + return dbui.to_gridPanel(db[tablename]) @directSvc.register def getTree(node): - return cvtSvc.to_tree(node) + return dbui.to_tree(node) @directSvc.register def create(data): diff --git a/models/widgets.py b/models/widgets.py index 6be3dfade3907844fba3ac2d54c5c07bf34580ca..92c718949affe59abd2d6f26e89d1fe93681f851 100755 --- a/models/widgets.py +++ b/models/widgets.py @@ -184,24 +184,23 @@ gridModifier.set_filters(*filters, # The navigation tree # formNode = dbui.Node(T('Forms')) -configurator = lambda tablename: cvtSvc.to_form_panel(db[tablename]) +configurator = lambda tablename: dbui.to_formPanel(db[tablename]) formNode.add_children(db.tables, func=configurator) gridNode = dbui.Node(T('Tables')) -configurator = lambda tablename: cvtSvc.to_grid_panel(db[tablename]) +configurator = lambda tablename: dbui.to_gridPanel(db[tablename]) gridNode.add_children(db.tables, func=configurator) reportNode = dbui.Node(T('Reports')) -node = dbui.to_panel(html='salut ma poule') +node = dbui.Panel(html='salut ma poule') reportNode.add_child(T('report_1'), node) -panel = dbui.to_panel() -fields = cvtSvc.to_form_items(dummy.foo1) -selector = dbui.to_fieldset(fields, title=T('Select')) -node = dbui.to_panelWithUrlSelector(panel, - selector, - controller="reports", - function="report_2") +fields = dbui.to_fields(dummy.foo1) +selector = dbui.FieldSet(items=fields, title=T('Select')) +node = dbui.PanelWithUrlSelector(baseUrl=URL('reports', 'report_2'), + isMathJax=dbui.is_mathjax(), + panelCfg=dbui.Panel(), + selectorCfg=selector) reportNode.add_child(T('report_2'), node) viewportModifier = dbui.ViewportModifier() diff --git a/modules/plugin_dbui/__init__.py b/modules/plugin_dbui/__init__.py index 1988a8c846673c3290292d5ed233c18ddba12da5..9f0bed21624fd620827e837b46d555538e4d8c38 100755 --- a/modules/plugin_dbui/__init__.py +++ b/modules/plugin_dbui/__init__.py @@ -2,9 +2,42 @@ """ -from cvtsvc import CvtSvc +from converter import (to_field, + to_fields, + to_formPanel, + to_gridColumn, + to_gridColumnModel, + to_gridFilter, + to_gridPanel, + to_jsonstore, + to_tree) from dbsvc import DbSvc from directsvc import DBUI, DirectSvc +from extjs import (CheckBox, + ComboBox, + CompositeField, + DateField, + DirectStore, + Field, + FieldSet, + FormPanel, + GridColumn, + GridColumnModel, + GridFilter, + GridPanel, + GridRowNumberer, + GridTemplateColumn, + GridWithFilter, + NumberField, + Panel, + PanelWithUrlSelector, + SetBox, + Spacer, + TabPanel, + TextArea, + TextField, + TimeField, + Viewport) from fieldsmodifier import FieldsModifier from formmodifier import configure_forms, FormModifier from gridmodifier import configure_grids, GridModifier @@ -27,10 +60,5 @@ from helper import (as_list, is_table_with_foreign_fields, rows_serializer) from mapper import map_default, map_tabpanel -from modifier import Spacer, Widget -from navtree import (Node, - to_fieldset, - to_panel, - to_panelWithUrlSelector, - to_urlPanel) +from navtree import Node from viewportmodifier import ViewportModifier \ No newline at end of file diff --git a/modules/plugin_dbui/converter.py b/modules/plugin_dbui/converter.py new file mode 100644 index 0000000000000000000000000000000000000000..824930a66dd32cb52dd8b1629022c87d1fba7fb3 --- /dev/null +++ b/modules/plugin_dbui/converter.py @@ -0,0 +1,550 @@ +""" Converters transforming Web2py DAL objects into ExtJS widgets. + +Author: R. Le Gac + +""" +from extjs import * +from gluon import current +from helper import (encode_field, + get_field_validators, + get_foreign_field, + get_set_field, + is_foreign_field, + is_set_field) +from mapper import map_default + +# Associated gluon.dal.field type with an ExtJS widget +# The dictionary contains configuration parameters of the Ext JS widget +FTYPE_TO_XTYPE = {'boolean': (CheckBox, {}),\ + 'date': (DateField, {'format': 'Y-m-d'}),\ + 'datetime': (DateField, {'format': 'Y-m-d H:i:s'}),\ + 'double': (NumberField, {'decimalPrecision':2, + 'decimalSeparator': '.'}),\ + 'id': (TextField, {}),\ + 'integer': (NumberField, {}),\ + 'password': (TextField, {'inputType': 'password'}),\ + 'reference': (ComboBox, {}),\ + 'string': (TextField, {}),\ + 'text': (TextArea, {}),\ + 'time': (TimeField, {}),\ + 'upload': (TextField, {'inputType': 'file'})} + + +# constant defining a ExtJS store +ROOT = 'records' +STOREID = '%sStore' +SUCCESS= 'success' +TOTAL = 'count' + + +def _to_field(field, **kwargs): + """Private converter. It is recommended to used to_field. + + Convert a gluon.dal.Field into an ExtJS.form.Field and its + inherited class. It does not handle composite field. + The conversion takes into account the FieldsModifier instructions. + + ExtJS configuration parameters are applied in the following order: + constructor, modifers, keyword arguments. + + """ + T = current.T + + fieldname = field.name + tablename = field.tablename + + f_name = encode_field(tablename, fieldname) + + # foreign key + if is_foreign_field(field): + + k_tablename, k_fieldname, k_key = get_foreign_field(field) + + cfg = ComboBox(name=f_name, + hiddenName=f_name, + displayField=encode_field(k_tablename, k_fieldname), + valueField=encode_field(k_tablename, k_key), + store=STOREID % k_tablename) + + # set field + elif is_set_field(field): + cfg = SetBox(name=f_name, + model={"setData": get_set_field(field)}) + + # other types of fields + else: + cls, di = FTYPE_TO_XTYPE[field.type] + cfg = cls(name=f_name, **di) + + # field label translate in the local languqge + cfg["fieldLabel"] = str(T(field.label)) + + # default value and not null + # NOTE: the entryForm doesn't work when both default and + # notnull condition are defined. + if field.default: + cfg["value"] = str(field.default) + elif field.notnull: + cfg["allowBlank"] = False + + # read only field + if field.readable and not field.writable: + cfg["readOnly"] = True + + # tool tip + if field.comment: + cfg["tipText"] = field.comment + + # validator constraints + cfg.update(get_field_validators(field)) + + # hide primary key + # hide field which are not readable and not writable + # hide field request by the form modifier + hidden = field.name == "id" + hidden = hidden or ((not field.readable) and (not field.writable)) + + modifier_forms = PluginManager('dbui').dbui.modifier_forms + if tablename in modifier_forms: + if fieldname in modifier_forms[tablename].hidden_fields: + hidden = True + + if hidden: + cfg["hidden"] = True + cfg["hideLabel"] = True + cfg["readOnly"] = True + + # configuration options set by the field_modifers + modifier_fields = PluginManager('dbui').dbui.modifier_fields + if tablename in modifier_fields: + if fieldname in modifier_fields[tablename].extjs_fields: + cfg.update(modifier_fields[tablename].extjs_fields[fieldname]) + + # configuration options from the keyword arguments + cfg.update(kwargs) + + return cfg + + +def to_field(field, **kwargs): + """Convert a gluon.dal.Field into an Ext.form.Field and its inherited class + as well as into a composite field, i.e Ext.form.CompositeField. + + Composite fields are set using the FieldsModifer. + + Return None if the field is comsumed by a CompositeField. + + ExtJS configuration parameters are applied in the following order: + constructor, modifers, keyword arguments. + + """ + table = field.table + tablename = field.tablename + modifier_fields = PluginManager('dbui').dbui.modifier_fields + + # do we have composite field for this table ? + composite_fields = None + if tablename in modifier_fields: + modifier_field = modifier_fields[tablename] + composite_fields = modifier_field.composite_fields + + # main field of the composite field + # it will consume the embedded field too + if composite_fields and field.name in composite_fields.main: + + cfg = CompositeField() + + for fieldname in composite_fields[field.name].fields: + cfg.append_items(_to_field(table[fieldname])) + + # use configuration options from the modifier + cfg.update(composite_fields[field.name].extjs) + + # an embedded field already used in a composite field + elif composite_fields and field.name in composite_fields.others: + cfg = None + + # a basic field + else: + cfg = _to_field(field) + + # configuration options from keyword arguments + if cfg: + cfg.update(kwargs) + + return cfg + + +def to_fields(table): + """Convert a gluon.dal.Table into a list of Ext.form.Field, + Ext.form.CompositeField or and Ext.form.FieldSet. + + Individual fields can be organized in Fieldset and in CompositeField. + They are defined in the application model using modifiers namely + the FormModifier and the FieldsModifier respectively. + + The conversion takes into account the modifier instructions. + + """ + li = [] + tablename = table._tablename + modifier_forms = PluginManager('dbui').dbui.modifier_forms + + # do we have FieldSets + field_sets, modifier_form = None, None + if tablename in modifier_forms: + modifier_form = modifier_forms[tablename] + if modifier_form and modifier_form.field_sets: + field_sets = modifier_form.field_sets + + # Table with Ext.form.FieldSet and/or Ext.form.CompositeFields + # and/or Ext.form.Field and/or Spacer + + # NOTE: the field id is add in order to run this form + # in all context, create, update, destroy. + # It is add to the latest fieldset. + id_is_used = False + + if field_sets: + for fieldset in field_sets: + + cfg = FieldSet() + for fieldname in fieldset.fields: + + if isinstance(fieldname, Base): + cfg.append_items(fieldname) + continue + + if fieldname == 'id': + id_is_used = True + + field = table[fieldname] + item = to_field(field) + if item: + cfg.append_items(item) + + # fieldset configuration options ser by the modifier + cfg.update(fieldset.extjs) + li.append(cfg) + + # append the field Id the last field set + if not id_is_used: + li[-1].append_items(to_field(table['id'])) + + # Table with Ext.form.CompositeFields and/or Ext.form.Fields + else: + for field in table: + cfg = to_field(field) + if cfg: + li.append(cfg) + + # map the list of fields/fieldSets on Ext.form.formPanel + mapper = map_default + if modifier_form and modifier_form.mapper: + mapper = modifier_form.mapper + + li = mapper(li) + + return li + + +def to_formPanel(table, **kwargs): + """Convert a gluon.dal.Table into a ExtJS App.form.FormPanel. + The conversion takes into account the form modifier instructions. + + ExtJS configuration parameters are applied in the following order: + constructor, modifers, keyword arguments. + + """ + tablename = table._tablename + + fields = to_fields(table) + cfg = FormPanel(items=fields) + + # add a store for non dummy database + if table._db._uri: + cfg['store'] = STOREID % tablename + + # configuration options from form modifier + modifier_forms = PluginManager('dbui').dbui.modifier_forms + if tablename in modifier_forms: + cfg.update(modifier_forms[tablename].extjs) + + # configuration options form the keyword arguments + cfg.update(kwargs) + + return cfg + + +def to_gridColumn(field, **kwargs): + """Convert a gluon.dal.Field into an Ext.grid.Column. + The conversion takes into account the grid modifier instructions. + + ExtJS configuration parameters are applied in the following order: + constructor, modifers, keyword arguments. + + """ + T = current.T + + fieldname = field.name + tablename = field.tablename + + cfg = GridColumn(header=str(T(field.label)), + sortable=True) + + # name of the field in the data store + # replace foreign key by the pointing column + if is_foreign_field(field): + k_tablename, k_fieldname, k_key = get_foreign_field(field) + index = encode_field(k_tablename, k_fieldname) + + else: + index = encode_field(tablename, fieldname) + + cfg["dataIndex"] = index + + # hide the primary key + if fieldname == "id": + cfg["hidden"] = True + + # hide field which are not readable and not writable + if (not field.readable) and (not field.writable): + cfg["hidden"] = True + + # Hide fields request by the grid modifiers + modifier_grids = PluginManager('dbui').dbui.modifier_grids + if tablename in modifier_grids: + if fieldname in modifier_grids[tablename].hidden_columns: + cfg["hidden"] = True + + # configuration options from the grid modifier + if tablename in modifier_grids: + if fieldname in modifier_grids[tablename].configure_columns: + cfg.update(modifier_grids[tablename].configure_columns[fieldname]) + + # configuration options from the keyword arguments + cfg.update(kwargs) + + return cfg + + +def to_gridColumnModel(table): + """Convert a gluon.dal.Table into an Ext.grid.ColumnModel. + The conversion takes into account the grid modifier instructions. + + Currently the gridColumModel is implemented as a list of gridColumn. + + """ + delete_columns = [] + template_columns = [] + tablename = table._tablename + + # get modifier requirements + modifier_grids = PluginManager('dbui').dbui.modifier_grids + if tablename in modifier_grids: + delete_columns = modifier_grids[tablename].delete_columns + template_columns = modifier_grids[tablename].template_columns + + # standard column + cfg = GridColumnModel() + for field in table: + fieldname = field.name + if fieldname not in delete_columns: + cfg.append(to_gridColumn(field)) + + # template column + for tpl in template_columns: + col = GridTemplateColumn(sortable=True) + col.update(tpl.extjs) + cfg.insert(tpl.position, col) + + # row numbering in the first column + if modifier_grids[tablename].row_numbering: + cfg.insert(0, GridRowNumberer()) + + return cfg + + +def to_gridFilter(table, **kwargs): + """Convert a gluon.dal.Table into an ExtJS App.grid.GridFilter. + The grid filter is set using the grid modifiers. + Return an empty dictionary if not defined + + ExtJS configuration parameters are applied in the following order: + constructor, modifers, keyword arguments. + + """ + T = current.T + tablename = table._tablename + + modifier_grids = PluginManager('dbui').dbui.modifier_grids + if tablename not in modifier_grids: + return {} + + grid_filters = modifier_grids[tablename].grid_filters + if not grid_filters: + return {} + + di = GridFilter(title=T('Filter %s' % tablename)) + + for (fieldname, operator, comment) in grid_filters.filters: + field = table[fieldname] + + # get the standard configuration for the field + cfg = to_field(field) + + # remove default value for fields which are not comboBox + if cfg['xtype'] != 'xcombobox': + cfg['value'] = '' + cfg['allowBlank'] = True + + # replace all textarea by textfield + if cfg['xtype'] == 'textarea': + cfg['xtype'] = 'textfield' + + # prepare the name for the where close + cfg['name'] = '[%s.%s]' % (tablename, fieldname) + + # store information to customize the filter + cfg['filterComment'] = comment + cfg['filterOperator'] = operator + cfg['filterType'] = field.type + + di.append_items(cfg) + + # configuration options from the grid modifier + di.update(grid_filters.extjs) + + # configuration option from keyword arguments + di.update(kwargs) + + return di + + +def to_gridPanel(table, **kwargs): + """Convert a gluon.dal.Table into an ExtJS App.grid.GridPanel. + The conversion takes into account the grid modifier instructions. + + ExtJS configuration parameters are applied in the following order: + constructor, modifers, keyword arguments. + + """ + tablename = table._tablename + + cfg = GridPanel(columns=to_gridColumnModel(table), + frame=False, + viewConfig={'forceFit': True}) + + # add a store for non dummy database + if table._db._uri: + cfg['store'] = STOREID % tablename + + # configuration option from the grid modifier + modifier_grids = PluginManager('dbui').dbui.modifier_grids + if tablename in modifier_grids: + cfg.update(modifier_grids[tablename].extjs) + + # configuration option from the keyword arguments + cfg.update(kwargs) + + # grid with filter + filter = to_gridFilter(table) + if filter: + cfg = GridWithFilter(panelCfg=dict(cfg), + selectorCfg=filter) + + return cfg + + +def to_jsonstore(table, **kwargs): + """Convert a gluon.dal.Table into an App.data.DirectStore. + + ExtJS configuration parameters are applied in the following order: + constructor, modifers, keyword arguments. + + """ + db = table._db + tablename = table._tablename + + # NOTE: the configuration option baseParams defined the + # transactions parameters seen by the server + # This dictionary contains 3 keys: + # tablename: name of the table in the database + # dbFields: list of fields which should be red + # where: define inner join, mainly to resolve foreign key + base_params = {'tableName': tablename, 'dbFields': []} + + cfg = DirectStore(autoLoad=False, + autoSave=True, + baseParams=base_params, + fields=[], + idProperty=encode_field(tablename,'id'), + root=ROOT, + successProperty=SUCCESS, + storeId=STOREID % tablename, + totalProperty=TOTAL) + + for field in table: + fieldname = field.name + + # the field of the table + # encode field name and define default value + field_cfg = {'name': encode_field(tablename, fieldname)} + if field.default: + field_cfg['defaultValue'] = str(field.default) + + base_params['dbFields'].append([tablename, fieldname]) + cfg['fields'].append(field_cfg) + + # NOTE: the store contains all fields of the table. + # It also contains the pointing values for foreign keys + # Therefore the store has the the id and the value for + # each foreign key. This technique is use to resolve foreign key + # in form (combobox) and in grid. + if is_foreign_field(field): + + if 'where' not in base_params: base_params['where'] = [] + k_tablename, k_fieldname, k_key = get_foreign_field(field) + + # encode the field name + k_field_cfg = {'name': encode_field(k_tablename, k_fieldname)} + base_params['dbFields'].append([k_tablename, k_fieldname]) + cfg['fields'].append(k_field_cfg) + + # retrieve the default value for the foreign key + if field.default: + row = db(db[k_tablename].id == field.default).select().first() + if row: + k_field_cfg['defaultValue'] = row[k_fieldname] + + # define the where close + nt = (tablename, fieldname, k_tablename, k_key) + base_params['where'].append("[%s.%s] == [%s.%s]" % nt) + + # configuration options from the keyword arguments + cfg.update(kwargs) + + return cfg + + +def to_tree(node): + """Convert the Node structure into a list of dictionary + configuring Ext.tree.TreeNode. + + The application navigation tree is defined using ViewportModifier. + + The argument node is required by ExtJS 3.4 but it is not + used here. + + """ + cfg = [] + + modifier_viewports = PluginManager('dbui').dbui.modifier_viewports + + if modifier_viewports: + nav_nodes = modifier_viewports.nodes + for node in nav_nodes: + cfg.append(node.get_node()) + + return cfg + diff --git a/modules/plugin_dbui/cvtsvc.py b/modules/plugin_dbui/cvtsvc.py deleted file mode 100644 index fef95b88f8d8a01b39969d36cbb08e5faadf85f2..0000000000000000000000000000000000000000 --- a/modules/plugin_dbui/cvtsvc.py +++ /dev/null @@ -1,526 +0,0 @@ -""" Service to convert gluon.dal object (Table, Field) into configuration -dictionary for ExtJS widget (Ext.form.FormPanel, Ext.grid.GridPanel, -Ext.data.DirectJsonStore). - -It can also convert Node into Ext.tree.TreeNode - -The table can belong to a to a dummy database. -It is useful to create standalone form with the powerful validation -mechanism provided by web2py. - -""" - -import modifier - -from basesvc import BaseSvc -from gluon.dal import Field -from helper import (encode_field, - get_field_validators, - get_foreign_field, - get_set_field, - is_foreign_field, - is_set_field) -from mapper import map_default -from modifier import Widget - - -# Convert a field type into a widget xtype -# The dictionary contain the configuration parameters of the Ext JS widget -FTYPE_TO_XTYPE = {'boolean': {'xtype': 'checkbox'},\ - 'date': {'xtype': 'datefield', 'format': 'Y-m-d'},\ - 'datetime': {'xtype': 'datefield', 'format': 'Y-m-d H:i:s'},\ - 'double': {'xtype': 'numberfield', 'decimalPrecision':2, - 'decimalSeparator': '.'},\ - 'id': {'xtype': 'textfield'},\ - 'integer': {'xtype': 'numberfield'},\ - 'password': {'xtype': 'textfield', 'inputType': 'password'},\ - 'reference': {'xtype': 'xcombobox'},\ - 'string': {'xtype': 'textfield'},\ - 'text': {'xtype': 'textarea'},\ - 'time': {'xtype': 'timefield'},\ - 'upload': {'xtype': 'textfield', 'inputType': 'file'}} - -# constant defining a store -ROOT = 'records' -STOREID = '%sStore' -SUCCESS= 'success' -TOTAL = 'count' - - -class CvtSvc(BaseSvc): - - - def __init__(self, environment): - BaseSvc.__init__(self, environment) - - - def to_column_model(self, field): - """Convert a gluon.dal.Field into a configuration dictionary - for and Ext.grid.Column. - - """ - T = self.environment['T'] - - fieldname = field.name - tablename = field.tablename - model = {'sortable': True} - - # column header - model["header"] = str(T(field.label)) - - # replace foreign key by the pointing column - if is_foreign_field(field): - - k_tablename, k_fieldname, k_key = get_foreign_field(field) - model["dataIndex"] = encode_field(k_tablename, k_fieldname) - - # standard column - else: - model["dataIndex"] = encode_field(tablename, fieldname) - - # hide the primary key - if fieldname == "id": - model["hidden"] = True - - # hide field which are not readable and not writable - if (not field.readable) and (not field.writable): - model["hidden"] = True - - # Hide fields request by the grid modifiers - modifier_grids = self.environment['plugins'].dbui.modifier_grids - if tablename in modifier_grids: - if fieldname in modifier_grids[tablename].hidden_columns: - model["hidden"] = True - - return model - - - def to_columns_models(self, table): - """Convert a gluon.dal.Table into a configuration dictionary - for an the Ext.grid.ColumnModel. - - """ - - models = [] - delete_columns = [] - template_columns = [] - - tablename = table._tablename - - # get modifier requirements - modifier_grids = self.environment['plugins'].dbui.modifier_grids - if tablename in modifier_grids: - configure_columns = modifier_grids[tablename].configure_columns - delete_columns = modifier_grids[tablename].delete_columns - template_columns = modifier_grids[tablename].template_columns - - # standard column - for field in table: - fieldname = field.name - if fieldname not in delete_columns: - model = self.to_column_model(field) - if fieldname in configure_columns: - model.update(configure_columns[fieldname]) - models.append(model) - - # template column - for tpl in template_columns: - col = {'xtype': 'templatecolumn', 'sortable': True} - col.update(tpl.extjs) - models.insert(tpl.position, col) - - # row numbering in the first column - if modifier_grids[tablename].row_numbering: - models.insert(0, {'xtype': 'rownumberer'}) - - - return models - - - def to_field_item(self, field): - """Convert a gluon.dal.Field into a configuration dictionary - for an Ext.formField. - - The field can also be translated into an Ext.form.CompositeField. - if it corresponds to the field field of the composite field. - Composite field are define using the FieldsModifer. - - Return None if the field is used in a CompositeField - - """ - modifier_fields = self.environment['plugins'].dbui.modifier_fields - table = field.table - tablename = field.tablename - - # do we have composite field for this table ? - composite_fields = None - if tablename in modifier_fields: - modifier_field = modifier_fields[tablename] - composite_fields = modifier_field.composite_fields - - # main field of the composite field - # it will consume the embedded field too - if composite_fields and field.name in composite_fields.main: - - cfg = {'xtype': 'compositefield', 'items':[]} - - for fieldname in composite_fields[field.name].fields: - cfg['items'].append(self.to_field(table[fieldname])) - - cfg.update(composite_fields[field.name].extjs) - return cfg - - # an embedded field already used in a composite field - elif composite_fields and field.name in composite_fields.others: - return None - - # a standard field - else: - return self.to_field(field) - - - def to_field(self, field): - """Convert a gluon.dal.Field into a configuration dictionary for - an ExtJS.form.Field. - - The configuration dictionary can be tune using the FieldsModifier. - - """ - T = self.environment['T'] - - fieldname = field.name - tablename = field.tablename - - cfg= {"name": encode_field(tablename, fieldname)} - - # foreign key - if is_foreign_field(field): - - k_tablename, k_fieldname, k_key = get_foreign_field(field) - - cfg["xtype"] = "xcombobox" - cfg["hiddenName"] = encode_field(tablename, fieldname) - cfg["displayField"] = encode_field(k_tablename, k_fieldname) - cfg["valueField"] = encode_field(k_tablename, k_key) - cfg["store"] = STOREID % k_tablename - - # set field - elif is_set_field(field): - cfg["xtype"] = "xsetbox" - cfg["model"] = {"setData": get_set_field(field)} - - # other types of fields - else: - cfg.update(FTYPE_TO_XTYPE[field.type]) - - # field label - cfg["fieldLabel"] = str(T(field.label)) - - # default value and not null - # NOTE: the entryForm doesn't work when both default and - # notnull condition are defined. - if field.default: - cfg["value"] = str(field.default) - elif field.notnull: - cfg["allowBlank"] = False - - # read only field - if field.readable and not field.writable: - cfg["readOnly"] = True - - # tool tip - if field.comment: - cfg["tipText"] = field.comment - - # validators - cfg.update(get_field_validators(field)) - - # hide primary key - # hide field which are not readable and not writable - # hide field request by the form modifier - hidden = field.name == "id" - hidden = hidden or ((not field.readable) and (not field.writable)) - - modifier_forms = self.environment['plugins'].dbui.modifier_forms - if tablename in modifier_forms: - if fieldname in modifier_forms[tablename].hidden_fields: - hidden = True - - if hidden: - cfg["hidden"] = True - cfg["hideLabel"] = True - cfg["readOnly"] = True - - # configuration options set by the field_modifers - modifier_fields = self.environment['plugins'].dbui.modifier_fields - if tablename in modifier_fields: - if fieldname in modifier_fields[tablename].extjs_fields: - cfg.update(modifier_fields[tablename].extjs_fields[fieldname]) - - return cfg - - - def to_form_items(self, table): - """Convert a gluon.dal.Table into a list of Ext.form.Field, - Ext.form.CompositeField or and Ext.form.FieldSet. - - Composite fields are defined using FieldsModifier. - Field sets are defined using FormModifier. - - """ - - items = [] - - modifier_forms = self.environment['plugins'].dbui.modifier_forms - tablename = table._tablename - - # do we have FieldSets - field_sets, modifier_form = None, None - if tablename in modifier_forms: - modifier_form = modifier_forms[tablename] - if modifier_form and modifier_form.field_sets: - field_sets = modifier_form.field_sets - - # Table with Ext.form.FieldSet and/or Ext.form.CompositeFields - # and/or Ext.form.Field and/or Spacer - - # NOTE: the field id is add in order to run this form - # in all context, create, update, destroy. - # It is add to the latest fieldset. - id_is_used = False - - if field_sets: - for fieldset in field_sets: - - cfg = {'xtype': 'fieldset', 'items': []} - items.append(cfg) - - for fieldname in fieldset.fields: - - if isinstance(fieldname, Widget): - cfg['items'].append(fieldname.extjs) - continue - - if fieldname == 'id': - id_is_used = True - - field = table[fieldname] - item = self.to_field_item(field) - if item: - cfg['items'].append(item) - - cfg.update(fieldset.extjs) - - # append the field Id the last field set - if not id_is_used: - items[-1]['items'].append(self.to_field(table['id'])) - - # Table with Ext.form.CompositeFields and/or Ext.form.Fields - else: - for field in table: - item = self.to_field_item(field) - if item: - items.append(item) - - # map the list of fields/fieldSets on Ext.form.formPanel - mapper = map_default - if modifier_form and modifier_form.mapper: - mapper = modifier_form.mapper - - items = mapper(items) - - return items - - - def to_form_panel(self, table): - """Convert a gluon.dal.Table into a configuration dictionary - for the App.form.FormPanel. - - the configuration dictionary can be tunes using the FormModifier. - - """ - tablename = table._tablename - - cfg = {'items': self.to_form_items(table), - 'xtype': 'xform'} - - # add a store for non dummy database - if table._db._uri: - cfg['store'] = STOREID % tablename - - # handle form modifier - modifier_forms = self.environment['plugins'].dbui.modifier_forms - if tablename in modifier_forms: - cfg.update(modifier_forms[tablename].extjs) - - return cfg - - - def to_grid_filter(self, table): - """Get configuration dictionary for the Ext.grid.GridFiler. - A grid filter is defined using the GridModifier. - - """ - T = self.environment['T'] - tablename = table._tablename - - modifier_grids = self.environment['plugins'].dbui.modifier_grids - if tablename not in modifier_grids: - return {} - - grid_filters = modifier_grids[tablename].grid_filters - if not grid_filters: - return {} - - di = {'xtype':'xgridfilter', - 'items': [], - 'title': T('Filter %s' % tablename)} - - for (fieldname, operator, comment) in grid_filters.filters: - field = table[fieldname] - - # get the standard configuration for the field - cfg = self.to_field(field) - - # remove default value for fields which are not comboBox - if cfg['xtype'] != 'xcombobox': - cfg['value'] = '' - cfg['allowBlank'] = True - - # replace all textarea by textfield - if cfg['xtype'] == 'textarea': - cfg['xtype'] = 'textfield' - - # prepare the name for the where close - cfg['name'] = '[%s.%s]' % (tablename, fieldname) - - # store information to customize the filter - cfg['filterComment'] = comment - cfg['filterOperator'] = operator - cfg['filterType'] = field.type - - di['items'].append(cfg) - - # user configuration options for Ext.form.FieldSet - di.update(grid_filters.extjs) - - return di - - - def to_grid_panel(self, table): - """Convert a gluon.dal.Table into a configuration dictionary - for the App.grid.GridPanel. - - The configuration dictionary can be tuned using the GridModifier. - - """ - tablename = table._tablename - - cfg = {'columns': self.to_columns_models(table), - 'frame': False, - 'store': STOREID % tablename, - 'viewConfig': {'forceFit': True}, - 'xtype': 'xgrid'} - - # handle the user configuration option for Ext.grid.GridPanel - modifier_grids = self.environment['plugins'].dbui.modifier_grids - if tablename in modifier_grids: - cfg.update(modifier_grids[tablename].extjs) - - # grid with filter - filter = self.to_grid_filter(table) - if filter: - cfg = {'panelCfg': dict(cfg), - 'selectorCfg': filter, - 'xtype': 'xgridwithfilter'} - - return cfg - - - def to_jsonstore(self, table): - """convert a gluon.dal.Table into a configuration dictionary - for the Ext.data.DirectJsonStore. - - """ - db = table._db - tablename = table._tablename - - # NOTE: the configuration option baseParams defined the - # transactions parameters seen by the server - # This dictionary contains 3 keys: - # tablename: name of the table in the database - # dbFields: list of fields which should be red - # where: define inner join, mainly to resolve foreign key - base_params = {'tableName': tablename, 'dbFields': []} - - cfg = {'autoLoad': False, - 'autoSave': True, - 'baseParams': base_params, - 'fields': [], - 'idProperty': encode_field(tablename,'id'), - 'root': ROOT, - 'successProperty': SUCCESS, - 'storeId': STOREID % tablename, - 'totalProperty': TOTAL, - 'xtype': 'xdirectstore'} - - for field in table: - fieldname = field.name - - # the field of the table - # encode field name and define default value - field_cfg = {'name': encode_field(tablename, fieldname)} - if field.default: - field_cfg['defaultValue'] = str(field.default) - - base_params['dbFields'].append([tablename, fieldname]) - cfg['fields'].append(field_cfg) - - # NOTE: the store contains all fields of the table. - # It also contains the pointing values for foreign keys - # Therefore the store has the the id and the value for - # each foreign key. This technique is use to resolve foreign key - # in form (combobox) and in grid. - if is_foreign_field(field): - - if 'where' not in base_params: base_params['where'] = [] - k_tablename, k_fieldname, k_key = get_foreign_field(field) - - # encode the field name - k_field_cfg = {'name': encode_field(k_tablename, k_fieldname)} - base_params['dbFields'].append([k_tablename, k_fieldname]) - cfg['fields'].append(k_field_cfg) - - # retrieve the default value for the foreign key - if field.default: - row = db(db[k_tablename].id == field.default).select().first() - if row: - k_field_cfg['defaultValue'] = row[k_fieldname] - - # define the where close - nt = (tablename, fieldname, k_tablename, k_key) - base_params['where'].append("[%s.%s] == [%s.%s]" % nt) - - return cfg - - - def to_tree(self, node): - """Convert the Node structure into a list of dictionary - configuring Ext.tree.TreeNode. - - Teh application navigation tree is defined using ViewportModifier. - - The argument node is required by ExtJS 3.4 but it is not - used here. - - """ - cfg = [] - - modifier_viewports = self.environment['plugins'].dbui.modifier_viewports - - if modifier_viewports: - nav_nodes = modifier_viewports.nodes - for node in nav_nodes: - cfg.append(node.get_node()) - - return cfg diff --git a/modules/plugin_dbui/dbsvc.py b/modules/plugin_dbui/dbsvc.py index cc61c52f27f92779f7cbb88e1db11a282e8b305a..d84417d1d483aa091f747dd3d480b1be03d66fcb 100644 --- a/modules/plugin_dbui/dbsvc.py +++ b/modules/plugin_dbui/dbsvc.py @@ -3,7 +3,7 @@ """ from basesvc import BaseSvc -from cvtsvc import ROOT, SUCCESS, TOTAL +from converter import ROOT, SUCCESS, TOTAL from gluon.contrib import simplejson as json from helper import (encode_field, decode_field, diff --git a/modules/plugin_dbui/extjs.py b/modules/plugin_dbui/extjs.py new file mode 100644 index 0000000000000000000000000000000000000000..4b55ab33f65686582c3f8d6a45081eb0e2af425a --- /dev/null +++ b/modules/plugin_dbui/extjs.py @@ -0,0 +1,184 @@ +""" ExtJS components configurators + +Author: R. Le Gac + +""" + +from gluon import current +from gluon.tools import PluginManager + +MSG_XTYPE = 'Invalid keyword "xtype"' + + +class ExtJSException(BaseException): pass + + +class Base(dict): + """Base class for ExtJS configurator. + A protection is set to avoid changing the xtype of the element. + + Two helper functions append_items and append_plugins + to deal with items and plugins. + + """ + xtype = None + + def __init__(self, **kwargs): + + if 'xtype' in kwargs: + raise ExtJSException(MSG_XTYPE) + + self['xtype'] = self.xtype + self.update(kwargs) + + + def _append(self, key, t): + li = list(t) + if key in self: + self[key].extend(li) + else: + self[key] = li + + + def append_items(self, *args): + self._append('items', args) + + + def append_plugins(self, *args): + self._append('plugins', args) + + +class CheckBox(Base): + """Configurator for Ext.form.Checkbox.""" + xtype = 'checkbox' + + +class ComboBox(Base): + """Configurator for App.form.CombBox.""" + xtype = 'xcombobox' + + +class CompositeField(Base): + """Configurator for Ext.form.CompositeField.""" + xtype = 'compositefield' + + +class DateField(Base): + """Configurator for Ext.form.DateField.""" + xtype = 'datefield' + + +class DirectStore(Base): + """Configurator for App.data.DirectStore.""" + xtype = 'xdirectstore' + + +class Field(Base): + """Configurator for Ext.form.Field.""" + xtype = 'field' + + +class FieldSet(Base): + """Configurator for Ext.form.FieldSet. + + By default, the layout for children Field is set to anchor 99%. + It can be overwritten using the keywords defaults. + + """ + xtype = 'fieldset' + + def __init__(self, **kwargs): + self['defaults'] = {'anchor': '99%'} + Base.__init__(self, **kwargs) + + +class FormPanel(Base): + """Configurator for App.form.FormPanel.""" + xtype = 'xform' + + +class GridColumn(dict): + """Configurator for Ext.grid.Column.""" + + +class GridColumnModel(list): + """Configurator for Ext.grid.ColumnModel implemented + as a list of GridColumn. + + """ + + +class GridFilter(Base): + """Configurator for App.grid.GridFilter.""" + xtype = 'xgridfilter' + + +class GridPanel(Base): + """Configurator for App.grid.Grid.""" + xtype = 'xgrid' + + +class GridRowNumberer(dict): + """Configurator for Ext.grid.RowNumberer.""" + xtype = 'rownumberer' + + +class GridTemplateColumn(Base): + """Configurator for Ext.grid.TemplateColumn.""" + xtype = 'templatecolumn' + + +class GridWithFilter(Base): + """Configurator for App.grid.GridWithFilter.""" + xtype = 'xgridwithfilter' + + +class NumberField(Base): + """Configurator for Ext.form.NumberField.""" + xtype = 'numberfield' + + +class Panel(Base): + """Configurator for Ext.Panel.""" + xtype = 'panel' + + +class PanelWithUrlSelector(Base): + """Configurator for App.PanelWithUrlSelector.""" + xtype = 'xpanelwithurlselector' + + +class SetBox(Base): + """Configurator for App.form.SetBox.""" + xtype = 'xsetbox' + + +class Spacer(Base): + """Configurator for Ext.Spacer.""" + xtype = 'spacer' + + +class TabPanel(Base): + """Configurator for Ext.TabPanel.""" + xtype = 'tabpanel' + + +class TextArea(Base): + """Configurator for Ext.form.TextArea.""" + xtype = 'textarea' + + +class TextField(Base): + """Configurator for Ext.form.TextField.""" + xtype = 'textfield' + + +class TimeField(Base): + """Configurator for Ext.form.TimeField.""" + xtype = 'timefield' + + +class Viewport(Base): + """Configurator for App.Viewport.""" + xtype = 'xviewport' + \ No newline at end of file diff --git a/modules/plugin_dbui/mapper.py b/modules/plugin_dbui/mapper.py index 5a55f4b57c049864905036126dcac910a8a31065..d0fba586d378c536222e7cbb77f8a861dc99d57b 100644 --- a/modules/plugin_dbui/mapper.py +++ b/modules/plugin_dbui/mapper.py @@ -1,6 +1,8 @@ """ Author: R. Le Gac """ +from extjs import TabPanel + def map_default(fields): """Map a list of field in the Ext.form.formPanel @@ -18,10 +20,9 @@ def map_tabpanel(fieldsets): Return the items list for the Ext.form.FormPanel. """ - tabpanel = {'activeTab': 0, - 'defaults': {'anchor': '100%'}, - 'items': [], - 'xtype': 'tabpanel'} + + tabpanel = TabPanel(activeTab=0, + defaults={'anchor': '98%'}) for fieldset in fieldsets: @@ -33,6 +34,6 @@ def map_tabpanel(fieldsets): 'items': fieldset, 'title': title} - tabpanel['items'].append(tab) + tabpanel.append_items(tab) return [tabpanel] \ No newline at end of file diff --git a/modules/plugin_dbui/modifier.py b/modules/plugin_dbui/modifier.py index 82008f1ff2bdcee618be216f6d5c4a6bfa152913..c0cb223c850302622f6a0ce4eb632e47da869e51 100644 --- a/modules/plugin_dbui/modifier.py +++ b/modules/plugin_dbui/modifier.py @@ -79,21 +79,3 @@ class Modifier(object): """ self.data.extjs.update(extjs) - - -class Widget(object): - """Base class defining a the configuration of an ExtJS widget. - - """ - def __init__(self, xtype, **kwargs): - self.extjs = dict(xtype=xtype, **kwargs) - - -class Spacer(Widget): - """Define a spacer which can be add when combining fields in fieldset - or in field container. - - """ - def __init__(self, **kwargs): - Widget.__init__(self, 'spacer', **kwargs) - diff --git a/modules/plugin_dbui/navtree.py b/modules/plugin_dbui/navtree.py index b5b9e39cc349d670183d4a1119a3bbffd4a86e40..9b70e582954b519a08185ba544a9d3e14264f71e 100644 --- a/modules/plugin_dbui/navtree.py +++ b/modules/plugin_dbui/navtree.py @@ -3,141 +3,7 @@ """ import locale -import os from gluon import current -from helper import is_mathjax - - -def to_panel(**kwargs): - """Return the configuration dictionary of an Ext.Panel. - - The keyword arguments are the configuration options of - the Ext.Panel widget. - - """ - cfg = {'xtype': 'panel'} - cfg.update(kwargs) - return cfg - - -def to_fieldset(fields, **kwargs): - """Return the confiuration dictionary of Ext.form.Fieldset. - This widget contains list of Ext.form.Field. - - fields is a list of configuration dictioanry for form.Fields. - - The keyword arguments are the configuration options of - the Ext.form.FieldSet widget. - - """ - cfg = {'defaults': {'anchor': '99%'}, - 'items': fields, - 'xtype': 'fieldset'} - cfg.update(kwargs) - return cfg - - -def to_panelWithUrlSelector(panelCfg, - selectorCfg, - baseUrl=None, - application=None, - controller=None, - function=None, - ctrlField=None, - extField=None, - funcField=None): - """Return the configuration dictionary for an App.PanelWithUrlSelector. - - This widget is split in two part a main panel and a selector. - The selector display a list of fields organized in fieldset. - There values define the URL parameters. The main panel displays - the URL content. - - Configuration dictionary for the main panel and fieldset are set via: - - panelCfg - - selectorCfg - - In web2py an URL is defined as application/controller/function.extension - There are several ways to define it. - - application - The base URL is: /application - By default the application is the current application. - - baseUrl - Another way to defined the URL associated to the panel. - It should be a well-formed URL string, i.e http://blabla. - It is a more general approach where the URL can point to a - any links. - - controller - The base URL is: /application/controller. - - function - The base URL is: /application/controller/function. - - The URL can be modified dynamicaly by extracting the name of the - controller, function and/or extension from the selector fields. - - ctrlField - Name of the field defining the name of the controller. - When define the URL become baseURL/ctrlFieldValue. - - extField - Name of the field defining the name of the extension. - When define the URL become baseUrl.extFieldValue. - Useful to play with different view rendering the same - controller/function. - - funcField - Name of the form field defining the name of the function. - When define the URL become baseURL/funcFieldValue. - To be used with ctrlField. In that case the URL is - /application/ctrFieldValue/funcFieldValue. - - """ - url = baseUrl - if not url: - if application: - url = os.path.join('/', application) - else: - url = os.path.join('/', current.request.application) - - if controller: - url = os.path.join(url, controller) - - if function: - url = os.path.join(url, function) - - cfg = {'baseUrl': url, - 'ctrlField': ctrlField, - 'extField': extField, - 'funcField': funcField, - 'isMathJax': is_mathjax(), - 'panelCfg': panelCfg, - 'selectorCfg': selectorCfg, - 'xtype': 'xpanelwithurlselector'} - - return cfg - - -def to_urlPanel(url): - """Return the configuration dictionary for an Ext.Panel - displaying an URL. - - url - well-formed URL string, i.e http://blabla - - """ - cfg = to_panel(autoload=url, - preventBodyReset=True) - - if is_mathjax(): - cfg['plugins'] = ['pPanelMathJax'] - - return cfg class Node(object): diff --git a/static/plugin_dbui/CHANGELOG b/static/plugin_dbui/CHANGELOG index ec45b714f5b3b5a9fe2505edf460a4caeebb1e15..4c2eda0ad6e338f6f6b25b2d5fa3519ad037181d 100644 --- a/static/plugin_dbui/CHANGELOG +++ b/static/plugin_dbui/CHANGELOG @@ -2,8 +2,11 @@ HEAD - Bugs fixed - - Add the configuration parameter extField in App.PanelWithUrlSelector. - Design to manipulate different web2py views like foo.html, foo.xml, ... + - More general configuration for App.PanelWithUrlSelector. + - Major redesign of the configuration section. Remove the CvtSvc and + add two modules extjs and converter. The first one contains a serie + of configurators mapping ExtJS components while the second one contained + functions translating DAL object into ExtJS configurator. 0.4.5 (Feb 2012) - Consolidation version diff --git a/static/plugin_dbui/src/appviewport.js b/static/plugin_dbui/src/appviewport.js index 23f7b79b299de54f56cc28a9a3ed59169d3585a0..05ca3e6027cf0bbf98ac745bc94ed7c95fe840aa 100644 --- a/static/plugin_dbui/src/appviewport.js +++ b/static/plugin_dbui/src/appviewport.js @@ -142,3 +142,5 @@ App.Viewport = Ext.extend(Ext.Viewport, { this.tabPanel.setActiveTab(tabId); } }); + +Ext.reg('xviewport', App.Viewport); \ No newline at end of file