def getCriterionList(self, **kw): """ Returns the list of criteria which are defined by the Predicate. Each criterion is returned in a TempBase instance intended to be displayed in a ListBox. XXX - It would be better to return criteria in a Criterion class instance """ # We do not create PersistentMappings first time we *see* Predicate_view. # Instead, we create them first time we modify Predicate document. if not self.getCriterionPropertyList(): return [] if getattr(aq_base(self), '_identity_criterion', None) is None: self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() criterion_dict = {} for p in self.getCriterionPropertyList(): criterion_dict[p] = newTempBase(self, 'new_%s' % p) criterion_dict[p].identity = self._identity_criterion.get(p, None) criterion_dict[p].uid = 'new_%s' % p criterion_dict[p].property = p criterion_dict[p].min = self._range_criterion.get(p, (None, None))[0] criterion_dict[p].max = self._range_criterion.get(p, (None, None))[1] criterion_list = criterion_dict.values() criterion_list.sort() return criterion_list
def getCriterionList(self, **kw): """ Returns the list of criteria which are defined by the Predicate. Each criterion is returned in a TempBase instance intended to be displayed in a ListBox. XXX - It would be better to return criteria in a Criterion class instance """ # We do not create PersistentMappings first time we *see* Predicate_view. # Instead, we create them first time we modify Predicate document. if not self.getCriterionPropertyList(): return [] if getattr(aq_base(self), "_identity_criterion", None) is None: self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() criterion_dict = {} for p in self.getCriterionPropertyList(): criterion_dict[p] = newTempBase(self, "new_%s" % p) criterion_dict[p].identity = self._identity_criterion.get(p, None) criterion_dict[p].uid = "new_%s" % p criterion_dict[p].property = p criterion_dict[p].min = self._range_criterion.get(p, (None, None))[0] criterion_dict[p].max = self._range_criterion.get(p, (None, None))[1] criterion_list = criterion_dict.values() criterion_list.sort() return criterion_list
def setCriterion(self, property, identity=None, min=None, max=None, **kw): """ This methods sets parameters of a criterion. There is at most one criterion per property. Defined parameters are identity -- if not None, allows for testing identity of the property with the provided value min -- if not None, allows for testing that the property is greater than min max -- if not None, allows for testing that the property is greater than max """ # XXX 'min' and 'max' are built-in functions. if getattr(aq_base(self), '_identity_criterion', None) is None: self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() if identity is not None : self._identity_criterion[property] = identity if min == '': min = None if max == '': max = None if min is None and max is None: try: del self._range_criterion[property] except KeyError: pass else: self._range_criterion[property] = (min, max) self.reindexObject()
def __init__ (self, id, title='', pdf_file=''): # holds all the cell informations, even those not related to this form self.all_cells = PersistentMapping() # holds the cells related to this pdf form self.cells = PersistentMapping() # File constructor will set the file content File.__init__(self, id, title, pdf_file)
def __init__ (self, id, title='', pdf_file=''): # holds information about all cells, even those not related to this form self.all_cells = PersistentMapping() # holds the cells related to this pdf form self.cells = PersistentMapping() # File constructor will set the file content File.__init__(self, id, title, pdf_file)
def _setCellRange(self, *args, **kw): """Set a new range for a matrix Each value for each axis is assigned an integer id. If the number of axis changes, everything is reset. Otherwise, ids are never changed, so that cells never need to be renamed: this means no sort is garanteed, and there can be holes. """ base_id = kw.get('base_id', 'cell') # Get (initialize if necessary) index for considered matrix (base_id). try: index = aq_base(self).index except AttributeError: index = self.index = PersistentMapping() to_delete = [] try: index = index[base_id] if len(args) != len(index): # The number of axis changes so we'll delete all existing cells and # renumber everything from 1. to_delete = INFINITE_SET, index.clear() except KeyError: index[base_id] = index = PersistentMapping() # For each axis ... for i, axis in enumerate(args): # ... collect old axis keys and allocate ids for new ones. axis = set(axis) last_id = -1 try: id_dict = index[i] except KeyError: index[i] = id_dict = PersistentMapping() else: delete = set() to_delete.append(delete) for k, v in id_dict.items(): try: axis.remove(k) if last_id < v: last_id = v except KeyError: delete.add(v) del id_dict[k] # At this point, last_id contains the greatest id. for k in sorted(axis): last_id += 1 id_dict[k] = last_id # Remove old cells if any. if any(to_delete): prefix = base_id + '_' prefix_len = len(prefix) for cell_id in list(self.objectIds()): if cell_id.startswith(prefix): for i, j in enumerate(cell_id[prefix_len:].split('_')): if int(j) in to_delete[i]: self._delObject(cell_id) break
def setPredicateCategoryList(self, category_list): """ This method updates a Predicate by implementing an AND operation on all predicates (or categories) provided in category_list. Categories behave as a special kind of predicate which only acts on category membership. WARNING: this method does not take into account scripts at this point. """ category_tool = aq_inner(self.portal_categories) base_category_id_list = category_tool.objectIds() membership_criterion_category_list = [] membership_criterion_base_category_list = [] multimembership_criterion_base_category_list = [] test_method_id_list = [] criterion_property_list = [] # reset criterions self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() for c in category_list: bc = c.split('/')[0] if bc in base_category_id_list: # This is a category membership_criterion_category_list.append(c) membership_criterion_base_category_list.append(bc) else: predicate_value = category_tool.resolveCategory(c) if predicate_value is not None: criterion_property_list.extend( predicate_value.getCriterionPropertyList()) membership_criterion_category_list.extend( predicate_value.getMembershipCriterionCategoryList()) membership_criterion_base_category_list.extend( predicate_value.getMembershipCriterionBaseCategoryList( )) multimembership_criterion_base_category_list.extend( predicate_value. getMultimembershipCriterionBaseCategoryList()) test_method_id_list += list( predicate_value.getTestMethodIdList() or []) for p in predicate_value.getCriterionList(): self.setCriterion(p.property, identity=p.identity, min=p.min, max=p.max) self.setCriterionPropertyList(criterion_property_list) self._setMembershipCriterionCategoryList( membership_criterion_category_list) self._setMembershipCriterionBaseCategoryList( membership_criterion_base_category_list) self._setMultimembershipCriterionBaseCategoryList( multimembership_criterion_base_category_list) self._setTestMethodIdList(test_method_id_list) self.reindexObject()
def __init__(self, *args, **kw): self.signature_methods = PersistentMapping() self.add_signature_method(OAuthSignatureMethod_PLAINTEXT()) self.add_signature_method(OAuthSignatureMethod_HMAC_SHA1()) self.consumer = OAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET) self.my_request_token = OAuthToken('requestkey', 'requestsecret') self.my_access_token = OAuthToken('accesskey', 'accesssecret') self.nonce = 'nonce' self.verifier = VERIFIER
def setLastGeneratedId(self, new_id, id_group=None): """ Set a new last id. This is usefull in order to reset a sequence of ids. """ if getattr(aq_base(self), 'dict_ids', None) is None: self.dict_ids = PersistentMapping() if id_group is not None and id_group != 'None': self.dict_ids[id_group] = new_id
def getDictLengthIdsItems(self): """ Return a copy of dict_length_ids. This is a workaround to access the persistent mapping content from ZSQL method to be able to insert initial tuples in the database at creation. """ if getattr(self, 'dict_length_ids', None) is None: self.dict_length_ids = PersistentMapping() return self.dict_length_ids.items()
def AddNewLocalVariableDict(self): """ Function to add a new Local Variable for a Data Notebook """ new_dict = PersistentMapping() variable_dict = PersistentMapping() module_dict = PersistentMapping() new_dict['variables'] = variable_dict new_dict['imports'] = module_dict return new_dict
def getLastGeneratedId(self, id_group=None, default=None): """ Get the last id generated """ warnings.warn('getLastGeneratedId is deprecated', DeprecationWarning) if getattr(aq_base(self), 'dict_ids', None) is None: self.dict_ids = PersistentMapping() last_id = None if id_group is not None and id_group != 'None': last_id = self.dict_ids.get(id_group, default) return last_id
def setPredicateCategoryList(self, category_list): """ This method updates a Predicate by implementing an AND operation on all predicates (or categories) provided in category_list. Categories behave as a special kind of predicate which only acts on category membership. WARNING: this method does not take into account scripts at this point. """ category_tool = aq_inner(self.portal_categories) base_category_id_list = category_tool.objectIds() membership_criterion_category_list = [] membership_criterion_base_category_list = [] multimembership_criterion_base_category_list = [] test_method_id_list = [] criterion_property_list = [] # reset criterions self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() for c in category_list: bc = c.split("/")[0] if bc in base_category_id_list: # This is a category membership_criterion_category_list.append(c) membership_criterion_base_category_list.append(bc) else: predicate_value = category_tool.resolveCategory(c) if predicate_value is not None: criterion_property_list.extend(predicate_value.getCriterionPropertyList()) membership_criterion_category_list.extend(predicate_value.getMembershipCriterionCategoryList()) membership_criterion_base_category_list.extend( predicate_value.getMembershipCriterionBaseCategoryList() ) multimembership_criterion_base_category_list.extend( predicate_value.getMultimembershipCriterionBaseCategoryList() ) test_method_id_list += list(predicate_value.getTestMethodIdList() or []) for p in predicate_value.getCriterionList(): self.setCriterion(p.property, identity=p.identity, min=p.min, max=p.max) self.setCriterionPropertyList(criterion_property_list) self._setMembershipCriterionCategoryList(membership_criterion_category_list) self._setMembershipCriterionBaseCategoryList(membership_criterion_base_category_list) self._setMultimembershipCriterionBaseCategoryList(multimembership_criterion_base_category_list) self._setTestMethodIdList(test_method_id_list) self.reindexObject()
def newObject(self, object=None, xml=None, simulate=False, # pylint: disable=redefined-builtin reset_local_roles=True, reset_workflow=True): """ modify the object with datas from the xml (action section) """ args = {} if simulate: return # Retrieve the list of users with a role and delete default roles if reset_local_roles: user_role_list = [x[0] for x in object.get_local_roles()] object.manage_delLocalRoles(user_role_list) if getattr(object, 'workflow_history', None) is not None and reset_workflow: object.workflow_history = PersistentMapping() if xml.prefix == 'xupdate': xml = xml[0] for subnode in xml.xpath('*'): #get only Element nodes (not Comments or Processing instructions) if subnode.xpath('name()') not in NOT_EDITABLE_PROPERTY_LIST: keyword_type = self.getPropertyType(subnode) # This is the case where the property is a list keyword = subnode.xpath('name()') args[keyword] = self.convertXmlValue(subnode, keyword_type) elif subnode.xpath('local-name()') in ADDABLE_PROPERTY_LIST\ + (XML_OBJECT_TAG,): self.addNode(object=object, xml=subnode, force=True) # We should first edit the object args = self.getFormatedArgs(args=args) # edit the object with a dictionnary of arguments, # like {"telephone_number":"02-5648"} self.editDocument(object=object, **args) if getattr(object, 'manage_afterEdit', None) is not None: object.manage_afterEdit() self.afterNewObject(object)
def generateNewId(self, id_group=None, default=None, method=_marker, id_generator=None): """ Generate the next id in the sequence of ids of a particular group """ if id_group in (None, 'None'): raise ValueError, '%s is not a valid id_group' % (repr(id_group), ) # for compatibilty with sql data, must not use id_group as a list if not isinstance(id_group, str): id_group = repr(id_group) warnings.warn( 'id_group must be a string, other types ' 'are deprecated.', DeprecationWarning) if id_generator is None: id_generator = 'document' if method is not _marker: warnings.warn("Use of 'method' argument is deprecated", DeprecationWarning) try: #use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) new_id = last_generator.generateNewId(id_group=id_group, \ default=default) except KeyError: # XXX backward compatiblity if self.getTypeInfo(): LOG('generateNewId', ERROR, 'while generating id') raise else: # Compatibility code below, in case the last version of erp5_core # is not installed yet warnings.warn( "You are using an old version of erp5_core to generate" "ids.\nPlease update erp5_core business template to " "use new id generators", DeprecationWarning) dict_ids = getattr(aq_base(self), 'dict_ids', None) if dict_ids is None: dict_ids = self.dict_ids = PersistentMapping() new_id = None # Getting the last id if default is None: default = 0 marker = [] new_id = dict_ids.get(id_group, marker) if method is _marker: if new_id is marker: new_id = default else: new_id = new_id + 1 else: if new_id is marker: new_id = default new_id = method(new_id) # Store the new value dict_ids[id_group] = new_id return new_id
def _setEncodedPassword( self, value, format='default', # pylint: disable=redefined-builtin ): password = getattr(aq_base(self), 'password', None) if password is None or isinstance(password, basestring): password = self.password = PersistentMapping() self.password[format] = value
def _getResponseHeaderRuleDictForModification(self): """ Retrieve persistent rule dict storage. Use only when a modification is requested, to avoid creating useless subobjects. """ try: return self._response_header_rule_dict except AttributeError: self._response_header_rule_dict = rule_dict = PersistentMapping() return rule_dict
def edit(self, **kwd): """ The edit method is overriden so that any time a criterion_property_list property is defined, a list of criteria is created to match the provided criterion_property_list. """ if getattr(aq_base(self), '_identity_criterion', None) is None: self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() if 'criterion_property_list' in kwd: criterion_property_list = kwd['criterion_property_list'] identity_criterion = PersistentMapping() range_criterion = PersistentMapping() for criterion in self._identity_criterion.iterkeys(): if criterion in criterion_property_list: identity_criterion[criterion] = self._identity_criterion[ criterion] for criterion in self._range_criterion.iterkeys(): if criterion in criterion_property_list: range_criterion[criterion] = self._range_criterion[ criterion] self._identity_criterion = identity_criterion self._range_criterion = range_criterion kwd['reindex_object'] = 1 return self._edit(**kwd)
def edit(self, **kwd): """ The edit method is overriden so that any time a criterion_property_list property is defined, a list of criteria is created to match the provided criterion_property_list. """ if getattr(aq_base(self), '_identity_criterion', None) is None: self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() if 'criterion_property_list' in kwd: criterion_property_list = kwd['criterion_property_list'] identity_criterion = PersistentMapping() range_criterion = PersistentMapping() for criterion in self._identity_criterion.iterkeys() : if criterion in criterion_property_list : identity_criterion[criterion] = self._identity_criterion[criterion] for criterion in self._range_criterion.iterkeys() : if criterion in criterion_property_list : range_criterion[criterion] = self._range_criterion[criterion] self._identity_criterion = identity_criterion self._range_criterion = range_criterion kwd['reindex_object'] = 1 return self._edit(**kwd)
def addVariable(self, id, text, REQUEST=None): ''' Add a variable expression. ''' if self.var_exprs is None: self.var_exprs = PersistentMapping() expr = None if text: expr = Expression(str(text)) self.var_exprs[id] = expr if REQUEST is not None: return self.manage_variables(REQUEST, 'Variable added.')
def getCriterionList(self, **kw): """ Returns the list of criteria which are defined by the Predicate. Each criterion is returned in a TempBase instance intended to be displayed in a ListBox. XXX - It would be better to return criteria in a Criterion class instance """ if getattr(aq_base(self), '_identity_criterion', None) is None: self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() criterion_dict = {} for p in self.getCriterionPropertyList(): criterion_dict[p] = newTempBase(self, 'new_%s' % p) criterion_dict[p].identity = self._identity_criterion.get(p, None) criterion_dict[p].uid = 'new_%s' % p criterion_dict[p].property = p criterion_dict[p].min = self._range_criterion.get(p, (None, None))[0] criterion_dict[p].max = self._range_criterion.get(p, (None, None))[1] criterion_list = criterion_dict.values() criterion_list.sort() return criterion_list
def _checkDataStructureMigration(self, id_generator): """ First, simulate previous data structure which is using PersisntentMapping as the storage, then migrate to OOBTree. Then, migrate the id generator again from OOBTree to OOBtree just to be sure.""" id_generator_reference = id_generator.getReference() reference_portal_type_dict = { 'test_sql_non_continuous_increasing':'SQL Non Continuous ' \ 'Increasing Id Generator', 'test_zodb_continuous_increasing':'ZODB Continuous ' \ 'Increasing Id Generator' } try: portal_type = reference_portal_type_dict[id_generator_reference] self.assertEqual(id_generator.getPortalType(), portal_type) except: raise ValueError("reference is not valid: %s" % id_generator_reference) self._setLastIdDict(id_generator, PersistentMapping()) # simulate previous last_id_dict = self._getLastIdDict(id_generator) # setUp the data for migration test self._setUpLastMaxIdDict(id_generator_reference) # test migration: PersistentMapping to OOBTree self.assertTrue(isinstance(last_id_dict, PersistentMapping)) self._assertIdGeneratorLastMaxIdDict(id_generator) id_generator.rebuildGeneratorIdDict() # migrate the dict self._assertIdGeneratorLastMaxIdDict(id_generator) # test migration: OOBTree to OOBTree. this changes nothing, just to be sure last_id_dict = self._getLastIdDict(id_generator) self.assertTrue(isinstance(last_id_dict, OOBTree)) self._assertIdGeneratorLastMaxIdDict(id_generator) id_generator.rebuildGeneratorIdDict() # migrate the dict self._assertIdGeneratorLastMaxIdDict(id_generator) # test migration: SQL to OOBTree if id_generator.getPortalType() == \ 'SQL Non Continuous Increasing Id Generator': self._setLastIdDict(id_generator, OOBTree()) # set empty one last_id_dict = self._getLastIdDict(id_generator) assert(len(last_id_dict), 0) # 0 because it is empty self.assertTrue(isinstance(last_id_dict, OOBTree)) # migrate the dict totally from sql table in this case id_generator.rebuildGeneratorIdDict() self._assertIdGeneratorLastMaxIdDict(id_generator)
def getResetPasswordKey(self, user_login): # generate expiration date expiration_date = DateTime() + self._expiration_day # generate a random string key = self._generateUUID() # XXX before r26093, _password_request_dict was initialized by an OOBTree and # replaced by a dict on each request, so if it's data structure is not up # to date, we update it if needed if not isinstance(self._password_request_dict, PersistentMapping): LOG('ERP5.PasswordTool', INFO, 'Updating password_request_dict to' ' PersistentMapping') self._password_request_dict = PersistentMapping() # register request self._password_request_dict[key] = (user_login, expiration_date) return key
def setVariables(self, ids=[], REQUEST=None): ''' set values for Variables set by this state ''' if self.var_exprs is None: self.var_exprs = PersistentMapping() ve = self.var_exprs if REQUEST is not None: for id in ve.keys(): fname = 'varexpr_%s' % id val = REQUEST[fname] expr = None if val: expr = Expression(str(REQUEST[fname])) ve[id] = expr return self.manage_variables(REQUEST, 'Variables changed.')
def WorkflowTool_setStatusOf(self, wf_id, ob, status): """ Append an entry to the workflow history. o Invoked by workflow definitions. """ wfh = None has_history = 0 if getattr(aq_base(ob), 'workflow_history', None) is not None: history = ob.workflow_history if history is not None: has_history = 1 wfh = history.get(wf_id, None) if wfh is not None and not isinstance(wfh, WorkflowHistoryList): wfh = WorkflowHistoryList(list(wfh)) ob.workflow_history[wf_id] = wfh if wfh is None: wfh = WorkflowHistoryList() if not has_history: ob.workflow_history = PersistentMapping() ob.workflow_history[wf_id] = wfh wfh.append(status)
def recordProperty(self, id): """ Records the current value of a property. id -- ID of the property """ for property_info in self.getPropertyMap(): if property_info['id'] == id: if property_info['type'] in list_types: value = self.getPropertyList(id) else: value = self.getProperty(id) break else: if id in self.getBaseCategoryList(): value = self.getPropertyList(id) else: # should be local property value = self.getProperty(id) try: self._getRecordedPropertyDict()[id] = value except AttributeError: self._recorded_property_dict = PersistentMapping({id: value})
def setTranslationDomain(self, prop_name, domain): """ Set a translation domain for given property. """ try: property_domain_dict = aq_base(self)._property_domain_dict except AttributeError: self._property_domain_dict = property_domain_dict = PersistentMapping( ) else: # BBB: If domain dict is not a stand-alone peristent object, changes made # to it won't be persistently stored. It used to work because the whole # dict was replaced, hence triggering a change on self. But this creates # an inconvenient API. For the sake of keeping BT diffs quiet, don't cast # that dict into a PersistentMapping. if not isinstance(property_domain_dict, Persistent): self._p_changed = 1 property_domain_dict[prop_name] = TranslationInformation( prop_name, domain) # Reset accessor cache self.getPortalObject().portal_types.\ resetDynamicDocumentsOnceAtTransactionBoundary()
def _updateWorkflowHistory(self, document, status_dict): """ Change the state of the object. """ # Create history attributes if needed if getattr(aq_base(document), 'workflow_history', None) is None: document.workflow_history = PersistentMapping() # XXX this _p_changed is apparently not necessary document._p_changed = 1 # Add an entry for the workflow in the history workflow_key = self._generateHistoryKey() if not document.workflow_history.has_key(workflow_key): document.workflow_history[workflow_key] = () # Update history document.workflow_history[workflow_key] += (status_dict, ) # XXX this _p_changed marks the document modified, but the # only the PersistentMapping is modified document._p_changed = 1 # XXX this _p_changed is apparently not necessary document.workflow_history._p_changed = 1
def setStatusOf(self, wf_id, ob, status): """ Append an entry to the workflow history. o Invoked by workflow definitions. """ from Products.ERP5Type.Workflow import WorkflowHistoryList as NewWorkflowHistoryList wfh = None has_history = 0 if getattr(aq_base(ob), 'workflow_history', None) is not None: history = ob.workflow_history if history is not None: has_history = 1 wfh = history.get(wf_id, None) if wfh is not None and not isinstance(wfh, NewWorkflowHistoryList): wfh = NewWorkflowHistoryList(wfh) ob.workflow_history[wf_id] = wfh if wfh is None: wfh = NewWorkflowHistoryList() if not has_history: ob.workflow_history = PersistentMapping() ob.workflow_history[wf_id] = wfh wfh.append(status)
def dumpDictLengthIdsItems(self): """ Store persistently data from SQL table portal_ids. """ portal_catalog = getattr(self, 'portal_catalog').getSQLCatalog() query = getattr(portal_catalog, 'z_portal_ids_dump') dict_length_ids = getattr(aq_base(self), 'dict_length_ids', None) if dict_length_ids is None: dict_length_ids = self.dict_length_ids = PersistentMapping() for line in query().dictionaries(): id_group = line['id_group'] last_id = line['last_id'] stored_last_id = self.dict_length_ids.get(id_group) if stored_last_id is None: self.dict_length_ids[id_group] = Length(last_id) else: stored_last_id_value = stored_last_id() if stored_last_id_value < last_id: stored_last_id.set(last_id) else: if stored_last_id_value > last_id: LOG('IdTool', WARNING, 'ZODB value (%r) for group %r is higher ' \ 'than SQL value (%r). Keeping ZODB value untouched.' % \ (stored_last_id, id_group, last_id))
def _checkConsistency(self, fixit=0): """ Constraint API. """ # Check useless cells to_delete_set = set() error_list = [] def addError(error_message): if fixit: error_message += ' (fixed)' error = (self.getRelativeUrl(), 'XMLMatrix inconsistency', 102, error_message) error_list.append(error) # We make sure first that there is an index if getattr(aq_base(self), 'index', None) is None: self.index = PersistentMapping() # We will check each cell of the matrix the matrix # XXX This code assumes the following predicate: # each subobject of an XMLMatrix is either a Cell that needs # consistency checking OR ( is not a Cell, and has an id that is # not like "(\w+_)+(\d+_)*\d+" ) # But Documents inheriting XMLMatrix can have unrelated, non-cell # subobjects, possibly with id looking like some_id_2. If it ever happens, # an error will be wrongly raised. for obj in self.objectValues(): object_id = obj.getId() # obect_id is equal to something like 'something_quantity_3_2' # So we need to check for every object.id if the value # looks like good or not. We split the name # check each key in index # First we make sure this is a cell object_id_split = object_id.split('_') base_id = None cell_coordinate_list = [] while object_id_split: coordinate = None try: coordinate = int(object_id_split[-1]) except ValueError: # The last item is not a coordinate, object_id_split hence # only contains the base_id elements base_id = '_'.join(object_id_split) break else: cell_coordinate_list.insert(0, coordinate) # the last item is a coordinate not part of base_id object_id_split.pop() current_dimension = len(cell_coordinate_list) if current_dimension > 0 and base_id is not None: if not self.index.has_key(base_id): # The matrix does not have this base_id addError("There is no index for base_id %s" % base_id) to_delete_set.add(object_id) continue # Check empty indices. empty_list = [] base_item = self.index[base_id] for key, value in base_item.iteritems(): if value is None or len(value) == 0: addError("There is no id for the %dth axis of base_id %s" % (key, base_id)) empty_list.append(key) if fixit: for i in empty_list: del base_item[key] len_id = len(base_item) if current_dimension != len_id: addError("Dimension of cell is %s but should be %s" % (current_dimension, len_id)) to_delete_set.add(object_id) else : for i, coordinate in enumerate(cell_coordinate_list): if coordinate >= len(base_item[i]): addError("Cell %s is out of bound" % object_id) to_delete_set.add(object_id) break if fixit and len(to_delete_set) > 0: self.manage_delObjects(list(to_delete_set)) return error_list
def _solveBySplitting(self, activate_kw=None): """ contains all the logic to split. This method is convenient in case another solver needs it. """ solver_dict = {} new_movement_list = [] configuration_dict = self.getConfigurationPropertyDict() delivery_dict = {} for simulation_movement in self.getDeliveryValueList(): delivery_dict.setdefault(simulation_movement.getDeliveryValue(), []).append(simulation_movement) for movement, simulation_movement_list in delivery_dict.iteritems(): decision_quantity = movement.getQuantity() delivery_solver = self.getParentValue().newContent( portal_type=configuration_dict['delivery_solver'], temp_object=True) delivery_solver.setDeliveryValueList(simulation_movement_list) # Update the quantity using delivery solver algorithm split_list = delivery_solver.setTotalQuantity(decision_quantity, activate_kw=activate_kw) # Create split movements for (simulation_movement, split_quantity) in split_list: split_index = 0 simulation_id = simulation_movement.getId().split("_split_")[0] new_id = "%s_split_%s" % (simulation_id, split_index) applied_rule = simulation_movement.getParentValue() while getattr(aq_base(applied_rule), new_id, None) is not None: split_index += 1 new_id = "%s_split_%s" % (simulation_id, split_index) # Copy at same level kw = _getPropertyAndCategoryList(simulation_movement) kw.update(delivery=None, quantity=split_quantity) new_movement = applied_rule.newContent( new_id, simulation_movement.getPortalType(), activate_kw=activate_kw, **kw) new_movement_list.append(new_movement) # Dirty code until IPropertyRecordable is revised. # Merge original simulation movement recorded property to new one. recorded_property_dict = simulation_movement._getRecordedPropertyDict(None) if recorded_property_dict: new_movement_recorded_property_dict = new_movement._getRecordedPropertyDict(None) if new_movement_recorded_property_dict is None: new_movement_recorded_property_dict = new_movement._recorded_property_dict = PersistentMapping() new_movement_recorded_property_dict.update(recorded_property_dict) # record zero quantity property, because this was originally zero. # without this, splitanddefer after accept decision does not work # properly. current_quantity = new_movement.getQuantity() new_movement.setQuantity(0) new_movement.recordProperty('quantity') new_movement.setQuantity(current_quantity) start_date = configuration_dict.get('start_date', None) if start_date is not None: new_movement.recordProperty('start_date') new_movement.setStartDate(start_date) stop_date = configuration_dict.get('stop_date', None) if stop_date is not None: new_movement.recordProperty('stop_date') new_movement.setStopDate(stop_date) if activate_kw: new_movement.setDefaultActivateParameterDict({}) simulation_movement.expand(activate_kw=activate_kw) new_movement.expand(activate_kw=activate_kw) # Finish solving if self.getPortalObject().portal_workflow.isTransitionPossible( self, 'succeed'): self.succeed() solver_dict["new_movement_list"] = new_movement_list return solver_dict
class IdTool(BaseTool): """ This tools handles the generation of IDs. """ id = 'portal_ids' meta_type = 'ERP5 Id Tool' portal_type = 'Id Tool' title = 'Id Generators' # Declarative Security security = ClassSecurityInfo() security.declareProtected( Permissions.ManagePortal, 'manage_overview' ) manage_overview = DTMLFile( 'explainIdTool', _dtmldir ) def newContent(self, *args, **kw): """ the newContent is overriden to not use generateNewId """ if id not in kw: new_id = self._generateNextId() if new_id is not None: kw['id'] = new_id else: raise ValueError('Failed to gererate id') return BaseTool.newContent(self, *args, **kw) def _get_id(self, id): """ _get_id is overrided to not use generateNewId It is used for example when an object is cloned """ if self._getOb(id, None) is None : return id return self._generateNextId() @caching_instance_method(id='IdTool._getLatestIdGenerator', cache_factory='erp5_content_long') def _getLatestIdGenerator(self, reference): """ Tries to find the id_generator with the latest version from the current object. Use the low-level to create a site without catalog """ assert reference id_last_generator = None version_last_generator = 0 for generator in self.objectValues(): if generator.getReference() == reference: # Version Property Sheet defines 'version' property as a 'string' version = int(generator.getVersion()) if version > version_last_generator: id_last_generator = generator.getId() version_last_generator = version if id_last_generator is None: raise KeyError(repr(reference)) return id_last_generator def _getLatestGeneratorValue(self, id_generator): """ Return the last generator with the reference """ return self._getOb(self._getLatestIdGenerator(id_generator)) security.declareProtected(Permissions.AccessContentsInformation, 'generateNewId') def generateNewId(self, id_group=None, default=None, method=_marker, id_generator=None, poison=False): """ Generate the next id in the sequence of ids of a particular group """ if id_group in (None, 'None'): raise ValueError('%r is not a valid id_group' % id_group) # for compatibilty with sql data, must not use id_group as a list if not isinstance(id_group, str): id_group = repr(id_group) warnings.warn('id_group must be a string, other types ' 'are deprecated.', DeprecationWarning) if id_generator is None: id_generator = 'document' if method is not _marker: warnings.warn("Use of 'method' argument is deprecated", DeprecationWarning) try: #use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) new_id = last_generator.generateNewId( id_group=id_group, default=default, poison=poison, ) except KeyError: # XXX backward compatiblity if self.getTypeInfo(): LOG('generateNewId', ERROR, 'while generating id') raise else: # Compatibility code below, in case the last version of erp5_core # is not installed yet warnings.warn("You are using an old version of erp5_core to generate" "ids.\nPlease update erp5_core business template to " "use new id generators", DeprecationWarning) dict_ids = getattr(aq_base(self), 'dict_ids', None) if dict_ids is None: dict_ids = self.dict_ids = PersistentMapping() new_id = None # Getting the last id if default is None: default = 0 marker = [] new_id = dict_ids.get(id_group, marker) if method is _marker: if new_id is marker: new_id = default else: new_id = new_id + 1 else: if new_id is marker: new_id = default new_id = method(new_id) # Store the new value dict_ids[id_group] = new_id return new_id security.declareProtected(Permissions.AccessContentsInformation, 'generateNewIdList') def generateNewIdList(self, id_group=None, id_count=1, default=None, store=_marker, id_generator=None, poison=False): """ Generate a list of next ids in the sequence of ids of a particular group """ if id_group in (None, 'None'): raise ValueError('%r is not a valid id_group' % id_group) # for compatibilty with sql data, must not use id_group as a list if not isinstance(id_group, str): id_group = repr(id_group) warnings.warn('id_group must be a string, other types ' 'are deprecated.', DeprecationWarning) if id_generator is None: id_generator = 'uid' if store is not _marker: warnings.warn("Use of 'store' argument is deprecated.", DeprecationWarning) try: #use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) new_id_list = last_generator.generateNewIdList(id_group=id_group, id_count=id_count, default=default, poison=poison) except (KeyError, ValueError): # XXX backward compatiblity if self.getTypeInfo(): LOG('generateNewIdList', ERROR, 'while generating id') raise else: # Compatibility code below, in case the last version of erp5_core # is not installed yet warnings.warn("You are using an old version of erp5_core to generate" "ids.\nPlease update erp5_core business template to " "use new id generators", DeprecationWarning) new_id = None if default is None: default = 1 # XXX It's temporary, a New API will be implemented soon # the code will be change portal = self.getPortalObject() try: query = portal.IdTool_zGenerateId commit = portal.IdTool_zCommit except AttributeError: portal_catalog = portal.portal_catalog.getSQLCatalog() query = portal_catalog.z_portal_ids_generate_id commit = portal_catalog.z_portal_ids_commit try: result = query(id_group=id_group, id_count=id_count, default=default) finally: commit() new_id = result[0]['LAST_INSERT_ID()'] if store: if getattr(aq_base(self), 'dict_length_ids', None) is None: # Length objects are stored in a persistent mapping: there is one # Length object per id_group. self.dict_length_ids = PersistentMapping() if self.dict_length_ids.get(id_group) is None: self.dict_length_ids[id_group] = Length(new_id) self.dict_length_ids[id_group].set(new_id) if six.PY2: new_id_list = range(new_id - id_count, new_id) else: new_id_list = list(range(new_id - id_count, new_id)) return new_id_list security.declareProtected(Permissions.ModifyPortalContent, 'initializeGenerator') def initializeGenerator(self, id_generator=None, all=False): """ Initialize generators. This is mostly used when a new ERP5 site is created. Some generators will need to do some initialization like creating SQL Database, prepare some data in ZODB, etc """ if not all: #Use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) last_generator.initializeGenerator() else: # recovery all the generators and initialize them for generator in self.objectValues(\ portal_type='Application Id Generator'): generator.initializeGenerator() security.declareProtected(Permissions.ModifyPortalContent, 'clearGenerator') def clearGenerator(self, id_generator=None, all=False): """ Clear generators data. This can be usefull when working on a development instance or in some other rare cases. This will loose data and must be use with caution This can be incompatible with some particular generator implementation, in this case a particular error will be raised (to be determined and added here) """ if not all: #Use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) last_generator.clearGenerator() else: if len(self.objectValues()) == 0: # compatibility with old API self.getPortalObject().IdTool_zDropTable() self.getPortalObject().IdTool_zCreateTable() for generator in self.objectValues(\ portal_type='Application Id Generator'): generator.clearGenerator() ## XXX Old API deprecated #backward compatibility security.declareProtected(Permissions.AccessContentsInformation, 'generateNewLengthIdList') generateNewLengthIdList = generateNewIdList security.declareProtected(Permissions.AccessContentsInformation, 'getLastLengthGeneratedId') def getLastLengthGeneratedId(self, id_group, default=None): """ Get the last length id generated """ warnings.warn('getLastLengthGeneratedId is deprecated', DeprecationWarning) # check in persistent mapping if exists if getattr(aq_base(self), 'dict_length_ids', None) is not None: last_id = self.dict_length_ids.get(id_group) if last_id is not None: return last_id.value - 1 # otherwise check in mysql # XXX It's temporary, a New API will be implemented soon # the code will be change portal = self.getPortalObject() try: query = portal.IdTool_zGetLastId except AttributeError: query = portal.portal_catalog.getSQLCatalog().z_portal_ids_get_last_id result = query(id_group=id_group) if len(result): try: return result[0]['last_id'] except KeyError: return result[0]['LAST_INSERT_ID()'] return default security.declareProtected(Permissions.AccessContentsInformation, 'getLastGeneratedId') def getLastGeneratedId(self, id_group=None, default=None): """ Get the last id generated """ warnings.warn('getLastGeneratedId is deprecated', DeprecationWarning) if getattr(aq_base(self), 'dict_ids', None) is None: self.dict_ids = PersistentMapping() last_id = None if id_group is not None and id_group != 'None': last_id = self.dict_ids.get(id_group, default) return last_id security.declareProtected(Permissions.ModifyPortalContent, 'setLastGeneratedId') def setLastGeneratedId(self, new_id, id_group=None): """ Set a new last id. This is usefull in order to reset a sequence of ids. """ if getattr(aq_base(self), 'dict_ids', None) is None: self.dict_ids = PersistentMapping() if id_group is not None and id_group != 'None': self.dict_ids[id_group] = new_id security.declareProtected(Permissions.AccessContentsInformation, 'generateNewLengthId') def generateNewLengthId(self, id_group=None, default=None, store=_marker): """Generates an Id using a conflict free id generator. Deprecated. """ warnings.warn('generateNewLengthId is deprecated.\n' 'Use generateNewIdList with a sql id_generator', DeprecationWarning) if store is not _marker: return self.generateNewIdList(id_group=id_group, id_count=1, default=default, store=store)[0] return self.generateNewIdList(id_group=id_group, id_count=1, default=default)[0] security.declareProtected(Permissions.AccessContentsInformation, 'getDictLengthIdsItems') def getDictLengthIdsItems(self): """ Return a copy of dict_length_ids. This is a workaround to access the persistent mapping content from ZSQL method to be able to insert initial tuples in the database at creation. """ if getattr(self, 'dict_length_ids', None) is None: self.dict_length_ids = PersistentMapping() return self.dict_length_ids.items() security.declarePrivate('dumpDictLengthIdsItems') def dumpDictLengthIdsItems(self): """ Store persistently data from SQL table portal_ids. """ portal_catalog = getattr(self, 'portal_catalog').getSQLCatalog() query = getattr(portal_catalog, 'z_portal_ids_dump') dict_length_ids = getattr(aq_base(self), 'dict_length_ids', None) if dict_length_ids is None: dict_length_ids = self.dict_length_ids = PersistentMapping() for line in query().dictionaries(): id_group = line['id_group'] last_id = line['last_id'] stored_last_id = self.dict_length_ids.get(id_group) if stored_last_id is None: self.dict_length_ids[id_group] = Length(last_id) else: stored_last_id_value = stored_last_id() if stored_last_id_value < last_id: stored_last_id.set(last_id) else: if stored_last_id_value > last_id: LOG('IdTool', WARNING, 'ZODB value (%r) for group %r is higher ' \ 'than SQL value (%r). Keeping ZODB value untouched.' % \ (stored_last_id, id_group, last_id))
class PasswordTool(BaseTool): """ PasswordTool is used to allow a user to change its password """ title = 'Password Tool' id = 'portal_password' meta_type = 'ERP5 Password Tool' portal_type = 'Password Tool' allowed_types = () # Declarative Security security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview' ) manage_overview = DTMLFile( 'explainPasswordTool', _dtmldir ) _expiration_day = 1 _password_request_dict = {} def __init__(self, id=None): if id is None: id = self.__class__.id self._password_request_dict = PersistentMapping() # XXX no call to BaseTool.__init__ ? # BaseTool.__init__(self, id) security.declareProtected('Manage users', 'getResetPasswordKey') def getResetPasswordKey(self, user_login): # generate expiration date expiration_date = DateTime() + self._expiration_day # generate a random string key = self._generateUUID() # XXX before r26093, _password_request_dict was initialized by an OOBTree and # replaced by a dict on each request, so if it's data structure is not up # to date, we update it if needed if not isinstance(self._password_request_dict, PersistentMapping): LOG('ERP5.PasswordTool', INFO, 'Updating password_request_dict to' ' PersistentMapping') self._password_request_dict = PersistentMapping() # register request self._password_request_dict[key] = (user_login, expiration_date) return key security.declareProtected('Manage users', 'getResetPasswordUrl') def getResetPasswordUrl(self, user_login=None, key=None, site_url=None): if user_login is not None: # XXX Backward compatibility key = self.getResetPasswordKey(user_login) parameter = urlencode(dict(reset_key=key)) method = self._getTypeBasedMethod("getSiteUrl") if method is not None: base_url = method() else: base_url = "%s/portal_password/PasswordTool_viewResetPassword" % ( site_url,) url = "%s?%s" %(base_url, parameter) return url security.declareProtected('Manage users', 'getResetPasswordUrl') def getExpirationDateForKey(self, key=None): return self._password_request_dict[key][1] def mailPasswordResetRequest(self, user_login=None, REQUEST=None, notification_message=None, sender=None, store_as_event=False): """ Create a random string and expiration date for request Parameters: user_login -- Reference of the user to send password reset link REQUEST -- Request object notification_message -- Notification Message Document used to build the email. As default, a standart text will be used. sender -- Sender (Person or Organisation) of the email. As default, the default email address will be used store_as_event -- whenever CRM is available, store notifications as events """ if REQUEST is None: REQUEST = get_request() if user_login is None: user_login = REQUEST["user_login"] site_url = self.getPortalObject().absolute_url() if REQUEST and 'came_from' in REQUEST: site_url = REQUEST.came_from msg = None # check user exists, and have an email user_list = self.getPortalObject().acl_users.\ erp5_users.getUserByLogin(user_login) if len(user_list) == 0: msg = translateString("User ${user} does not exist.", mapping={'user':user_login}) else: # We use checked_permission to prevent errors when trying to acquire # email from organisation user = user_list[0] email_value = user.getDefaultEmailValue( checked_permission='Access content information') if email_value is None or not email_value.asText(): msg = translateString( "User ${user} does not have an email address, please contact site " "administrator directly", mapping={'user':user_login}) if msg: if REQUEST is not None: parameter = urlencode(dict(portal_status_message=msg)) ret_url = '%s/login_form?%s' % \ (site_url, parameter) return REQUEST.RESPONSE.redirect( ret_url ) return msg key = self.getResetPasswordKey(user_login=user_login) url = self.getResetPasswordUrl(key=key, site_url=site_url) # send mail message_dict = {'instance_name':self.getPortalObject().getTitle(), 'reset_password_link':url, 'expiration_date':self.getExpirationDateForKey(key)} if notification_message is None: subject = translateString("[${instance_name}] Reset of your password", mapping={'instance_name': self.getPortalObject().getTitle()}) subject = subject.translate() message = translateString("\nYou requested to reset your ${instance_name}"\ " account password.\n\n" \ "Please copy and paste the following link into your browser: \n"\ "${reset_password_link}\n\n" \ "Please note that this link will be valid only one time, until "\ "${expiration_date}.\n" \ "After this date, or after having used this link, you will have to make " \ "a new request\n\n" \ "Thank you", mapping=message_dict) message = message.translate() else: subject = notification_message.getTitle() if notification_message.getContentType() == "text/html": message = notification_message.asEntireHTML(substitution_method_parameter_dict=message_dict) else: message = notification_message.asText(substitution_method_parameter_dict=message_dict) self.getPortalObject().portal_notifications.sendMessage(sender=sender, recipient=[user,], subject=subject, message=message, store_as_event=store_as_event) if REQUEST is not None: msg = translateString("An email has been sent to you.") parameter = urlencode(dict(portal_status_message=msg)) ret_url = '%s/login_form?%s' % (site_url, parameter) return REQUEST.RESPONSE.redirect( ret_url ) def _generateUUID(self, args=""): """ Generate a unique id that will be used as url for password """ # this code is based on # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/213761 # by Carl Free Jr # as uuid module is only available in pyhton 2.5 t = long( time.time() * 1000 ) r = long( random.random()*100000000000000000L ) try: a = socket.gethostbyname( socket.gethostname() ) except: # if we can't get a network address, just imagine one a = random.random()*100000000000000000L data = ' '.join((str(t), str(r), str(a), str(args))) data = md5_new(data).hexdigest() return data def resetPassword(self, reset_key=None, REQUEST=None): """ """ # XXX-Aurel : is it used ? if REQUEST is None: REQUEST = get_request() user_login, expiration_date = self._password_request_dict.get(reset_key, (None, None)) site_url = self.getPortalObject().absolute_url() if REQUEST and 'came_from' in REQUEST: site_url = REQUEST.came_from if reset_key is None or user_login is None: ret_url = '%s/login_form' % site_url return REQUEST.RESPONSE.redirect( ret_url ) # check date current_date = DateTime() if current_date > expiration_date: msg = translateString("Date has expire.") parameter = urlencode(dict(portal_status_message=msg)) ret_url = '%s/login_form?%s' % (site_url, parameter) return REQUEST.RESPONSE.redirect( ret_url ) # redirect to form as all is ok REQUEST.set("password_key", reset_key) return self.reset_password_form(REQUEST=REQUEST) def removeExpiredRequests(self, **kw): """ Browse dict and remove expired request """ current_date = DateTime() for key, (login, date) in self._password_request_dict.items(): if date < current_date: self._password_request_dict.pop(key) def changeUserPassword(self, password, password_key, password_confirm=None, user_login=None, REQUEST=None, **kw): """ Reset the password for a given login """ # check the key register_user_login, expiration_date = self._password_request_dict.get( password_key, (None, None)) current_date = DateTime() msg = None if REQUEST is None: REQUEST = get_request() site_url = self.getPortalObject().absolute_url() if REQUEST and 'came_from' in REQUEST: site_url = REQUEST.came_from if self.getWebSiteValue(): site_url = self.getWebSiteValue().absolute_url() if register_user_login is None: msg = "Key not known. Please ask reset password." elif user_login is not None and register_user_login != user_login: msg = translateString("Bad login provided.") elif current_date > expiration_date: msg = translateString("Date has expire.") if msg is not None: if REQUEST is not None: parameter = urlencode(dict(portal_status_message=msg)) ret_url = '%s/login_form?%s' % (site_url, parameter) return REQUEST.RESPONSE.redirect( ret_url ) else: return msg # all is OK, change password and remove it from request dict self._password_request_dict.pop(password_key) persons = self.getPortalObject().acl_users.erp5_users.getUserByLogin(register_user_login) person = persons[0] person._forceSetPassword(password) person.reindexObject() if REQUEST is not None: msg = translateString("Password changed.") parameter = urlencode(dict(portal_status_message=msg)) ret_url = '%s/login_form?%s' % (site_url, parameter) return REQUEST.RESPONSE.redirect( ret_url )
class OAuthTool(BaseTool): """ OAuthTool is used to allow API authentification """ title = 'OAuth Tool' id = 'portal_oauth' meta_type = 'ERP5 OAuth Tool' portal_type = 'OAuth Tool' allowed_types = () # Declarative Security security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview' ) manage_overview = DTMLFile( 'explainOAuthTool', _dtmldir ) signature_methods = {} def __init__(self, *args, **kw): self.signature_methods = PersistentMapping() self.add_signature_method(OAuthSignatureMethod_PLAINTEXT()) self.add_signature_method(OAuthSignatureMethod_HMAC_SHA1()) self.consumer = OAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET) self.my_request_token = OAuthToken('requestkey', 'requestsecret') self.my_access_token = OAuthToken('accesskey', 'accesssecret') self.nonce = 'nonce' self.verifier = VERIFIER def add_signature_method(self, signature_method): self.signature_methods[signature_method.get_name()] = signature_method return self.signature_methods def fetch_request_token(self, oauth_request): """Processes a request_token request and returns the request token on success. """ try: # Get the request token for authorization. token = self._get_token(oauth_request, 'request') except OAuthError: LOG("initial token request called", 300, "") # No token required for the initial token request. version = self._get_version(oauth_request) consumer = self._get_consumer(oauth_request) try: callback = self.get_callback(oauth_request) except OAuthError: callback = None # 1.0, no callback specified. self._check_signature(oauth_request, consumer, None) # Fetch a new token. if consumer.key == self.consumer.key: if callback: # want to check here if callback is sensible # for mock store, we assume it is LOG("setting callback method %s" %(callback), 300, "") self.my_request_token.set_callback(callback) token = self.my_request_token else: token = None return token def fetch_access_token(self, oauth_request): """Processes an access_token request and returns the access token on success. """ version = self._get_version(oauth_request) consumer = self._get_consumer(oauth_request) try: verifier = self._get_verifier(oauth_request) except OAuthError: verifier = None # Get the request token. token = self._get_token(oauth_request, 'request') self._check_signature(oauth_request, consumer, token) if consumer.key == self.consumer.key and \ token.key == self.my_request_token.key and \ verifier == self.verifier: # want to check here if token is authorized # for mock store, we assume it is return self.my_access_token return None def verify_request(self, oauth_request): """Verifies an api call and checks all the parameters.""" # -> consumer and token version = self._get_version(oauth_request) consumer = self._get_consumer(oauth_request) # Get the access token. token = self._get_token(oauth_request, 'access') self._check_signature(oauth_request, consumer, token) parameters = oauth_request.get_nonoauth_parameters() return consumer, token, parameters def authorize_token(self, token, user): """Authorize a request token.""" if token.key == self.my_request_token.key: return self.my_request_token return None def get_callback(self, oauth_request): """Get the callback URL.""" return oauth_request.get_parameter('oauth_callback') def build_authenticate_header(self, realm=''): """Optional support for the authenticate header.""" return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} def _get_version(self, oauth_request): """Verify the correct version request for this server.""" try: version = oauth_request.get_parameter('oauth_version') except: version = VERSION if version and version != VERSION: raise OAuthError('OAuth version %s not supported.' % str(version)) return version def _get_signature_method(self, oauth_request): """Figure out the signature with some defaults.""" try: signature_method = oauth_request.get_parameter( 'oauth_signature_method') except: signature_method = SIGNATURE_METHOD try: # Get the signature method object. signature_method = self.signature_methods[signature_method] except: signature_method_names = ', '.join(self.signature_methods.keys()) raise OAuthError('Signature method %s not supported try one of the ' 'following: %s' % (signature_method, signature_method_names)) return signature_method def _get_consumer(self, oauth_request): consumer_key = oauth_request.get_parameter('oauth_consumer_key') if consumer_key != self.consumer.key: raise OAuthError('Invalid consumer.') return self.consumer def _get_token(self, oauth_request, token_type='access'): """Try to find the token for the provided request token key.""" token_field = oauth_request.get_parameter('oauth_token') token_attrib = getattr(self, 'my_%s_token' % token_type) if token_field == token_attrib.key: try: callback = self.get_callback(oauth_request) except OAuthError: callback = None # 1.0, no callback specified. LOG("setting callback method %s" %(callback), 300, "in _get_token") token_attrib.set_callback(callback) return token_attrib else: raise OAuthError('Invalid %s token: %s' % (token_type, token_field)) def _get_verifier(self, oauth_request): return oauth_request.get_parameter('oauth_verifier') def _check_signature(self, oauth_request, consumer, token): timestamp, nonce = oauth_request._get_timestamp_nonce() self._check_timestamp(timestamp) self._check_nonce(consumer, token, nonce) signature_method = self._get_signature_method(oauth_request) try: signature = oauth_request.get_parameter('oauth_signature') except: raise OAuthError('Missing signature.') # Validate the signature. valid_sig = signature_method.check_signature(oauth_request, consumer, token, signature) if not valid_sig: key, base = signature_method.build_signature_base_string( oauth_request, consumer, token) raise OAuthError('Invalid signature. Expected signature base ' 'string: %s' % base) built = signature_method.build_signature(oauth_request, consumer, token) def _check_timestamp(self, timestamp): """Verify that timestamp is recentish.""" timestamp = int(timestamp) now = int(time.time()) lapsed = abs(now - timestamp) if lapsed > TIMESTAMP_THRESHOLD: raise OAuthError('Expired timestamp: given %d and now %s has a ' 'greater difference than threshold %d' % (timestamp, now, TIMESTAMP_THRESHOLD)) def _check_nonce(self, consumer, token, nonce): """Verify that the nonce is uniqueish.""" if token and consumer.key == self.consumer.key and \ (token.key == self.my_request_token.key or token.key == self.my_access_token.key) \ and nonce == self.nonce: raise OAuthError('Nonce already used: %s' % str(nonce)) def send_oauth_error(self, err, REQUEST): """ return error """ print err REQUEST.response.setStatus(status=401, reason=err) return REQUEST.response def call(self, REQUEST=None, **kw): """ this method handle all the call on the portal """ path = REQUEST.getURL() headers = REQUEST._auth command = REQUEST['REQUEST_METHOD'] parameters = REQUEST.form postdata = None LOG("-------call--------", 300, "\npath %s\nheader %s\ncommand %s\nparameters %s\n\nXXXXXXXXXXXXXXX" %(path, headers, command, parameters)) # if command == "POST": # import pdb # pdb.set_trace() # construct the oauth request from the request parameters oauth_request = OAuthRequest.from_request(command, path, headers=headers, parameters=parameters, query_string=postdata) # request token if path.startswith(REQUEST_TOKEN_URL): try: # create a request token token = self.fetch_request_token(oauth_request) LOG("Return %s" %(token.to_string()), 300, "") return token.to_string() # # send okay response # self.send_response(200, 'OK') # self.end_headers() # # return the token # self.wfile.write(token.to_string()) except OAuthError, err: raise LOG("Error returned %s" %(err,), 300, "") self.send_oauth_error(err, REQUEST) return # user authorization if path.startswith(AUTHORIZATION_URL): try: return self.manage_oauth_authorize(oauth_token=self._get_token(oauth_request, "request"), oauth_callback=self.get_callback(oauth_request)) # get the request token # token = self.fetch_request_token(oauth_request) # # authorize the token (kind of does nothing for now) # token = self.authorize_token(token, None) # token.set_verifier(VERIFIER) # return token.get_callback_url() # send okay response # self.send_response(200, 'OK') # self.end_headers() # # return the callback url (to show server has it) # self.wfile.write(token.get_callback_url()) except OAuthError, err: self.send_oauth_error(err, REQUEST) return
class Predicate(XMLObject): """ A Predicate object defines a list of criterions which can be applied to test a document or to search for documents. Predicates are defined by a combination of PropertySheet values (ex. membership_criterion_list) and criterion list (ex. quantity is between 0 and 10). An additional script can be associated to extend the standard Predicate semantic with any additional script based test. The idea between Predicate in ERP5 is to have a simple way of defining simple predicates which can be later searched through a simplistic rule based engine and which can still provide complete expressivity through additional scripting. The approach is intended to provide the expressivity of a rule based system without the burden of building a fully expressive rule engine. """ meta_type = 'ERP5 Predicate' portal_type = 'Predicate' add_permission = Permissions.AddPortalContent isPredicate = ConstantGetter('isPredicate', value=True) # Declarative security security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) # Declarative properties property_sheets = ( PropertySheet.Base , PropertySheet.Predicate , PropertySheet.CategoryCore , PropertySheet.SortIndex ) # Declarative interfaces zope.interface.implements( interfaces.IPredicate, ) security.declareProtected( Permissions.AccessContentsInformation, 'test' ) def test(self, context, tested_base_category_list=None, strict_membership=0, isMemberOf=None, **kw): """ A Predicate can be tested on a given context. Parameters can passed in order to ignore some conditions. - tested_base_category_list: this is the list of category that we do want to test. For example, we might want to test only the destination or the source of a predicate. - if strict_membership is specified, we should make sure that we are strictly a member of tested categories - isMemberOf can be a function caching results for CategoryTool.isMemberOf: it is always called with given 'context' and 'strict_membership' values, and different categories. """ self = self.asPredicate() if self is None: # asPredicate returned None, so this predicate never applies. # But if we reach this it is because catalog is not up to date. return False result = 1 if getattr(aq_base(self), '_identity_criterion', None) is None: self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() # LOG('PREDICATE TEST', 0, # 'testing %s on context of %s' % \ # (self.getRelativeUrl(), context.getRelativeUrl())) for property, value in self._identity_criterion.iteritems(): if isinstance(value, (list, tuple)): result = context.getProperty(property) in value else: result = context.getProperty(property) == value # LOG('predicate test', 0, # '%s after prop %s : %s == %s' % \ # (result, property, context.getProperty(property), value)) if not result: return result for property, (min, max) in self._range_criterion.iteritems(): value = context.getProperty(property) if min is not None: result = value >= min # LOG('predicate test', 0, # '%s after prop %s : %s >= %s' % \ # (result, property, value, min)) if not result: return result if max is not None: result = value < max # LOG('predicate test', 0, # '%s after prop %s : %s < %s' % \ # (result, property, value, max)) if not result: return result multimembership_criterion_base_category_list = \ self.getMultimembershipCriterionBaseCategoryList() membership_criterion_base_category_list = \ self.getMembershipCriterionBaseCategoryList() tested_base_category = {} # LOG('predicate test', 0, # 'categories will be tested in multi %s single %s as %s' % \ # (multimembership_criterion_base_category_list, # membership_criterion_base_category_list, # self.getMembershipCriterionCategoryList())) # Test category memberships. Enable the read-only transaction cache # because this part is strictly read-only, and context.isMemberOf # is very expensive when the category list has many items. if isMemberOf is None: isMemberOf = context._getCategoryTool().isMemberOf with readOnlyTransactionCache(): for c in self.getMembershipCriterionCategoryList(): bc = c.split('/', 1)[0] if tested_base_category_list is None or bc in tested_base_category_list: if bc in multimembership_criterion_base_category_list: if not isMemberOf(context, c, strict_membership=strict_membership): return 0 elif bc in membership_criterion_base_category_list and \ not tested_base_category.get(bc): tested_base_category[bc] = \ isMemberOf(context, c, strict_membership=strict_membership) if 0 in tested_base_category.itervalues(): return 0 # Test method calls test_method_id_list = self.getTestMethodIdList() if test_method_id_list is not None : for test_method_id in test_method_id_list : if test_method_id is not None: method = getattr(context,test_method_id) try: result = method(self) except TypeError: if method.func_code.co_argcount != isinstance(method, MethodType): raise # backward compatibilty with script that takes no argument warn('Predicate %s uses an old-style method (%s) that does not' ' take the predicate as argument' % ( self.getRelativeUrl(), method.__name__), DeprecationWarning) result = method() # LOG('predicate test', 0, # '%s after method %s ' % (result, test_method_id)) if not result: return result test_tales_expression = self.getTestTalesExpression() if test_tales_expression != 'python: True': expression = Expression(test_tales_expression) from Products.ERP5Type.Utils import createExpressionContext # evaluate a tales expression with the tested value as context result = expression(createExpressionContext(context)) return result @UnrestrictedMethod def _unrestrictedResolveCategory(self, *args): # Categories used on predicate can be not available to user query, which # shall be applied with predicate. portal_categories = getToolByName(self, 'portal_categories') return portal_categories.resolveCategory(*args) security.declareProtected( Permissions.AccessContentsInformation, 'buildSQLQuery' ) def buildSQLQuery(self, strict_membership=0, table='category', join_table='catalog', join_column='uid', **kw): """ A Predicate can be rendered as an SQL expression. This can be used to generate SQL requests in reports or in catalog search queries. XXX - This method is not implemented yet """ # Build the identity criterion catalog_kw = {} catalog_kw.update(kw) # query_table, REQUEST, ignore_empty_string, **kw criterion_list = self.getCriterionList() # BBB: accessor is not present on old Predicate property sheet. if criterion_list or getattr(self, 'isEmptyPredicateValid', lambda: True)(): for criterion in criterion_list: if criterion.min and criterion.max: catalog_kw[criterion.property] = { 'query' : (criterion.min, criterion.max), 'range' : 'minmax' } elif criterion.min: catalog_kw[criterion.property] = { 'query' : criterion.min, 'range' : 'min' } elif criterion.max: catalog_kw[criterion.property] = { 'query' : criterion.max, 'range' : 'max' } else: # if a filter was passed as argument if catalog_kw.has_key(criterion.property): if isinstance(catalog_kw[criterion.property], (tuple, list)): catalog_filter_set = set(catalog_kw[criterion.property]) else: catalog_filter_set = set([catalog_kw[criterion.property]]) if isinstance(criterion.identity, (tuple, list)): parameter_filter_set = set(criterion.identity) else: parameter_filter_set = set([criterion.identity]) catalog_kw[criterion.property] = \ list(catalog_filter_set.intersection(parameter_filter_set)) else: catalog_kw[criterion.property] = criterion.identity else: # By catalog definition, no object has uid 0, so this condition forces an # empty result. catalog_kw['uid'] = 0 portal_catalog = getToolByName(self, 'portal_catalog') from_table_dict = {} # First build SQL for membership criteria # It would be much nicer if all this was handled by the catalog in a central place membership_dict = {} for base_category in self.getMembershipCriterionBaseCategoryList(): membership_dict[base_category] = [] # Init dict with valid base categories for category in self.getMembershipCriterionCategoryList(): base_category = category.split('/')[0] # Retrieve base category if membership_dict.has_key(base_category): category_value = self._unrestrictedResolveCategory(category, None) if category_value is not None: table_alias = "single_%s_%s" % (table, base_category) from_table_dict[table_alias] = 'category' membership_dict[base_category].append(category_value.asSQLExpression( strict_membership=strict_membership, table=table_alias, base_category=base_category)) membership_select_list = [] for expression_list in membership_dict.values(): or_expression = ' OR '.join(expression_list) if or_expression: membership_select_list.append('( %s )' % or_expression) # Then build SQL for multimembership_dict criteria multimembership_dict = {} for base_category in self.getMultimembershipCriterionBaseCategoryList(): multimembership_dict[base_category] = [] # Init dict with valid base categories join_count = 0 for category in self.getMembershipCriterionCategoryList(): base_category = category.split('/')[0] # Retrieve base category if multimembership_dict.has_key(base_category): category_value = self._unrestrictedResolveCategory(category) if category_value is not None: join_count += 1 table_alias = "multi_%s_%s" % (table, join_count) from_table_dict[table_alias] = 'category' multimembership_dict[base_category].append(category_value.asSQLExpression( strict_membership=strict_membership, table=table_alias, base_category=base_category)) multimembership_select_list = [] for expression_list in multimembership_dict.values(): and_expression = ' AND '.join(expression_list) if and_expression: multimembership_select_list.append(and_expression) # Build the join where expression join_select_list = [] for k in from_table_dict.iterkeys(): join_select_list.append('%s.%s = %s.uid' % (join_table, join_column, k)) sql_text = ' AND '.join(join_select_list + membership_select_list + multimembership_select_list) # Now merge identity and membership criteria if len(sql_text): catalog_kw['where_expression'] = SQLQuery(sql_text) else: catalog_kw['where_expression'] = '' # force implicit join catalog_kw['implicit_join'] = True sql_query = portal_catalog.buildSQLQuery(**catalog_kw) # XXX from_table_list is None most of the time after the explicit_join work for alias, table in sql_query['from_table_list']: if from_table_dict.has_key(alias): raise KeyError, "The same table is used twice for an identity criterion and for a membership criterion" from_table_dict[alias] = table sql_query['from_table_list'] = from_table_dict.items() return sql_query # Compatibililty SQL Sql security.declareProtected( Permissions.AccessContentsInformation, 'buildSqlQuery' ) buildSqlQuery = buildSQLQuery security.declareProtected( Permissions.AccessContentsInformation, 'asSQLExpression' ) def asSQLExpression(self, strict_membership=0, table='category'): """ A Predicate can be rendered as an SQL expression. This can be used to generate SQL requests in reports or in catalog search queries. """ return self.buildSQLQuery(strict_membership=strict_membership, table=table)['where_expression'] # Compatibililty SQL Sql security.declareProtected( Permissions.AccessContentsInformation, 'asSqlExpression' ) asSqlExpression = asSQLExpression security.declareProtected( Permissions.AccessContentsInformation, 'asSQLJoinExpression' ) def asSQLJoinExpression(self, strict_membership=0, table='category', join_table='catalog', join_column='uid'): """ """ table_list = self.buildSQLQuery(strict_membership=strict_membership, table=table)['from_table_list'] sql_text_list = map(lambda (a,b): '%s AS %s' % (b,a), filter(lambda (a,b): a != join_table, table_list)) return ' , '.join(sql_text_list) # Compatibililty SQL Sql security.declareProtected( Permissions.AccessContentsInformation, 'asSqlJoinExpression' ) asSqlJoinExpression = asSQLJoinExpression def searchResults(self, **kw): """ """ portal_catalog = getToolByName(self, 'portal_catalog') return portal_catalog.searchResults(build_sql_query_method=self.buildSQLQuery,**kw) def countResults(self, REQUEST=None, used=None, **kw): """ """ portal_catalog = getToolByName(self, 'portal_catalog') return portal_catalog.countResults(build_sql_query_method=self.buildSQLQuery,**kw) security.declareProtected( Permissions.AccessContentsInformation, 'getCriterionList' ) def getCriterionList(self, **kw): """ Returns the list of criteria which are defined by the Predicate. Each criterion is returned in a TempBase instance intended to be displayed in a ListBox. XXX - It would be better to return criteria in a Criterion class instance """ if getattr(aq_base(self), '_identity_criterion', None) is None: self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() criterion_dict = {} for p in self.getCriterionPropertyList(): criterion_dict[p] = newTempBase(self, 'new_%s' % p) criterion_dict[p].identity = self._identity_criterion.get(p, None) criterion_dict[p].uid = 'new_%s' % p criterion_dict[p].property = p criterion_dict[p].min = self._range_criterion.get(p, (None, None))[0] criterion_dict[p].max = self._range_criterion.get(p, (None, None))[1] criterion_list = criterion_dict.values() criterion_list.sort() return criterion_list security.declareProtected( Permissions.ModifyPortalContent, 'setCriterion' ) def setCriterion(self, property, identity=None, min=None, max=None, **kw): """ This methods sets parameters of a criterion. There is at most one criterion per property. Defined parameters are identity -- if not None, allows for testing identity of the property with the provided value min -- if not None, allows for testing that the property is greater than min max -- if not None, allows for testing that the property is greater than max """ # XXX 'min' and 'max' are built-in functions. if getattr(aq_base(self), '_identity_criterion', None) is None: self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() if identity is not None : self._identity_criterion[property] = identity if min == '': min = None if max == '': max = None if min is None and max is None: try: del self._range_criterion[property] except KeyError: pass else: self._range_criterion[property] = (min, max) self.reindexObject() security.declareProtected( Permissions.ModifyPortalContent, 'edit' ) def edit(self, **kwd): """ The edit method is overriden so that any time a criterion_property_list property is defined, a list of criteria is created to match the provided criterion_property_list. """ if getattr(aq_base(self), '_identity_criterion', None) is None: self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() if 'criterion_property_list' in kwd: criterion_property_list = kwd['criterion_property_list'] identity_criterion = PersistentMapping() range_criterion = PersistentMapping() for criterion in self._identity_criterion.iterkeys() : if criterion in criterion_property_list : identity_criterion[criterion] = self._identity_criterion[criterion] for criterion in self._range_criterion.iterkeys() : if criterion in criterion_property_list : range_criterion[criterion] = self._range_criterion[criterion] self._identity_criterion = identity_criterion self._range_criterion = range_criterion kwd['reindex_object'] = 1 return self._edit(**kwd) # Predicate fusion method security.declareProtected( Permissions.ModifyPortalContent, 'setPredicateCategoryList' ) def setPredicateCategoryList(self, category_list): """ This method updates a Predicate by implementing an AND operation on all predicates (or categories) provided in category_list. Categories behave as a special kind of predicate which only acts on category membership. WARNING: this method does not take into account scripts at this point. """ category_tool = aq_inner(self.portal_categories) base_category_id_list = category_tool.objectIds() membership_criterion_category_list = [] membership_criterion_base_category_list = [] multimembership_criterion_base_category_list = [] test_method_id_list = [] criterion_property_list = [] # reset criterions self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() for c in category_list: bc = c.split('/')[0] if bc in base_category_id_list: # This is a category membership_criterion_category_list.append(c) membership_criterion_base_category_list.append(bc) else: predicate_value = category_tool.resolveCategory(c) if predicate_value is not None: criterion_property_list.extend(predicate_value.getCriterionPropertyList()) membership_criterion_category_list.extend( predicate_value.getMembershipCriterionCategoryList()) membership_criterion_base_category_list.extend( predicate_value.getMembershipCriterionBaseCategoryList()) multimembership_criterion_base_category_list.extend( predicate_value.getMultimembershipCriterionBaseCategoryList()) test_method_id_list += list(predicate_value.getTestMethodIdList() or []) for p in predicate_value.getCriterionList(): self.setCriterion(p.property, identity=p.identity, min=p.min, max=p.max) self.setCriterionPropertyList(criterion_property_list) self._setMembershipCriterionCategoryList(membership_criterion_category_list) self._setMembershipCriterionBaseCategoryList(membership_criterion_base_category_list) self._setMultimembershipCriterionBaseCategoryList(multimembership_criterion_base_category_list) self._setTestMethodIdList(test_method_id_list) self.reindexObject() security.declareProtected(Permissions.AccessContentsInformation, 'generatePredicate') def generatePredicate(self, multimembership_criterion_base_category_list=(), membership_criterion_base_category_list=(), criterion_property_list=(), identity_criterion=None, range_criterion=None,): """ This method generates a new temporary predicate based on an ad-hoc interpretation of local properties of an object. For example, a start_range_min property will be interpreted as a way to define a min criterion on start_date. The purpose of this method is to be called from a script called PortalType_asPredicate to ease the generation of Predicates based on range properties. It should be considered mostly as a trick to simplify the development of Predicates and forms. """ new_membership_criterion_category_list = list(self.getMembershipCriterionCategoryList()) new_membership_criterion_base_category_list = list(self.getMembershipCriterionBaseCategoryList()) new_multimembership_criterion_base_category_list = list(self.getMultimembershipCriterionBaseCategoryList()) for base_category in multimembership_criterion_base_category_list: category_list = self.getProperty(base_category + '_list') if category_list is not None and len(category_list)>0: for category in category_list: new_membership_criterion_category_list.append(base_category + '/' + category) if base_category not in new_multimembership_criterion_base_category_list: new_multimembership_criterion_base_category_list.append(base_category) for base_category in membership_criterion_base_category_list: category_list = self.getProperty(base_category + '_list') if category_list is not None and len(category_list)>0: for category in category_list: new_membership_criterion_category_list.append(base_category + '/' + category) if base_category not in new_membership_criterion_base_category_list: new_membership_criterion_base_category_list.append(base_category) new_criterion_property_list = list(self.getCriterionPropertyList()) # We need to build new criteria for asContext, and we should not # modify the original, so we always make copies. Since the usage is # temporary, use dicts instead of persistent mappings. new_identity_criterion = dict(getattr(self, '_identity_criterion', None) or {}) new_identity_criterion.update(identity_criterion or {}) new_range_criterion = dict(getattr(self, '_range_criterion', None) or {}) new_range_criterion.update(range_criterion or {}) # Look at local properties and make it criterion properties for property in criterion_property_list: if property not in self.getCriterionPropertyList() \ and property in self.propertyIds(): new_criterion_property_list.append(property) property_min = property + '_range_min' property_max = property + '_range_max' if getattr(self, 'get%s' % convertToUpperCase(property), None) is not None\ and self.getProperty(property) is not None: new_identity_criterion[property] = self.getProperty(property) elif getattr(self, 'get%s' % convertToUpperCase(property_min), None) is not None: min = self.getProperty(property_min) max = self.getProperty(property_max) new_range_criterion[property] = (min,max) # Return a new context with new properties, like if # we have a predicate with local properties new_self = self.asContext( membership_criterion_category=new_membership_criterion_category_list, membership_criterion_base_category=new_membership_criterion_base_category_list, multimembership_criterion_base_category=new_multimembership_criterion_base_category_list, criterion_property_list=new_criterion_property_list, _identity_criterion=new_identity_criterion, _range_criterion=new_range_criterion) return new_self security.declareProtected(Permissions.AccessContentsInformation, 'asPredicate') def asPredicate(self, script_id=None): """ This method tries to convert the current Document into a predicate looking up methods named Class_asPredictae, MetaType_asPredicate, PortalType_asPredicate """ cache = getTransactionalVariable() key = id(self), script_id if 'asPredicate' in cache: cache = cache['asPredicate'] if key in cache: return cache[key] else: cache = cache['asPredicate'] = {} script = self._getTypeBasedMethod('asPredicate', script_id) if script is not None: self = script() cache[key] = self return self def searchPredicate(self, **kw): """ Returns a list of documents matching the predicate TO BE IMPLEMENTED using portal_catalog(**kw) """ pass security.declareProtected(Permissions.AccessContentsInformation, 'getMembershipCriterionCategoryList') def getMembershipCriterionCategoryList(self, filter=None, **kw): """ If filter is specified, return category only or document only in membership_criterion_category values. """ all_list = self._baseGetMembershipCriterionCategoryList() if filter in ('category', 'document'): portal_categories = self.getPortalObject().portal_categories result_dict = {'category':[], 'document':[]} for x in all_list: try: if portal_categories.restrictedTraverse(x).getPortalType() == \ 'Category': result_dict['category'].append(x) else: result_dict['document'].append(x) except KeyError: result_dict['document'].append(x) return result_dict[filter] else: return all_list security.declareProtected(Permissions.ModifyPortalContent, 'setMembershipCriterionDocumentList' ) def setMembershipCriterionDocumentList(self, document_list): """ Appends to membership_criterion_category values. """ return self.setMembershipCriterionCategoryList( (self.getMembershipCriterionCategoryList() + document_list))
def generateNewIdList(self, id_group=None, id_count=1, default=None, store=_marker, id_generator=None, poison=False): """ Generate a list of next ids in the sequence of ids of a particular group """ if id_group in (None, 'None'): raise ValueError('%r is not a valid id_group' % id_group) # for compatibilty with sql data, must not use id_group as a list if not isinstance(id_group, str): id_group = repr(id_group) warnings.warn('id_group must be a string, other types ' 'are deprecated.', DeprecationWarning) if id_generator is None: id_generator = 'uid' if store is not _marker: warnings.warn("Use of 'store' argument is deprecated.", DeprecationWarning) try: #use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) new_id_list = last_generator.generateNewIdList(id_group=id_group, id_count=id_count, default=default, poison=poison) except (KeyError, ValueError): # XXX backward compatiblity if self.getTypeInfo(): LOG('generateNewIdList', ERROR, 'while generating id') raise else: # Compatibility code below, in case the last version of erp5_core # is not installed yet warnings.warn("You are using an old version of erp5_core to generate" "ids.\nPlease update erp5_core business template to " "use new id generators", DeprecationWarning) new_id = None if default is None: default = 1 # XXX It's temporary, a New API will be implemented soon # the code will be change portal = self.getPortalObject() try: query = portal.IdTool_zGenerateId commit = portal.IdTool_zCommit except AttributeError: portal_catalog = portal.portal_catalog.getSQLCatalog() query = portal_catalog.z_portal_ids_generate_id commit = portal_catalog.z_portal_ids_commit try: result = query(id_group=id_group, id_count=id_count, default=default) finally: commit() new_id = result[0]['LAST_INSERT_ID()'] if store: if getattr(aq_base(self), 'dict_length_ids', None) is None: # Length objects are stored in a persistent mapping: there is one # Length object per id_group. self.dict_length_ids = PersistentMapping() if self.dict_length_ids.get(id_group) is None: self.dict_length_ids[id_group] = Length(new_id) self.dict_length_ids[id_group].set(new_id) if six.PY2: new_id_list = range(new_id - id_count, new_id) else: new_id_list = list(range(new_id - id_count, new_id)) return new_id_list
def updateMovementCollection(rule, context, *args, **kw): orig_updateMovementCollection(rule, context, *args, **kw) new_parent = context.getParentValue() for sm in context.getMovementList(): delivery = sm.getDelivery() if delivery: sm_dict = old_dict.pop(delivery) else: sm_dict = order_dict[new_parent] order_dict[sm] = sm_dict k = get_matching_key(sm) sm_list = sm_dict.pop(k, ()) if len(sm_list) > 1: # Heuristic to find matching old simulation movements for the # currently expanded applied rule. We first try to preserve same # tree structure (new & old parent SM match), then we look for an # old possible parent that is in the same branch. try: old_parent = old_dict[new_parent] except KeyError: old_parent = simulation_tool best_dict = {} for old_sm in sm_list: parent = old_sm.getParentValue().getParentValue() if parent is old_parent: parent = None elif not (parent.aq_inContextOf(old_parent) or old_parent.aq_inContextOf(parent)): continue best_dict.setdefault(parent, []).append(old_sm) try: best_sm_list = best_dict[None] except KeyError: best_sm_list, = best_dict.values() if len(best_sm_list) < len(sm_list): sm_dict[k] = list(set(sm_list).difference(best_sm_list)) sm_list = best_sm_list if len(sm_list) > 1: kw = sm.__dict__.copy() # We may have several old matching SM, e.g. in case of split. for old_sm in sm_list: movement = old_sm.getDeliveryValue() if sm is None: sm = context.newContent(portal_type=rule.movement_type) sm.__dict__ = dict(kw, **sm.__dict__) order_dict[sm] = sm_dict if delivery: assert movement.getRelativeUrl() == delivery elif movement is not None: sm._setDeliveryValue(movement) delivery_set.add(sm.getExplanationValue()) try: sm.delivery_ratio = old_sm.aq_base.delivery_ratio except AttributeError: pass recorded_property_dict = {} edit_kw = {} kw['quantity'] = 0 for tester in rule._getUpdatingTesterList(): old = get_original_property_dict(tester, old_sm, sm, movement) if old is not None: new = tester.getUpdatablePropertyDict(sm, movement) if old != new: edit_kw.update(old) if 'quantity' in new and old_sm is not sm_list[-1]: quantity = new.pop('quantity') kw['quantity'] = quantity - old.pop('quantity') if new != old or sm.quantity != quantity: raise NotImplementedError # quantity_unit/efficiency ? else: recorded_property_dict.update(new) if recorded_property_dict: sm._recorded_property_dict = PersistentMapping( recorded_property_dict) sm._edit(**edit_kw) old_dict[sm] = old_sm sm = None
class XMLMatrix(Folder): """ A mix-in class which provides a matrix like access to objects. Matrices are of any dimension. A single XMLMatrix may contain multiple matrices, of different dimension. Each matrix is associated to a so-called 'base_id'. We still must make XMLMatrix a subclass of Base so that we can inherit from ExtensionClass.Base which is required for multiple inheritance to work as expected. Read this for more information: http://www.daa.com.au/pipermail/pygtk/2001-May/001180.html In our case, we will use Folder because we want to inherit from Folder consistency checking """ # Declarative security security = ClassSecurityInfo() # Matrix Methods security.declareProtected( Permissions.AccessContentsInformation, 'getCell' ) def getCell(self, *kw , **kwd): """ Access a cell at row and column """ if getattr(aq_base(self), 'index', None) is None: return None base_id = kwd.get('base_id', "cell") if not self.index.has_key(base_id): return None cell_id = self.keyToId(kw, base_id = base_id) if cell_id is None: return None return self.get(cell_id) security.declareProtected( Permissions.AccessContentsInformation, 'getCellProperty' ) def getCellProperty(self, *kw , **kwd): """ Get a property of a cell at row and column """ base_id= kwd.get('base_id', "cell") cell = self.getCell(*kw, **kwd) if cell is None: return None return cell.getProperty(base_id) security.declareProtected( Permissions.AccessContentsInformation, 'hasCell' ) def hasCell(self, *kw , **kwd): """ Checks if matrix corresponding to base_id contains cell specified by *kw coordinates. """ return self.getCell(*kw, **kwd) is not None security.declareProtected( Permissions.AccessContentsInformation, 'hasCellContent' ) def hasCellContent(self, base_id='cell'): """ Checks if matrix corresponding to base_id contains cells. """ aq_self = aq_base(self) if getattr(aq_self, 'index', None) is None: return 0 if not self.index.has_key(base_id): return 0 for i in self.getCellIds(base_id=base_id): if hasattr(self, i): # We should try to use aq_self if possible but XXX return 1 return 0 security.declareProtected( Permissions.AccessContentsInformation, 'hasInRange' ) def hasInRange(self, *kw , **kwd): """ Checks if *kw coordinates are in the range of the matrix in kwd['base_id']. """ if getattr(aq_base(self), 'index', None) is None: return 0 base_id = kwd.get('base_id', "cell") if not self.index.has_key(base_id): return 0 base_item = self.index[base_id] for i, my_id in enumerate(kw): if not base_item.has_key(i) or not base_item[i].has_key(my_id): return 0 return 1 security.declareProtected( Permissions.ModifyPortalContent, '_setCellRange' ) def _setCellRange(self, *kw, **kwd): """ Set a new range for a matrix. If needed, it will resize and/or reorder matrix content. """ movement = {} # Maps original cell id to its new id for each moved cell. new_index = PersistentMapping() base_id = kwd.get('base_id', 'cell') if getattr(aq_base(self), 'index', None) is None: self.index = PersistentMapping() # Return if previous range is the same current_range = self.getCellRange(base_id=base_id) if current_range == list(kw): # kw is a tuple return # Create the new index for the range given in *kw # *kw practical example: # kw = [ ['color/blue', 'color/red'], ['size/S', 'size/L']] for i, index_ids in enumerate(kw): temp = PersistentMapping() for j, my_id in enumerate(index_ids): temp[my_id] = j new_index[i] = temp if self.index.has_key(base_id): # Compute cell movement from their position in previous range to their # position in the new range. for i, i_value in self.index[base_id].iteritems(): if new_index.has_key(i): temp = {} for my_id, my_value in i_value.iteritems(): temp[my_value] = new_index[i].get(my_id) movement[i] = temp # List all valid cell ids for current base_id. object_id_list = [] for obj in self.objectValues(): object_id = obj.getId() if object_id.find(base_id) == 0: # Check that all '_'-separated fields are of int type. if (object_id) > len(base_id): try: int(object_id[len(base_id)+1:].split('_')[0]) test = self._getOb(object_id) # If the object was created # during this transaction, # then we do not need to # work on it object_id_list.append(object_id) except (ValueError, KeyError): pass # Prepend 'temp_' to all cells, to avoid id conflicts while renaming. for object_id in object_id_list: new_name = 'temp_' + object_id obj = self._getOb(object_id) self._delObject(object_id) obj.id = new_name self._setObject(new_name, aq_base(obj)) # Rename all cells to their final name. for object_id in object_id_list: # Retrieve the place of the object, for movement_0_0 it is ['0','0'] object_place = object_id[len(base_id)+1:].split('_') to_delete = 1 # We must have the same number of dimensions if len(object_place) == len(new_index): # Let us browse each dimension of the previous index for i in range(len(object_place)): # Build each part of the nex id by looking up int. values old_place = int(object_place[i]) # We are looking inside the movement dictionnary where # we should move the object, so for example # 'qantity_2_5' is renamed as 'quantity_4_3' if movement.has_key(i): if movement[i].has_key(old_place): # Replace the place of the cell only if there where a change if (movement[i][old_place]) != None: object_place[i] = str(movement[i][old_place]) to_delete = 0 else: object_place[i] = None # XXX In this case, we delete every cell wich are not in the # movement dictionnary, may be this is very bad, so may be # we need to add an extra check here, ie if # if movement[i].has_key(old_place) returns None, # We may want to keep the cell, but only if we are sure # the movement dictionnary will not define a new cell with this id new_object_id_list = [] temp_object_id = 'temp_' + object_id o = self._getOb(temp_object_id) if not to_delete and not (None in object_place): self._delObject(temp_object_id) # In all cases, we have # to remove the temp object object_place.insert(0, base_id) new_name = '_'.join(object_place) o.id = new_name new_object_id_list.extend(new_name) self._setObject(new_name, aq_base(o)) if new_name != object_id: # Theses two lines are very important, if the object is renamed # then we must uncatalog the previous one o.unindexObject(path='%s/%s' % (self.getUrl() , object_id)) # Force a new uid to be allocated, because unindexObject creates # an activity which will later delete lines from catalog based # on their uid, and it is not garanted that indexation will happen # after this deletion. # It is bad to waste uids, but this data structure offers no # alternative because cell id gives its index in the matrix, # so reordering axes requires the cell id to change. # XXX: It can be improved, but requires most of this file to be # rewritten, and compatibility code must be written as data # structure would most probably change. o.uid = None o.reindexObject() # we reindex in case position has changed # uid should be consistent else: # In all cases, we have to remove the temp object #WARNING -> if path is not good, it will not be able to uncatalog !!! #o.immediateReindexObject() # STILL A PROBLEM -> getUidForPath XXX if object_id not in new_object_id_list: # do not unindex a new object o.isIndexable = ConstantGetter('isIndexable', value=True) o.unindexObject(path='%s/%s' % (self.getUrl() , object_id)) # unindexed already forced o.isIndexable = ConstantGetter('isIndexable', value=False) self._delObject(temp_object_id) # object will be removed # from catalog automaticaly # We don't need the old index any more, we # can set the new index self.index[base_id] = new_index security.declareProtected( Permissions.ModifyPortalContent, 'setCellRange' ) def setCellRange(self, *kw, **kwd): """ Update the matrix ranges using provided lists of indexes (kw). Any number of list can be provided """ self._setCellRange(*kw, **kwd) self.reindexObject() security.declareProtected(Permissions.ModifyPortalContent, '_updateCellRange') def _updateCellRange(self, base_id, **kw): """ The asCellRange script is Portal Type dependent which is not the case with this kind of code a better implementation consists in defining asCellRange as a generic method at matrix level (OverridableMethod(portal_type)) which lookups for script in class, meta_type and PT form interaction could be implemented with interaction workflow this method should be renamed updateCellRange or updateMatrixCellRange base_id is parameter of updateCellRange asCellRange scripts should be unified if possible """ script = self._getTypeBasedMethod('asCellRange', **kw) if script is None: raise UnboundLocalError,\ "Did not find cell range script for portal type: %r" %\ self.getPortalType() cell_range = script(base_id=base_id, matrixbox=0, **kw) self._setCellRange(base_id=base_id, *cell_range) security.declareProtected(Permissions.ModifyPortalContent, 'updateCellRange') def updateCellRange(self, base_id='cell', **kw): """ same as _updateCellRange, but reindex the object. """ self._updateCellRange(base_id=base_id, **kw) self.reindexObject() security.declareProtected( Permissions.ModifyPortalContent, '_renameCellRange' ) def _renameCellRange(self, *kw, **kwd): """ Rename a range for a matrix, this method can also handle a changement of the size of a matrix """ base_id = kwd.get('base_id', 'cell') if getattr(aq_base(self), 'index', None) is None: self.index = PersistentMapping() # Return if previous range is the same current_range = self.getCellRange(base_id=base_id) or [] if current_range == list(kw): # kw is a tuple LOG('XMLMatrix',0,'return form _setCellRange - no need to change range') return current_len = len(current_range) new_len = len(kw) len_delta = new_len - current_len # We must make sure the base_id exists # in the event of a matrix creation for example if not self.index.has_key(base_id): # Create an index for this base_id self.index[base_id] = PersistentMapping() cell_id_list = [] for cell_id in self.getCellIdList(base_id = base_id): if self.get(cell_id) is not None: cell_id_list.append(cell_id) # First, delete all cells which are out of range. size_list = map(len, kw) if len_delta < 0: size_list.extend([1] * (-len_delta)) def is_in_range(cell_id): for i, index in enumerate(cell_id[len(base_id)+1:].split('_')): if int(index) >= size_list[i]: self._delObject(cell_id) return False return True cell_id_list = filter(is_in_range, cell_id_list) # Secondly, rename coordinates. This does not change cell ids. for i in range(max(new_len, current_len)): if i >= new_len: del self.index[base_id][i] else: if i >= current_len: self.index[base_id][i] = PersistentMapping() for place in self.index[base_id][i].keys(): if place not in kw[i]: del self.index[base_id][i][place] for j, place in enumerate(kw[i]): self.index[base_id][i][place] = j # Lastly, rename ids and catalog/uncatalog everything. if len_delta > 0: # Need to move, say, base_1_2 -> base_1_2_0 appended_id = '_0' * len_delta for old_id in cell_id_list: cell = self.get(old_id) if cell is not None: new_id = old_id + appended_id self._delObject(old_id) cell.isIndexable = ConstantGetter('isIndexable', value=False) cell.id = new_id self._setObject(new_id, aq_base(cell)) cell.isIndexable = ConstantGetter('isIndexable', value=True) cell.reindexObject() #cell.unindexObject(path='%s/%s' % (self.getUrl(), old_id)) elif len_delta < 0: # Need to move, say, base_1_2_0 -> base_1_2 removed_id_len = 2 * (-len_delta) for old_id in cell_id_list: cell = self.get(old_id) if cell is not None: new_id = old_id[:-removed_id_len] self._delObject(old_id) cell.isIndexable = ConstantGetter('isIndexable', value=False) cell.id = new_id self._setObject(new_id, aq_base(cell)) cell.isIndexable = ConstantGetter('isIndexable', value=True) cell.reindexObject() #cell.unindexObject(path='%s/%s' % (self.getUrl(), old_id)) security.declareProtected( Permissions.ModifyPortalContent, 'renameCellRange' ) def renameCellRange(self, *kw, **kwd): """ Update the matrix ranges using provided lists of indexes (kw). This keep cell values if we add/remove dimensions Any number of list can be provided """ self._renameCellRange(*kw, **kwd) self.reindexObject() security.declareProtected(Permissions.AccessContentsInformation, 'getCellRange') def getCellRange(self, base_id='cell'): """ Returns the cell range as a list of index ids """ if getattr(aq_base(self), 'index', None) is None: return [] cell_range = self.index.get(base_id, None) if cell_range is None: return None result = [] for value in cell_range.itervalues(): result_items = sorted(value.iteritems(), key=lambda x:x[1]) result.append([x[0] for x in result_items]) return result security.declareProtected( Permissions.ModifyPortalContent, 'newCell' ) def newCell(self, *kw, **kwd): """ This method creates a new cell """ if getattr(aq_base(self), 'index', None) is None: return None base_id = kwd.get('base_id', "cell") if not self.index.has_key(base_id): return None cell_id = self.keyToId(kw, base_id = base_id) if cell_id is None: raise KeyError, 'Invalid key: %s' % str(kw) cell = self.get(cell_id) if cell is not None: return cell else: return self.newCellContent(cell_id,**kwd) security.declareProtected( Permissions.ModifyPortalContent, 'newCellContent' ) def newCellContent(self, cell_id, portal_type=None, **kw): """ Creates a new content as a cell. This method is meant to be overriden by subclasses. """ if portal_type is None: for x in self.allowedContentTypes(): portal_type_id = x.getId() if portal_type_id.endswith(' Cell'): portal_type = portal_type_id break return self.newContent(id=cell_id, portal_type=portal_type, **kw) security.declareProtected( Permissions.AccessContentsInformation, 'getCellKeyList' ) def getCellKeyList(self, base_id = 'cell'): """ Returns a list of possible keys as tuples """ if getattr(aq_base(self), 'index', None) is None: return () if not self.index.has_key(base_id): return () index = self.index[base_id] id_tuple = [v.keys() for v in index.itervalues()] if len(id_tuple) == 0: return () return cartesianProduct(id_tuple) security.declareProtected( Permissions.AccessContentsInformation, 'getCellKeys' ) getCellKeys = getCellKeyList # We should differenciate in future existing tuples from possible tuples security.declareProtected( Permissions.AccessContentsInformation, 'getCellRangeKeyList' ) getCellRangeKeyList = getCellKeyList security.declareProtected( Permissions.AccessContentsInformation, 'keyToId' ) def keyToId(self, kw, base_id = 'cell'): """ Converts a key into a cell id """ index = self.index[base_id] cell_id_list = [base_id] append = cell_id_list.append for i, item in enumerate(kw): try: append(str(index[i][item])) except KeyError: return None return '_'.join(cell_id_list) security.declareProtected( Permissions.AccessContentsInformation, 'getCellIdList' ) def getCellIdList(self, base_id = 'cell'): """ Returns a list of possible ids as tuples """ if getattr(aq_base(self), 'index', None) is None: return () if not self.index.has_key(base_id): return () result = [] append = result.append for kw in self.getCellKeys(base_id = base_id): cell_id = self.keyToId(kw, base_id = base_id ) if cell_id is not None: append(cell_id) return result security.declareProtected( Permissions.AccessContentsInformation, 'getCellIds' ) getCellIds = getCellIdList security.declareProtected( Permissions.AccessContentsInformation, 'cellIds' ) cellIds = getCellIdList # We should differenciate in future all possible ids for existing ids security.declareProtected( Permissions.AccessContentsInformation, 'getCellRangeIdList' ) getCellRangeIdList = getCellIdList security.declareProtected( Permissions.AccessContentsInformation, 'getCellValueList' ) def getCellValueList(self, base_id = 'cell'): """ Returns a list of cell values as tuples """ result = [] append = result.append for id in self.getCellIdList(base_id=base_id): o = self.get(id) if o is not None: append(o) return result security.declareProtected( Permissions.AccessContentsInformation, 'cellValues' ) cellValues = getCellValueList security.declareProtected( Permissions.AccessContentsInformation, 'getMatrixList' ) def getMatrixList(self): """ Return possible base_id values """ if getattr(aq_base(self), 'index', None) is None: return () return self.index.keys() security.declareProtected( Permissions.ModifyPortalContent, 'delMatrix' ) def delMatrix(self, base_id = 'cell'): """ Delete all cells for a given base_id XXX BAD NAME: make a difference between deleting matrix and matrix cells """ ids = self.getCellIds(base_id = base_id) my_ids = [] append = my_ids.append for i in self.objectIds(): if i in ids: append(i) if len(my_ids) > 0: self.manage_delObjects(ids=my_ids) security.declareProtected( Permissions.AccessContentsInformation, 'delCells' ) delCells = delMatrix security.declareProtected( Permissions.AccessContentsInformation, '_checkConsistency' ) def _checkConsistency(self, fixit=0): """ Constraint API. """ # Check useless cells to_delete_set = set() error_list = [] def addError(error_message): if fixit: error_message += ' (fixed)' error = (self.getRelativeUrl(), 'XMLMatrix inconsistency', 102, error_message) error_list.append(error) # We make sure first that there is an index if getattr(aq_base(self), 'index', None) is None: self.index = PersistentMapping() # We will check each cell of the matrix the matrix # XXX This code assumes the following predicate: # each subobject of an XMLMatrix is either a Cell that needs # consistency checking OR ( is not a Cell, and has an id that is # not like "(\w+_)+(\d+_)*\d+" ) # But Documents inheriting XMLMatrix can have unrelated, non-cell # subobjects, possibly with id looking like some_id_2. If it ever happens, # an error will be wrongly raised. for obj in self.objectValues(): object_id = obj.getId() # obect_id is equal to something like 'something_quantity_3_2' # So we need to check for every object.id if the value # looks like good or not. We split the name # check each key in index # First we make sure this is a cell object_id_split = object_id.split('_') base_id = None cell_coordinate_list = [] while object_id_split: coordinate = None try: coordinate = int(object_id_split[-1]) except ValueError: # The last item is not a coordinate, object_id_split hence # only contains the base_id elements base_id = '_'.join(object_id_split) break else: cell_coordinate_list.insert(0, coordinate) # the last item is a coordinate not part of base_id object_id_split.pop() current_dimension = len(cell_coordinate_list) if current_dimension > 0 and base_id is not None: if not self.index.has_key(base_id): # The matrix does not have this base_id addError("There is no index for base_id %s" % base_id) to_delete_set.add(object_id) continue # Check empty indices. empty_list = [] base_item = self.index[base_id] for key, value in base_item.iteritems(): if value is None or len(value) == 0: addError("There is no id for the %dth axis of base_id %s" % (key, base_id)) empty_list.append(key) if fixit: for i in empty_list: del base_item[key] len_id = len(base_item) if current_dimension != len_id: addError("Dimension of cell is %s but should be %s" % (current_dimension, len_id)) to_delete_set.add(object_id) else : for i, coordinate in enumerate(cell_coordinate_list): if coordinate >= len(base_item[i]): addError("Cell %s is out of bound" % object_id) to_delete_set.add(object_id) break if fixit and len(to_delete_set) > 0: self.manage_delObjects(list(to_delete_set)) return error_list security.declareProtected( Permissions.ModifyPortalContent, 'notifyAfterUpdateRelatedContent' ) def notifyAfterUpdateRelatedContent(self, previous_category_url, new_category_url): """ We must do some matrix range update in the event matrix range is defined by a category """ LOG('XMLMatrix notifyAfterUpdateRelatedContent', 0, str(new_category_url)) update_method = self.portal_categories.updateRelatedCategory for base_id in self.getMatrixList(): cell_range = self.getCellRange(base_id=base_id) new_cell_range = [] for range_item_list in cell_range: new_range_item_list = map(lambda c: update_method(c, previous_category_url, new_category_url), range_item_list) new_cell_range.append(new_range_item_list) kwd = {'base_id': base_id} LOG('XMLMatrix notifyAfterUpdateRelatedContent matrix', 0, str(base_id)) LOG('XMLMatrix notifyAfterUpdateRelatedContent _renameCellRange', 0, str(new_cell_range)) self._renameCellRange(*new_cell_range,**kwd)
class IdTool(BaseTool): """ This tools handles the generation of IDs. """ zope.interface.implements(interfaces.IIdTool) id = 'portal_ids' meta_type = 'ERP5 Id Tool' portal_type = 'Id Tool' # Declarative Security security = ClassSecurityInfo() security.declareProtected( Permissions.ManagePortal, 'manage_overview' ) manage_overview = DTMLFile( 'explainIdTool', _dtmldir ) def newContent(self, *args, **kw): """ the newContent is overriden to not use generateNewId """ if not kw.has_key(id): new_id = self._generateNextId() if new_id is not None: kw['id'] = new_id else: raise ValueError('Failed to gererate id') return BaseTool.newContent(self, *args, **kw) def _get_id(self, id): """ _get_id is overrided to not use generateNewId It is used for example when an object is cloned """ if self._getOb(id, None) is None : return id return self._generateNextId() @caching_instance_method(id='IdTool._getLatestIdGenerator', cache_factory='erp5_content_long') def _getLatestIdGenerator(self, reference): """ Tries to find the id_generator with the latest version from the current object. Use the low-level to create a site without catalog """ assert reference id_tool = self.getPortalObject().portal_ids id_last_generator = None version_last_generator = 0 for generator in id_tool.objectValues(): if generator.getReference() == reference: version = generator.getVersion() if version > version_last_generator: id_last_generator = generator.getId() version_last_generator = generator.getVersion() if id_last_generator is None: raise KeyError, 'The generator %s is not present' % (reference,) return id_last_generator def _getLatestGeneratorValue(self, id_generator): """ Return the last generator with the reference """ id_tool = self.getPortalObject().portal_ids last_id_generator = self._getLatestIdGenerator(id_generator) last_generator = id_tool._getOb(last_id_generator) return last_generator security.declareProtected(Permissions.AccessContentsInformation, 'generateNewId') def generateNewId(self, id_group=None, default=None, method=_marker, id_generator=None): """ Generate the next id in the sequence of ids of a particular group """ if id_group in (None, 'None'): raise ValueError, '%s is not a valid id_group' % (repr(id_group), ) # for compatibilty with sql data, must not use id_group as a list if not isinstance(id_group, str): id_group = repr(id_group) warnings.warn('id_group must be a string, other types ' 'are deprecated.', DeprecationWarning) if id_generator is None: id_generator = 'document' if method is not _marker: warnings.warn("Use of 'method' argument is deprecated", DeprecationWarning) try: #use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) new_id = last_generator.generateNewId(id_group=id_group, \ default=default) except KeyError: template_tool = getattr(self, 'portal_templates', None) revision = template_tool.getInstalledBusinessTemplateRevision('erp5_core') # XXX backward compatiblity if int(revision) > 1561: LOG('generateNewId', ERROR, 'while generating id') raise else: # Compatibility code below, in case the last version of erp5_core # is not installed yet warnings.warn("You are using an old version of erp5_core to generate" "ids.\nPlease update erp5_core business template to " "use new id generators", DeprecationWarning) dict_ids = getattr(aq_base(self), 'dict_ids', None) if dict_ids is None: dict_ids = self.dict_ids = PersistentMapping() new_id = None # Getting the last id if default is None: default = 0 marker = [] new_id = dict_ids.get(id_group, marker) if method is _marker: if new_id is marker: new_id = default else: new_id = new_id + 1 else: if new_id is marker: new_id = default new_id = method(new_id) # Store the new value dict_ids[id_group] = new_id return new_id security.declareProtected(Permissions.AccessContentsInformation, 'generateNewIdList') def generateNewIdList(self, id_group=None, id_count=1, default=None, store=_marker, id_generator=None): """ Generate a list of next ids in the sequence of ids of a particular group """ if id_group in (None, 'None'): raise ValueError, '%s is not a valid id_group' % (repr(id_group), ) # for compatibilty with sql data, must not use id_group as a list if not isinstance(id_group, str): id_group = repr(id_group) warnings.warn('id_group must be a string, other types ' 'are deprecated.', DeprecationWarning) if id_generator is None: id_generator = 'uid' if store is not _marker: warnings.warn("Use of 'store' argument is deprecated.", DeprecationWarning) try: #use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) new_id_list = last_generator.generateNewIdList(id_group=id_group, id_count=id_count, default=default) except (KeyError, ValueError): template_tool = getattr(self, 'portal_templates', None) revision = template_tool.getInstalledBusinessTemplateRevision('erp5_core') # XXX backward compatiblity if int(revision) > 1561: LOG('generateNewIdList', ERROR, 'while generating id') raise else: # Compatibility code below, in case the last version of erp5_core # is not installed yet warnings.warn("You are using an old version of erp5_core to generate" "ids.\nPlease update erp5_core business template to " "use new id generators", DeprecationWarning) new_id = None if default is None: default = 1 # XXX It's temporary, a New API will be implemented soon # the code will be change portal = self.getPortalObject() query = getattr(portal, 'IdTool_zGenerateId', None) commit = getattr(portal, 'IdTool_zCommit', None) if query is None or commit is None: portal_catalog = getattr(self, 'portal_catalog').getSQLCatalog() query = getattr(portal_catalog, 'z_portal_ids_generate_id') commit = getattr(portal_catalog, 'z_portal_ids_commit') if None in (query, commit): raise AttributeError, 'Error while generating Id: ' \ 'idTool_zGenerateId and/or idTool_zCommit could not ' \ 'be found.' try: result = query(id_group=id_group, id_count=id_count, default=default) finally: commit() new_id = result[0]['LAST_INSERT_ID()'] if store: if getattr(aq_base(self), 'dict_length_ids', None) is None: # Length objects are stored in a persistent mapping: there is one # Length object per id_group. self.dict_length_ids = PersistentMapping() if self.dict_length_ids.get(id_group) is None: self.dict_length_ids[id_group] = Length(new_id) self.dict_length_ids[id_group].set(new_id) new_id_list = range(new_id - id_count, new_id) return new_id_list security.declareProtected(Permissions.ModifyPortalContent, 'initializeGenerator') def initializeGenerator(self, id_generator=None, all=False): """ Initialize generators. This is mostly used when a new ERP5 site is created. Some generators will need to do some initialization like creating SQL Database, prepare some data in ZODB, etc """ if not all: #Use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) last_generator.initializeGenerator() else: # recovery all the generators and initialize them for generator in self.objectValues(\ portal_type='Application Id Generator'): generator.initializeGenerator() security.declareProtected(Permissions.ModifyPortalContent, 'clearGenerator') def clearGenerator(self, id_generator=None, all=False): """ Clear generators data. This can be usefull when working on a development instance or in some other rare cases. This will loose data and must be use with caution This can be incompatible with some particular generator implementation, in this case a particular error will be raised (to be determined and added here) """ if not all: #Use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) last_generator.clearGenerator() else: if len(self.objectValues()) == 0: # compatibility with old API self.getPortalObject().IdTool_zDropTable() self.getPortalObject().IdTool_zCreateTable() for generator in self.objectValues(\ portal_type='Application Id Generator'): generator.clearGenerator() ## XXX Old API deprecated #backward compatibility generateNewLengthIdList = generateNewIdList security.declareProtected(Permissions.AccessContentsInformation, 'getLastLengthGeneratedId') def getLastLengthGeneratedId(self, id_group, default=None): """ Get the last length id generated """ warnings.warn('getLastLengthGeneratedId is deprecated', DeprecationWarning) # check in persistent mapping if exists if getattr(aq_base(self), 'dict_length_ids', None) is not None: last_id = self.dict_length_ids.get(id_group) if last_id is not None: return last_id.value - 1 # otherwise check in mysql # XXX It's temporary, a New API will be implemented soon # the code will be change portal = self.getPortalObject() query = getattr(portal, 'IdTool_zGetLastId', None) if query is None: portal_catalog = getattr(self, 'portal_catalog').getSQLCatalog() query = getattr(portal_catalog, 'z_portal_ids_get_last_id') if query is None: raise AttributeError, 'Error while getting last Id: ' \ 'IdTool_zGetLastId could not ' \ 'be found.' result = query(id_group=id_group) if len(result): try: return result[0]['last_id'] except KeyError: return result[0]['LAST_INSERT_ID()'] return default security.declareProtected(Permissions.AccessContentsInformation, 'getLastGeneratedId') def getLastGeneratedId(self, id_group=None, default=None): """ Get the last id generated """ warnings.warn('getLastGeneratedId is deprecated', DeprecationWarning) if getattr(aq_base(self), 'dict_ids', None) is None: self.dict_ids = PersistentMapping() last_id = None if id_group is not None and id_group != 'None': last_id = self.dict_ids.get(id_group, default) return last_id security.declareProtected(Permissions.ModifyPortalContent, 'setLastGeneratedId') def setLastGeneratedId(self, new_id, id_group=None): """ Set a new last id. This is usefull in order to reset a sequence of ids. """ if getattr(aq_base(self), 'dict_ids', None) is None: self.dict_ids = PersistentMapping() if id_group is not None and id_group != 'None': self.dict_ids[id_group] = new_id security.declareProtected(Permissions.AccessContentsInformation, 'generateNewLengthId') def generateNewLengthId(self, id_group=None, default=None, store=_marker): """Generates an Id using a conflict free id generator. Deprecated. """ warnings.warn('generateNewLengthId is deprecated.\n' 'Use generateNewIdList with a sql id_generator', DeprecationWarning) if store is not _marker: return self.generateNewIdList(id_group=id_group, id_count=1, default=default, store=store)[0] return self.generateNewIdList(id_group=id_group, id_count=1, default=default)[0] security.declareProtected(Permissions.AccessContentsInformation, 'getDictLengthIdsItems') def getDictLengthIdsItems(self): """ Return a copy of dict_length_ids. This is a workaround to access the persistent mapping content from ZSQL method to be able to insert initial tuples in the database at creation. """ if getattr(self, 'dict_length_ids', None) is None: self.dict_length_ids = PersistentMapping() return self.dict_length_ids.items() security.declarePrivate('dumpDictLengthIdsItems') def dumpDictLengthIdsItems(self): """ Store persistently data from SQL table portal_ids. """ portal_catalog = getattr(self, 'portal_catalog').getSQLCatalog() query = getattr(portal_catalog, 'z_portal_ids_dump') dict_length_ids = getattr(aq_base(self), 'dict_length_ids', None) if dict_length_ids is None: dict_length_ids = self.dict_length_ids = PersistentMapping() for line in query().dictionaries(): id_group = line['id_group'] last_id = line['last_id'] stored_last_id = self.dict_length_ids.get(id_group) if stored_last_id is None: self.dict_length_ids[id_group] = Length(last_id) else: stored_last_id_value = stored_last_id() if stored_last_id_value < last_id: stored_last_id.set(last_id) else: if stored_last_id_value > last_id: LOG('IdTool', WARNING, 'ZODB value (%r) for group %r is higher ' \ 'than SQL value (%r). Keeping ZODB value untouched.' % \ (stored_last_id, id_group, last_id))
class PDFForm(File): """This class allows to fill PDF Form with TALES expressions, using a TALES expression for each cell. TODO: * cache compiled TALES * set _v_errors when setting invalid TALES (setCellTALES can raise, but not doEditCells) """ meta_type = "ERP5 PDF Form" icon = "www/PDFForm.png" # Those 2 are ugly names, but we keep compatibility # the page range we want to print (a TALES expr) __page_range__ = '' # the method to format values (a TALES expr) __format_method__ = '' # Declarative Security security = ClassSecurityInfo() # Declarative properties _properties = File._properties + ( {'id' : 'download_url', 'type' : 'lines', 'mode' : 'w' }, {'id' : 'business_template_include_content', 'type' : 'boolean', 'mode' : 'w' }, ) download_url = () business_template_include_content = 1 # Constructors constructors = (manage_addPDFForm, addPDFForm) manage_options = ( ( {'label':'Edit Cell TALES', 'action':'manage_cells'}, {'label':'Display Cell Names', 'action':'showCellNames'}, {'label':'Test PDF generation', 'action':'generatePDF'}, {'label':'View original', 'action':'viewOriginal'}, {'label':'Download PDF content from URL', 'action':'downloadPdfContent'}, ) + filter(lambda option:option['label'] != "View", File.manage_options) ) def __init__ (self, id, title='', pdf_file=''): # holds all the cell informations, even those not related to this form self.all_cells = PersistentMapping() # holds the cells related to this pdf form self.cells = PersistentMapping() # File constructor will set the file content File.__init__(self, id, title, pdf_file) security.declareProtected(Permissions.ManagePortal, 'manage_upload') def manage_upload(self, file=None, REQUEST=None) : """ Zope calls this when the content of the enclosed file changes. The 'cells' attribute is updated, but already defined cells are not erased, they are saved in the 'all_cells' attribute so if the pdf file is reverted, you do not loose the cells definitions. """ if not file or not hasattr(file, "read") : raise ValueError ("The pdf form file should not be empty") file.seek(0) # file is always valid here values = PDFTk().dumpDataFields(file) self.cells = {} for v in values : if v["FieldType"] not in ("Button", "Choice")\ or not int(v["FieldFlags"]) & 65536: k = v["FieldName"] if not self.all_cells.has_key(k) : self.cells[k] = "" else : self.cells[k] = self.all_cells[k] self.all_cells.update(self.cells) file.seek(0) File.manage_upload(self, file, REQUEST) if REQUEST: message = "Saved changes." return self.manage_main(self, REQUEST, manage_tabs_message=message) security.declareProtected(Permissions.ViewManagementScreens, 'manage_cells') manage_cells = PageTemplateFile('www/PDFForm_manageCells', globals(), __name__='manage_cells') security.declareProtected(Permissions.View, 'manage_FTPget') def manage_FTPget(self, REQUEST=None, RESPONSE=None) : """ get this pdf form via webDAV/FTP, it returns an XML representation of all the fields, then the pdf itself.""" from xml.dom.minidom import getDOMImplementation impl = getDOMImplementation() newdoc = impl.createDocument(None, "pdfform", None) top_element = newdoc.documentElement cells = newdoc.createElement('cells') pdfform_cell_list = self.cells.keys() pdfform_cell_list.sort() for cell in pdfform_cell_list : cell_node = newdoc.createElement('cell') cell_node.setAttribute('name', cell) tales = newdoc.createTextNode(self.cells[cell]) cell_node.appendChild(tales) cells.appendChild(cell_node) top_element.appendChild(cells) pdf_data = newdoc.createElement('pdf_data') pdf_content = newdoc.createTextNode(str(self.data)) pdf_data.appendChild(pdf_content) top_element.appendChild(pdf_data) content = newdoc.toprettyxml(' ') if RESPONSE : RESPONSE.setHeader('Content-Type', 'application/x-erp5-pdfform') RESPONSE.setHeader('Content-Length', len(content)) RESPONSE.write(content) return content manage_DAVget = manage_FTPget security.declareProtected(Permissions.ManagePortal, 'PUT') def PUT(self, REQUEST, RESPONSE): """(does not) Handle HTTP PUT requests.""" RESPONSE.setStatus(501) return RESPONSE manage_FTPput = PUT security.declareProtected(Permissions.View, 'hasPdfContent') def hasPdfContent(self) : """Return true if there is an enclosed PDF in this PDF Form.""" return self.data is not None and len(self.data) > 0 security.declareProtected(Permissions.ManagePortal, 'downloadPdfContent') def downloadPdfContent(self, REQUEST=None) : """Download the pdf content from one of `download_url` URL """ for url in self.getProperty('download_url') : try : response = urllib.urlopen(url) except IOError, e : LOG("PDFForm", WARNING, "Unable to download from %s" % url, e) continue if response.headers.getheader('Content-Type') != 'application/pdf': LOG("PDFForm", WARNING, "%s is not application/pdf" % url) continue self.manage_upload(cStringIO.StringIO(response.read())) self.content_type = 'application/pdf' if REQUEST is not None : return REQUEST.RESPONSE.redirect( "%s/manage_main?manage_tabs_message=Content+Downloaded" % self.absolute_url()) return raise ValueError, "Unable to download from any url from the "\ "`download_url` property."
class XMLMatrix(Folder): """ A mix-in class which provides a matrix like access to objects. Matrices are of any dimension. A single XMLMatrix may contain multiple matrices, of different dimension. Each matrix is associated to a so-called 'base_id'. We still must make XMLMatrix a subclass of Base so that we can inherit from ExtensionClass.Base which is required for multiple inheritance to work as expected. Read this for more information: http://www.daa.com.au/pipermail/pygtk/2001-May/001180.html In our case, we will use Folder because we want to inherit from Folder consistency checking """ # Declarative security security = ClassSecurityInfo() # Matrix Methods security.declareProtected( Permissions.AccessContentsInformation, 'getCell' ) def getCell(self, *kw , **kwd): """ Access a cell at row and column """ if getattr(aq_base(self), 'index', None) is None: return None base_id = kwd.get('base_id', "cell") if not self.index.has_key(base_id): return None cell_id = self.keyToId(kw, base_id = base_id) if cell_id is None: return None return self.get(cell_id) security.declareProtected( Permissions.AccessContentsInformation, 'getCellProperty' ) def getCellProperty(self, *kw , **kwd): """ Get a property of a cell at row and column """ base_id= kwd.get('base_id', "cell") cell = self.getCell(*kw, **kwd) if cell is None: return None return cell.getProperty(base_id) security.declareProtected( Permissions.AccessContentsInformation, 'hasCell' ) def hasCell(self, *kw , **kwd): """ Checks if matrix corresponding to base_id contains cell specified by *kw coordinates. """ return self.getCell(*kw, **kwd) is not None security.declareProtected( Permissions.AccessContentsInformation, 'hasCellContent' ) def hasCellContent(self, base_id='cell'): """ Checks if matrix corresponding to base_id contains cells. """ aq_self = aq_base(self) if getattr(aq_self, 'index', None) is None: return 0 if not self.index.has_key(base_id): return 0 for i in self.getCellIds(base_id=base_id): if hasattr(self, i): # We should try to use aq_self if possible but XXX return 1 return 0 security.declareProtected( Permissions.AccessContentsInformation, 'hasInRange' ) def hasInRange(self, *kw , **kwd): """ Checks if *kw coordinates are in the range of the matrix in kwd['base_id']. """ if getattr(aq_base(self), 'index', None) is None: return 0 base_id = kwd.get('base_id', "cell") if not self.index.has_key(base_id): return 0 base_item = self.index[base_id] for i, my_id in enumerate(kw): if not base_item.has_key(i) or not base_item[i].has_key(my_id): return 0 return 1 security.declareProtected( Permissions.ModifyPortalContent, '_setCellRange' ) def _setCellRange(self, *args, **kw): """Set a new range for a matrix Each value for each axis is assigned an integer id. If the number of axis changes, everything is reset. Otherwise, ids are never changed, so that cells never need to be renamed: this means no sort is garanteed, and there can be holes. """ base_id = kw.get('base_id', 'cell') # Get (initialize if necessary) index for considered matrix (base_id). try: index = aq_base(self).index except AttributeError: index = self.index = PersistentMapping() to_delete = [] try: index = index[base_id] if len(args) != len(index): # The number of axis changes so we'll delete all existing cells and # renumber everything from 1. to_delete = INFINITE_SET, index.clear() except KeyError: index[base_id] = index = PersistentMapping() # For each axis ... for i, axis in enumerate(args): # ... collect old axis keys and allocate ids for new ones. axis = set(axis) last_id = -1 try: id_dict = index[i] except KeyError: index[i] = id_dict = PersistentMapping() else: delete = set() to_delete.append(delete) for k, v in id_dict.items(): try: axis.remove(k) if last_id < v: last_id = v except KeyError: delete.add(v) del id_dict[k] # At this point, last_id contains the greatest id. for k in sorted(axis): last_id += 1 id_dict[k] = last_id # Remove old cells if any. if any(to_delete): prefix = base_id + '_' prefix_len = len(prefix) for cell_id in list(self.objectIds()): if cell_id.startswith(prefix): for i, j in enumerate(cell_id[prefix_len:].split('_')): if int(j) in to_delete[i]: self._delObject(cell_id) break security.declareProtected( Permissions.ModifyPortalContent, 'setCellRange' ) def setCellRange(self, *kw, **kwd): """ Update the matrix ranges using provided lists of indexes (kw). Any number of list can be provided """ self._setCellRange(*kw, **kwd) self.reindexObject() security.declareProtected(Permissions.ModifyPortalContent, '_updateCellRange') def _updateCellRange(self, base_id, **kw): """ The asCellRange script is Portal Type dependent which is not the case with this kind of code a better implementation consists in defining asCellRange as a generic method at matrix level (OverridableMethod(portal_type)) which lookups for script in class, meta_type and PT form interaction could be implemented with interaction workflow this method should be renamed updateCellRange or updateMatrixCellRange base_id is parameter of updateCellRange asCellRange scripts should be unified if possible """ script = self._getTypeBasedMethod('asCellRange', **kw) if script is None: raise UnboundLocalError,\ "Did not find cell range script for portal type: %r" %\ self.getPortalType() cell_range = script(base_id=base_id, matrixbox=0, **kw) self._setCellRange(base_id=base_id, *cell_range) security.declareProtected(Permissions.ModifyPortalContent, 'updateCellRange') def updateCellRange(self, base_id='cell', **kw): """ same as _updateCellRange, but reindex the object. """ self._updateCellRange(base_id=base_id, **kw) self.reindexObject() security.declareProtected( Permissions.ModifyPortalContent, '_renameCellRange' ) def _renameCellRange(self, *kw, **kwd): """ Rename a range for a matrix, this method can also handle a changement of the size of a matrix """ base_id = kwd.get('base_id', 'cell') if getattr(aq_base(self), 'index', None) is None: self.index = PersistentMapping() # Return if previous range is the same current_range = self.getCellRange(base_id=base_id) or [] if current_range == list(kw): # kw is a tuple LOG('XMLMatrix',0,'return form _setCellRange - no need to change range') return current_len = len(current_range) new_len = len(kw) len_delta = new_len - current_len # We must make sure the base_id exists # in the event of a matrix creation for example if not self.index.has_key(base_id): # Create an index for this base_id self.index[base_id] = PersistentMapping() cell_id_list = [] for cell_id in self.getCellIdList(base_id = base_id): if self.get(cell_id) is not None: cell_id_list.append(cell_id) # First, delete all cells which are out of range. size_list = map(len, kw) if len_delta < 0: size_list.extend([1] * (-len_delta)) def is_in_range(cell_id): for i, index in enumerate(cell_id[len(base_id)+1:].split('_')): if int(index) >= size_list[i]: self._delObject(cell_id) return False return True cell_id_list = filter(is_in_range, cell_id_list) # Secondly, rename coordinates. This does not change cell ids. for i in range(max(new_len, current_len)): if i >= new_len: del self.index[base_id][i] else: if i >= current_len: self.index[base_id][i] = PersistentMapping() for place in self.index[base_id][i].keys(): if place not in kw[i]: del self.index[base_id][i][place] for j, place in enumerate(kw[i]): self.index[base_id][i][place] = j # Lastly, rename ids and catalog/uncatalog everything. if len_delta > 0: # Need to move, say, base_1_2 -> base_1_2_0 appended_id = '_0' * len_delta for old_id in cell_id_list: cell = self.get(old_id) if cell is not None: new_id = old_id + appended_id self._delObject(old_id) cell.isIndexable = ConstantGetter('isIndexable', value=False) cell.id = new_id self._setObject(new_id, aq_base(cell)) cell.isIndexable = ConstantGetter('isIndexable', value=True) cell.reindexObject() #cell.unindexObject(path='%s/%s' % (self.getUrl(), old_id)) elif len_delta < 0: # Need to move, say, base_1_2_0 -> base_1_2 removed_id_len = 2 * (-len_delta) for old_id in cell_id_list: cell = self.get(old_id) if cell is not None: new_id = old_id[:-removed_id_len] self._delObject(old_id) cell.isIndexable = ConstantGetter('isIndexable', value=False) cell.id = new_id self._setObject(new_id, aq_base(cell)) cell.isIndexable = ConstantGetter('isIndexable', value=True) cell.reindexObject() #cell.unindexObject(path='%s/%s' % (self.getUrl(), old_id)) security.declareProtected( Permissions.ModifyPortalContent, 'renameCellRange' ) def renameCellRange(self, *kw, **kwd): """ Update the matrix ranges using provided lists of indexes (kw). This keep cell values if we add/remove dimensions Any number of list can be provided """ self._renameCellRange(*kw, **kwd) self.reindexObject() security.declareProtected(Permissions.AccessContentsInformation, 'getCellRange') def getCellRange(self, base_id='cell'): """ Returns the cell range as a list of index ids """ try: cell_range = aq_base(self).index[base_id] except (AttributeError, KeyError): return [] return [x.keys() for _, x in sorted(cell_range.iteritems())] security.declareProtected( Permissions.ModifyPortalContent, 'newCell' ) def newCell(self, *kw, **kwd): """ This method creates a new cell """ if getattr(aq_base(self), 'index', None) is None: return None base_id = kwd.get('base_id', "cell") if not self.index.has_key(base_id): return None cell_id = self.keyToId(kw, base_id = base_id) if cell_id is None: raise KeyError, 'Invalid key: %s' % str(kw) cell = self.get(cell_id) if cell is not None: return cell else: return self.newCellContent(cell_id,**kwd) security.declareProtected( Permissions.ModifyPortalContent, 'newCellContent' ) def newCellContent(self, cell_id, portal_type=None, **kw): """ Creates a new content as a cell. This method is meant to be overriden by subclasses. """ if portal_type is None: for x in self.allowedContentTypes(): portal_type_id = x.getId() if portal_type_id.endswith(' Cell'): portal_type = portal_type_id break return self.newContent(id=cell_id, portal_type=portal_type, **kw) security.declareProtected( Permissions.AccessContentsInformation, 'getCellKeyList' ) def getCellKeyList(self, base_id = 'cell'): """ Returns a list of possible keys as tuples """ if getattr(aq_base(self), 'index', None) is None: return () if not self.index.has_key(base_id): return () index = self.index[base_id] id_tuple = [v.keys() for v in index.itervalues()] if len(id_tuple) == 0: return () return cartesianProduct(id_tuple) security.declareProtected( Permissions.AccessContentsInformation, 'getCellKeys' ) getCellKeys = getCellKeyList # We should differenciate in future existing tuples from possible tuples security.declareProtected( Permissions.AccessContentsInformation, 'getCellRangeKeyList' ) getCellRangeKeyList = getCellKeyList security.declareProtected( Permissions.AccessContentsInformation, 'keyToId' ) def keyToId(self, kw, base_id = 'cell'): """ Converts a key into a cell id """ index = self.index[base_id] cell_id_list = [base_id] append = cell_id_list.append for i, item in enumerate(kw): try: append(str(index[i][item])) except KeyError: return None return '_'.join(cell_id_list) security.declareProtected( Permissions.AccessContentsInformation, 'getCellIdList' ) def getCellIdList(self, base_id = 'cell'): """ Returns a list of possible ids as tuples """ if getattr(aq_base(self), 'index', None) is None: return () if not self.index.has_key(base_id): return () result = [] append = result.append for kw in self.getCellKeys(base_id = base_id): cell_id = self.keyToId(kw, base_id = base_id ) if cell_id is not None: append(cell_id) return result security.declareProtected( Permissions.AccessContentsInformation, 'getCellIds' ) getCellIds = getCellIdList security.declareProtected( Permissions.AccessContentsInformation, 'cellIds' ) cellIds = getCellIdList # We should differenciate in future all possible ids for existing ids security.declareProtected( Permissions.AccessContentsInformation, 'getCellRangeIdList' ) getCellRangeIdList = getCellIdList security.declareProtected( Permissions.AccessContentsInformation, 'getCellValueList' ) def getCellValueList(self, base_id = 'cell'): """ Returns a list of cell values as tuples """ result = [] append = result.append for id in self.getCellIdList(base_id=base_id): o = self.get(id) if o is not None: append(o) return result security.declareProtected( Permissions.AccessContentsInformation, 'cellValues' ) cellValues = getCellValueList security.declareProtected( Permissions.AccessContentsInformation, 'getMatrixList' ) def getMatrixList(self): """ Return possible base_id values """ if getattr(aq_base(self), 'index', None) is None: return () return self.index.keys() security.declareProtected( Permissions.ModifyPortalContent, 'delMatrix' ) def delMatrix(self, base_id = 'cell'): """ Delete all cells for a given base_id XXX BAD NAME: make a difference between deleting matrix and matrix cells """ ids = self.getCellIds(base_id = base_id) my_ids = [] append = my_ids.append for i in self.objectIds(): if i in ids: append(i) if len(my_ids) > 0: self.manage_delObjects(ids=my_ids) security.declareProtected( Permissions.AccessContentsInformation, 'delCells' ) delCells = delMatrix security.declareProtected( Permissions.AccessContentsInformation, '_checkConsistency' ) def _checkConsistency(self, fixit=0): """ Constraint API. """ # Check useless cells to_delete_set = set() error_list = [] def addError(error_message): if fixit: error_message += ' (fixed)' error = (self.getRelativeUrl(), 'XMLMatrix inconsistency', 102, error_message) error_list.append(error) # We make sure first that there is an index if getattr(aq_base(self), 'index', None) is None: self.index = PersistentMapping() # We will check each cell of the matrix the matrix # XXX This code assumes the following predicate: # each subobject of an XMLMatrix is either a Cell that needs # consistency checking OR ( is not a Cell, and has an id that is # not like "(\w+_)+(\d+_)*\d+" ) # But Documents inheriting XMLMatrix can have unrelated, non-cell # subobjects, possibly with id looking like some_id_2. If it ever happens, # an error will be wrongly raised. for obj in self.objectValues(): object_id = obj.getId() # obect_id is equal to something like 'something_quantity_3_2' # So we need to check for every object.id if the value # looks like good or not. We split the name # check each key in index # First we make sure this is a cell object_id_split = object_id.split('_') base_id = None cell_coordinate_list = [] while object_id_split: coordinate = None try: coordinate = int(object_id_split[-1]) except ValueError: # The last item is not a coordinate, object_id_split hence # only contains the base_id elements base_id = '_'.join(object_id_split) break else: cell_coordinate_list.insert(0, coordinate) # the last item is a coordinate not part of base_id object_id_split.pop() current_dimension = len(cell_coordinate_list) if current_dimension > 0 and base_id is not None: if not self.index.has_key(base_id): # The matrix does not have this base_id addError("There is no index for base_id %s" % base_id) to_delete_set.add(object_id) continue # Check empty indices. empty_list = [] base_item = self.index[base_id] for key, value in base_item.iteritems(): if value is None or len(value) == 0: addError("There is no id for the %dth axis of base_id %s" % (key, base_id)) empty_list.append(key) if fixit: for i in empty_list: del base_item[key] len_id = len(base_item) if current_dimension != len_id: addError("Dimension of cell is %s but should be %s" % (current_dimension, len_id)) to_delete_set.add(object_id) else : for i, coordinate in enumerate(cell_coordinate_list): if coordinate >= len(base_item[i]): addError("Cell %s is out of bound" % object_id) to_delete_set.add(object_id) break if fixit and len(to_delete_set) > 0: self.manage_delObjects(list(to_delete_set)) return error_list security.declareProtected( Permissions.ModifyPortalContent, 'notifyAfterUpdateRelatedContent' ) def notifyAfterUpdateRelatedContent(self, previous_category_url, new_category_url): """ We must do some matrix range update in the event matrix range is defined by a category """ LOG('XMLMatrix notifyAfterUpdateRelatedContent', 0, str(new_category_url)) update_method = self.portal_categories.updateRelatedCategory for base_id in self.getMatrixList(): cell_range = self.getCellRange(base_id=base_id) new_cell_range = [] for range_item_list in cell_range: new_range_item_list = map(lambda c: update_method(c, previous_category_url, new_category_url), range_item_list) new_cell_range.append(new_range_item_list) kwd = {'base_id': base_id} LOG('XMLMatrix notifyAfterUpdateRelatedContent matrix', 0, str(base_id)) LOG('XMLMatrix notifyAfterUpdateRelatedContent _renameCellRange', 0, str(new_cell_range)) self._renameCellRange(*new_cell_range,**kwd)
def _setCellRange(self, *kw, **kwd): """ Set a new range for a matrix. If needed, it will resize and/or reorder matrix content. """ movement = {} # Maps original cell id to its new id for each moved cell. new_index = PersistentMapping() base_id = kwd.get('base_id', 'cell') if getattr(aq_base(self), 'index', None) is None: self.index = PersistentMapping() # Return if previous range is the same current_range = self.getCellRange(base_id=base_id) if current_range == list(kw): # kw is a tuple return # Create the new index for the range given in *kw # *kw practical example: # kw = [ ['color/blue', 'color/red'], ['size/S', 'size/L']] for i, index_ids in enumerate(kw): temp = PersistentMapping() for j, my_id in enumerate(index_ids): temp[my_id] = j new_index[i] = temp if self.index.has_key(base_id): # Compute cell movement from their position in previous range to their # position in the new range. for i, i_value in self.index[base_id].iteritems(): if new_index.has_key(i): temp = {} for my_id, my_value in i_value.iteritems(): temp[my_value] = new_index[i].get(my_id) movement[i] = temp # List all valid cell ids for current base_id. object_id_list = [] for obj in self.objectValues(): object_id = obj.getId() if object_id.find(base_id) == 0: # Check that all '_'-separated fields are of int type. if (object_id) > len(base_id): try: int(object_id[len(base_id)+1:].split('_')[0]) test = self._getOb(object_id) # If the object was created # during this transaction, # then we do not need to # work on it object_id_list.append(object_id) except (ValueError, KeyError): pass # Prepend 'temp_' to all cells, to avoid id conflicts while renaming. for object_id in object_id_list: new_name = 'temp_' + object_id obj = self._getOb(object_id) self._delObject(object_id) obj.id = new_name self._setObject(new_name, aq_base(obj)) # Rename all cells to their final name. for object_id in object_id_list: # Retrieve the place of the object, for movement_0_0 it is ['0','0'] object_place = object_id[len(base_id)+1:].split('_') to_delete = 1 # We must have the same number of dimensions if len(object_place) == len(new_index): # Let us browse each dimension of the previous index for i in range(len(object_place)): # Build each part of the nex id by looking up int. values old_place = int(object_place[i]) # We are looking inside the movement dictionnary where # we should move the object, so for example # 'qantity_2_5' is renamed as 'quantity_4_3' if movement.has_key(i): if movement[i].has_key(old_place): # Replace the place of the cell only if there where a change if (movement[i][old_place]) != None: object_place[i] = str(movement[i][old_place]) to_delete = 0 else: object_place[i] = None # XXX In this case, we delete every cell wich are not in the # movement dictionnary, may be this is very bad, so may be # we need to add an extra check here, ie if # if movement[i].has_key(old_place) returns None, # We may want to keep the cell, but only if we are sure # the movement dictionnary will not define a new cell with this id new_object_id_list = [] temp_object_id = 'temp_' + object_id o = self._getOb(temp_object_id) if not to_delete and not (None in object_place): self._delObject(temp_object_id) # In all cases, we have # to remove the temp object object_place.insert(0, base_id) new_name = '_'.join(object_place) o.id = new_name new_object_id_list.extend(new_name) self._setObject(new_name, aq_base(o)) if new_name != object_id: # Theses two lines are very important, if the object is renamed # then we must uncatalog the previous one o.unindexObject(path='%s/%s' % (self.getUrl() , object_id)) # Force a new uid to be allocated, because unindexObject creates # an activity which will later delete lines from catalog based # on their uid, and it is not garanted that indexation will happen # after this deletion. # It is bad to waste uids, but this data structure offers no # alternative because cell id gives its index in the matrix, # so reordering axes requires the cell id to change. # XXX: It can be improved, but requires most of this file to be # rewritten, and compatibility code must be written as data # structure would most probably change. o.uid = None o.reindexObject() # we reindex in case position has changed # uid should be consistent else: # In all cases, we have to remove the temp object #WARNING -> if path is not good, it will not be able to uncatalog !!! #o.immediateReindexObject() # STILL A PROBLEM -> getUidForPath XXX if object_id not in new_object_id_list: # do not unindex a new object o.isIndexable = ConstantGetter('isIndexable', value=True) o.unindexObject(path='%s/%s' % (self.getUrl() , object_id)) # unindexed already forced o.isIndexable = ConstantGetter('isIndexable', value=False) self._delObject(temp_object_id) # object will be removed # from catalog automaticaly # We don't need the old index any more, we # can set the new index self.index[base_id] = new_index
def generateNewIdList(self, id_group=None, id_count=1, default=None, store=_marker, id_generator=None): """ Generate a list of next ids in the sequence of ids of a particular group """ if id_group in (None, 'None'): raise ValueError, '%s is not a valid id_group' % (repr(id_group), ) # for compatibilty with sql data, must not use id_group as a list if not isinstance(id_group, str): id_group = repr(id_group) warnings.warn('id_group must be a string, other types ' 'are deprecated.', DeprecationWarning) if id_generator is None: id_generator = 'uid' if store is not _marker: warnings.warn("Use of 'store' argument is deprecated.", DeprecationWarning) try: #use _getLatestGeneratorValue here for that the technical level #must not call the method last_generator = self._getLatestGeneratorValue(id_generator) new_id_list = last_generator.generateNewIdList(id_group=id_group, id_count=id_count, default=default) except (KeyError, ValueError): template_tool = getattr(self, 'portal_templates', None) revision = template_tool.getInstalledBusinessTemplateRevision('erp5_core') # XXX backward compatiblity if int(revision) > 1561: LOG('generateNewIdList', ERROR, 'while generating id') raise else: # Compatibility code below, in case the last version of erp5_core # is not installed yet warnings.warn("You are using an old version of erp5_core to generate" "ids.\nPlease update erp5_core business template to " "use new id generators", DeprecationWarning) new_id = None if default is None: default = 1 # XXX It's temporary, a New API will be implemented soon # the code will be change portal = self.getPortalObject() query = getattr(portal, 'IdTool_zGenerateId', None) commit = getattr(portal, 'IdTool_zCommit', None) if query is None or commit is None: portal_catalog = getattr(self, 'portal_catalog').getSQLCatalog() query = getattr(portal_catalog, 'z_portal_ids_generate_id') commit = getattr(portal_catalog, 'z_portal_ids_commit') if None in (query, commit): raise AttributeError, 'Error while generating Id: ' \ 'idTool_zGenerateId and/or idTool_zCommit could not ' \ 'be found.' try: result = query(id_group=id_group, id_count=id_count, default=default) finally: commit() new_id = result[0]['LAST_INSERT_ID()'] if store: if getattr(aq_base(self), 'dict_length_ids', None) is None: # Length objects are stored in a persistent mapping: there is one # Length object per id_group. self.dict_length_ids = PersistentMapping() if self.dict_length_ids.get(id_group) is None: self.dict_length_ids[id_group] = Length(new_id) self.dict_length_ids[id_group].set(new_id) new_id_list = range(new_id - id_count, new_id) return new_id_list
def __init__(self, id=None): if id is None: id = self.__class__.id self._password_request_dict = PersistentMapping()
def test(self, context, tested_base_category_list=None, strict_membership=0, **kw): """ A Predicate can be tested on a given context. Parameters can passed in order to ignore some conditions. - tested_base_category_list: this is the list of category that we do want to test. For example, we might want to test only the destination or the source of a predicate. - if strict_membership is specified, we should make sure that we are strictly a member of tested categories """ self = self.asPredicate() result = 1 if getattr(aq_base(self), '_identity_criterion', None) is None: self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() # LOG('PREDICATE TEST', 0, # 'testing %s on context of %s' % \ # (self.getRelativeUrl(), context.getRelativeUrl())) for property, value in self._identity_criterion.iteritems(): if isinstance(value, (list, tuple)): result = context.getProperty(property) in value else: result = context.getProperty(property) == value # LOG('predicate test', 0, # '%s after prop %s : %s == %s' % \ # (result, property, context.getProperty(property), value)) if not result: return result for property, (min, max) in self._range_criterion.iteritems(): value = context.getProperty(property) if min is not None: result = value >= min # LOG('predicate test', 0, # '%s after prop %s : %s >= %s' % \ # (result, property, value, min)) if not result: return result if max is not None: result = value < max # LOG('predicate test', 0, # '%s after prop %s : %s < %s' % \ # (result, property, value, max)) if not result: return result multimembership_criterion_base_category_list = \ self.getMultimembershipCriterionBaseCategoryList() membership_criterion_base_category_list = \ self.getMembershipCriterionBaseCategoryList() tested_base_category = {} # LOG('predicate test', 0, # 'categories will be tested in multi %s single %s as %s' % \ # (multimembership_criterion_base_category_list, # membership_criterion_base_category_list, # self.getMembershipCriterionCategoryList())) membership_criterion_category_list = \ self.getMembershipCriterionCategoryList() if tested_base_category_list is not None: membership_criterion_category_list = [x for x in \ membership_criterion_category_list if x.split('/', 1)[0] in \ tested_base_category_list] # Test category memberships. Enable the read-only transaction cache # temporarily, if not enabled, because this part is strictly read-only, # and context.isMemberOf is very expensive, when the category list has # many items. enabled = getReadOnlyTransactionCache() is not None try: if not enabled: enableReadOnlyTransactionCache() for c in membership_criterion_category_list: bc = c.split('/', 1)[0] if (bc not in tested_base_category) and \ (bc in multimembership_criterion_base_category_list): tested_base_category[bc] = 1 elif (bc not in tested_base_category) and \ (bc in membership_criterion_base_category_list): tested_base_category[bc] = 0 if (bc in multimembership_criterion_base_category_list): tested_base_category[bc] = tested_base_category[bc] and \ context.isMemberOf(c, strict_membership=strict_membership) # LOG('predicate test', 0, # '%s after multi membership to %s' % \ # (tested_base_category[bc], c)) elif (bc in membership_criterion_base_category_list): tested_base_category[bc] = tested_base_category[bc] or \ context.isMemberOf(c, strict_membership=strict_membership) finally: if not enabled: disableReadOnlyTransactionCache() # LOG('predicate test', 0, # '%s after single membership to %s' % \ # (tested_base_category[bc], c)) result = 0 not in tested_base_category.values() # LOG('predicate test', 0, # '%s after category %s ' % (result, tested_base_category.items())) if not result: return result # Test method calls test_method_id_list = self.getTestMethodIdList() if test_method_id_list is not None : for test_method_id in test_method_id_list : if test_method_id is not None: method = getattr(context,test_method_id) try: result = method(self) except TypeError: if method.func_code.co_argcount != isinstance(method, MethodType): raise # backward compatibilty with script that takes no argument warn('Predicate %s uses an old-style method (%s) that does not' ' take the predicate as argument' % ( self.getRelativeUrl(), method.__name__), DeprecationWarning) result = method() # LOG('predicate test', 0, # '%s after method %s ' % (result, test_method_id)) if not result: return result test_tales_expression = self.getTestTalesExpression() if test_tales_expression != 'python: True': expression = Expression(test_tales_expression) from Products.ERP5Type.Utils import createExpressionContext # evaluate a tales expression with the tested value as context result = expression(createExpressionContext(context)) return result
def test(self, context, tested_base_category_list=None, strict_membership=0, isMemberOf=None, **kw): """ A Predicate can be tested on a given context. Parameters can passed in order to ignore some conditions. - tested_base_category_list: this is the list of category that we do want to test. For example, we might want to test only the destination or the source of a predicate. - if strict_membership is specified, we should make sure that we are strictly a member of tested categories - isMemberOf can be a function caching results for CategoryTool.isMemberOf: it is always called with given 'context' and 'strict_membership' values, and different categories. """ self = self.asPredicate() if self is None: # asPredicate returned None, so this predicate never applies. # But if we reach this it is because catalog is not up to date. return False result = 1 if getattr(aq_base(self), '_identity_criterion', None) is None: self._identity_criterion = PersistentMapping() self._range_criterion = PersistentMapping() # LOG('PREDICATE TEST', 0, # 'testing %s on context of %s' % \ # (self.getRelativeUrl(), context.getRelativeUrl())) for property, value in self._identity_criterion.iteritems(): if isinstance(value, (list, tuple)): result = context.getProperty(property) in value else: result = context.getProperty(property) == value # LOG('predicate test', 0, # '%s after prop %s : %s == %s' % \ # (result, property, context.getProperty(property), value)) if not result: return result for property, (min, max) in self._range_criterion.iteritems(): value = context.getProperty(property) if min is not None: result = value >= min # LOG('predicate test', 0, # '%s after prop %s : %s >= %s' % \ # (result, property, value, min)) if not result: return result if max is not None: result = value < max # LOG('predicate test', 0, # '%s after prop %s : %s < %s' % \ # (result, property, value, max)) if not result: return result multimembership_criterion_base_category_list = \ self.getMultimembershipCriterionBaseCategoryList() membership_criterion_base_category_list = \ self.getMembershipCriterionBaseCategoryList() tested_base_category = {} # LOG('predicate test', 0, # 'categories will be tested in multi %s single %s as %s' % \ # (multimembership_criterion_base_category_list, # membership_criterion_base_category_list, # self.getMembershipCriterionCategoryList())) # Test category memberships. Enable the read-only transaction cache # because this part is strictly read-only, and context.isMemberOf # is very expensive when the category list has many items. if isMemberOf is None: isMemberOf = context._getCategoryTool().isMemberOf with readOnlyTransactionCache(): for c in self.getMembershipCriterionCategoryList(): bc = c.split('/', 1)[0] if tested_base_category_list is None or bc in tested_base_category_list: if bc in multimembership_criterion_base_category_list: if not isMemberOf(context, c, strict_membership=strict_membership): return 0 elif bc in membership_criterion_base_category_list and \ not tested_base_category.get(bc): tested_base_category[bc] = \ isMemberOf(context, c, strict_membership=strict_membership) if 0 in tested_base_category.itervalues(): return 0 # Test method calls test_method_id_list = self.getTestMethodIdList() if test_method_id_list is not None : for test_method_id in test_method_id_list : if test_method_id is not None: method = getattr(context,test_method_id) try: result = method(self) except TypeError: if method.func_code.co_argcount != isinstance(method, MethodType): raise # backward compatibilty with script that takes no argument warn('Predicate %s uses an old-style method (%s) that does not' ' take the predicate as argument' % ( self.getRelativeUrl(), method.__name__), DeprecationWarning) result = method() # LOG('predicate test', 0, # '%s after method %s ' % (result, test_method_id)) if not result: return result test_tales_expression = self.getTestTalesExpression() if test_tales_expression != 'python: True': expression = Expression(test_tales_expression) from Products.ERP5Type.Utils import createExpressionContext # evaluate a tales expression with the tested value as context result = expression(createExpressionContext(context)) return result
def _renameCellRange(self, *kw, **kwd): """ Rename a range for a matrix, this method can also handle a changement of the size of a matrix """ base_id = kwd.get('base_id', 'cell') if getattr(aq_base(self), 'index', None) is None: self.index = PersistentMapping() # Return if previous range is the same current_range = self.getCellRange(base_id=base_id) or [] if current_range == list(kw): # kw is a tuple LOG('XMLMatrix',0,'return form _setCellRange - no need to change range') return current_len = len(current_range) new_len = len(kw) len_delta = new_len - current_len # We must make sure the base_id exists # in the event of a matrix creation for example if not self.index.has_key(base_id): # Create an index for this base_id self.index[base_id] = PersistentMapping() cell_id_list = [] for cell_id in self.getCellIdList(base_id = base_id): if self.get(cell_id) is not None: cell_id_list.append(cell_id) # First, delete all cells which are out of range. size_list = map(len, kw) if len_delta < 0: size_list.extend([1] * (-len_delta)) def is_in_range(cell_id): for i, index in enumerate(cell_id[len(base_id)+1:].split('_')): if int(index) >= size_list[i]: self._delObject(cell_id) return False return True cell_id_list = filter(is_in_range, cell_id_list) # Secondly, rename coordinates. This does not change cell ids. for i in range(max(new_len, current_len)): if i >= new_len: del self.index[base_id][i] else: if i >= current_len: self.index[base_id][i] = PersistentMapping() for place in self.index[base_id][i].keys(): if place not in kw[i]: del self.index[base_id][i][place] for j, place in enumerate(kw[i]): self.index[base_id][i][place] = j # Lastly, rename ids and catalog/uncatalog everything. if len_delta > 0: # Need to move, say, base_1_2 -> base_1_2_0 appended_id = '_0' * len_delta for old_id in cell_id_list: cell = self.get(old_id) if cell is not None: new_id = old_id + appended_id self._delObject(old_id) cell.isIndexable = ConstantGetter('isIndexable', value=False) cell.id = new_id self._setObject(new_id, aq_base(cell)) cell.isIndexable = ConstantGetter('isIndexable', value=True) cell.reindexObject() #cell.unindexObject(path='%s/%s' % (self.getUrl(), old_id)) elif len_delta < 0: # Need to move, say, base_1_2_0 -> base_1_2 removed_id_len = 2 * (-len_delta) for old_id in cell_id_list: cell = self.get(old_id) if cell is not None: new_id = old_id[:-removed_id_len] self._delObject(old_id) cell.isIndexable = ConstantGetter('isIndexable', value=False) cell.id = new_id self._setObject(new_id, aq_base(cell)) cell.isIndexable = ConstantGetter('isIndexable', value=True) cell.reindexObject()