From f00262fe4de3fcbf5d039d434fe1607b941b9198 Mon Sep 17 00:00:00 2001 From: Jean-Paul Smets <jp@nexedi.com> Date: Tue, 25 Jan 2005 10:10:31 +0000 Subject: [PATCH] Added new method to provide some kind of auto completion and simplify the user interface git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@2282 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5Form/RelationField.py | 185 ++++++++++++++++++++++++++++-- 1 file changed, 177 insertions(+), 8 deletions(-) diff --git a/product/ERP5Form/RelationField.py b/product/ERP5Form/RelationField.py index 3689f2a443..0c3ad71836 100755 --- a/product/ERP5Form/RelationField.py +++ b/product/ERP5Form/RelationField.py @@ -32,10 +32,14 @@ from Products.Formulator.DummyField import fields from Products.ERP5Type.Utils import convertToUpperCase from Products.CMFCore.utils import getToolByName from Globals import get_request +from Products.PythonScripts.Utility import allow_class from zLOG import LOG +MAX_SELECT = 50 # Max. number of catalog result +new_content_prefix = '_newContent_' -class RelationStringFieldWidget(Widget.TextWidget): + +class RelationStringFieldWidget(Widget.TextWidget, Widget.ListWidget): """ RelationStringField widget @@ -48,7 +52,8 @@ class RelationStringFieldWidget(Widget.TextWidget): """ property_names = Widget.TextWidget.property_names + \ ['update_method', 'jump_method', 'base_category', 'portal_type', 'catalog_index', - 'default_module', 'relation_setter_id', 'columns'] + 'default_module', 'relation_setter_id', 'columns', + 'first_item', 'items', 'size', 'extra_item'] update_method = fields.StringField('update_method', title='Update Method', @@ -99,6 +104,15 @@ class RelationStringFieldWidget(Widget.TextWidget): default="", required=0) + size = fields.IntegerField('size', + title='Size', + description=( + "The display size in rows of the field. If set to 1, the " + "widget will be displayed as a drop down box by many browsers, " + "if set to something higher, a list will be shown. Required."), + default=1, + required=1) + columns = fields.ListTextAreaField('columns', title="Columns", description=( @@ -109,22 +123,37 @@ class RelationStringFieldWidget(Widget.TextWidget): def render(self, field, key, value, REQUEST): """Render text input field. """ + relation_field_id = 'relation_%s' % key + relation_item_id = 'item_%s' % key here = REQUEST['here'] - portal_url_string = getToolByName(here, 'portal_url')() + portal_url = getToolByName(here, 'portal_url') + portal_url_string = portal_url() + portal_object = portal_url.getPortalObject() html_string = Widget.TextWidget.render(self, field, key, value, REQUEST) + if REQUEST.has_key(relation_item_id): + # Define default tales on the fly + tales_expr = field.tales.get('items', None) + if not tales_expr: + from Products.Formulator.TALESField import TALESMethod + field.tales['items'] = TALESMethod('REQUEST/relation_item_list') + REQUEST['relation_item_list'] = REQUEST.get(relation_item_id) + html_string += ' %s ' % Widget.ListWidget.render(self, + field, relation_field_id, None, REQUEST) + REQUEST['relation_item_list'] = None # We used to add a button which has a path reference to a base category... # but it really created too many problems # now we do it in another way # we compare what has been changed in the relation update script - html_string += ' <input type="image" src="%s/images/exec16.png" value="update..." name="%s:method">' \ - % (portal_url_string,field.get_value('update_method')) - if value not in ('', None): + html_string += ' <input type="image" src="%s/images/exec16.png" value="update..." name="%s/portal_selections/viewSearchRelatedDocumentDialog%s:method">' \ + % (portal_url_string, portal_object.getPath(), field.aq_parent._v_relation_field_index) + field.aq_parent._v_relation_field_index += 1 # Increase index + if value not in ('', None) and not REQUEST.has_key(relation_item_id): if REQUEST.get('selection_name') is not None: html_string += ' <a href="%s/%s?field_id=%s&form_id=%s&selection_name=%s&selection_index=%s"><img src="%s/images/jump.png"></a>' \ % (here.absolute_url(), field.get_value('jump_method'), field.id, field.aq_parent.id, REQUEST.get('selection_name'), REQUEST.get('selection_index'),portal_url_string) else: html_string += ' <a href="%s/%s?field_id=%s&form_id=%s"><img src="%s/images/jump.png"></a>' \ - % (here.absolute_url(), field.get_value('jump_method'), field.id, field.aq_parent.id,portal_url_string) + % (here.absolute_url(), field.get_value('jump_method'), field.id, field.aq_parent.id,portal_url_string) return html_string def render_view(self, field, value): @@ -141,11 +170,151 @@ class RelationStringFieldWidget(Widget.TextWidget): % (here.absolute_url(), field.get_value('jump_method'), field.id, field.aq_parent.id, portal_url_string) return html_string +class RelationEditor: + """ + A class holding all values required to update a relation + """ + + def __init__(self, field_id, base_category, portal_type, uid, portal_type_item, + key, value, relation_setter_id, display_text): + self.field_id = field_id + self.uid = uid + self.base_category = base_category + self.portal_type = portal_type + self.portal_type_item = portal_type_item + self.key = key + self.value = value + self.relation_setter_id = relation_setter_id + self.display_text = display_text + + def __call__(self, REQUEST): + if self.uid is not None: + # Decorate the request so that we can display + # the select item in a popup + relation_field_id = 'relation_%s' % self.field_id + relation_item_id = 'item_%s' % self.field_id + REQUEST.set(relation_item_id, ((self.display_text, self.uid),)) + REQUEST.set(relation_field_id, self.uid) + REQUEST.set(self.field_id[len('field_'):], self.value) # XXX Dirty + else: + # Make sure no default value appears + REQUEST.set(key, None) + + def view(self): + return self.__dict__ + + def edit(self, o): + if self.uid is not None: + if type(self.uid) is type('a') and self.uid.startswith(new_content_prefix): + # Create a new content + portal_type = self.uid[len(new_content_prefix):] + portal_module = None + for p_item in self.portal_type_item: + if p_item[0] == portal_type: + portal_module = p_item[1] + if portal_module is not None: + portal_module_object = getattr(o.getPortalObject(), portal_module) + kw ={} + kw[self.key] = self.value + kw['portal_type'] = portal_type + new_object = portal_module_object.newContent(**kw) + self.uid = new_object.getUid() + else: + raise + + # Edit relation + if self.relation_setter_id: + relation_setter = getattr(o, self.relation_setter_id) + relation_setter((), portal_type=self.portal_type) + relation_setter((int(self.uid),), portal_type=self.portal_type) + else: + o._setValueUids(self.base_category, (), portal_type=self.portal_type) + o._setValueUids(self.base_category, (int(self.uid),), portal_type=self.portal_type) + +allow_class(RelationEditor) + +class RelationStringFieldValidator(Validator.StringValidator): + """ + Validation includes lookup of relared instances + """ + + message_names = Validator.StringValidator.message_names +\ + ['relation_result_too_long', 'relation_result_ambiguous', 'relation_result_empty',] + + relation_result_too_long = "Too many documents were found." + relation_result_ambiguous = "Select appropriate document in the list." + relation_result_empty = "No such document was found." + + def validate(self, field, key, REQUEST): + relation_field_id = 'relation_%s' % key + relation_item_id = 'item_%s' % key + portal_type = map(lambda x:x[0],field.get_value('portal_type')) + portal_type_item = field.get_value('portal_type') + base_category = field.get_value( 'base_category') + # If the value is different, build a query + portal_selections = getToolByName(field, 'portal_selections') + portal_catalog = getToolByName(field, 'portal_catalog') + # Get the current value + value = Validator.StringValidator.validate(self, field, key, REQUEST) + # If the value is the same as the current field value, do nothing + current_value = field.get_value('default') + if value == current_value: + return RelationEditor(None, None, None, None, None, None, None, None, None) # Will be interpreted by Base_edit as "do nothing" + # If a relation has been defined in a popup menu, use it + relation_uid = REQUEST.get(relation_field_id, None) + catalog_index = field.get_value('catalog_index') + relation_setter_id = field.get_value('relation_setter_id') + if relation_uid not in (None, ''): + # A value has been defined by the user + if type(relation_uid) in (type([]), type(())): relation_uid = relation_uid[0] + related_object = portal_catalog.getObject(relation_uid) + if related_object is not None: + display_text = str(related_object.getProperty(catalog_index)) + else: + display_text = 'Object has been deleted' + return RelationEditor(key, base_category, portal_type, relation_uid, + portal_type_item, catalog_index, value, relation_setter_id, display_text) + kw ={} + kw[catalog_index] = value + kw['portal_type'] = portal_type + # Get the query results + relation_list = portal_catalog(**kw) + relation_uid_list = map(lambda x: x.uid, relation_list) + # Prepare a menu + menu_item_list = [('', '')] + for p in portal_type: + menu_item_list += [('New %s' % p, '%s%s' % (new_content_prefix,p))] + # If the length is 1, return uid + if len(relation_list) == 1: + relation_uid = relation_uid_list[0] + related_object = portal_catalog.getObject(relation_uid) + if related_object is not None: + display_text = str(related_object.getProperty(catalog_index)) + else: + display_text = 'Object has been deleted' + return RelationEditor(key, base_category, portal_type, relation_uid, + portal_type_item, catalog_index, value, relation_setter_id, display_text) + # If the length is 0, raise an error + if len(relation_list) == 0: + REQUEST.set(relation_item_id, menu_item_list) + self.raise_error('relation_result_empty', field) + # If the length is short, raise an error + if len(relation_list) < MAX_SELECT: + menu_item_list += [('-', '')] + menu_item_list += map(lambda x: (x.getObject().getProperty(catalog_index), x.uid), + relation_list) + REQUEST.set(relation_item_id, menu_item_list) + self.raise_error('relation_result_ambiguous', field) + # If the length is long, raise an error + REQUEST.set(relation_item_id, menu_item_list) + self.raise_error('relation_result_too_long', field) + RelationStringFieldWidgetInstance = RelationStringFieldWidget() -RelationStringFieldValidatorInstance = Validator.StringValidator() +RelationStringFieldValidatorInstance = RelationStringFieldValidator() class RelationStringField(ZMIField): meta_type = "RelationStringField" + is_relation_field = 1 widget = RelationStringFieldWidgetInstance validator = RelationStringFieldValidatorInstance -- 2.30.9