From ef213754104871150814ce4560fa53551d820794 Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Tue, 22 Apr 2014 19:37:36 +0200 Subject: [PATCH] [gen] Bugfixes and use of Ref.autoref fto avoid circular references within cross-class Ref fields. --- fields/ref.py | 30 ++++++++++++++++++++++++++++++ fields/workflow.py | 40 +++++++++++++++++++--------------------- gen/mixins/ToolMixin.py | 1 - gen/wrappers/__init__.py | 19 +++++++++++-------- 4 files changed, 60 insertions(+), 30 deletions(-) diff --git a/fields/ref.py b/fields/ref.py index 797d883..5ef7e08 100644 --- a/fields/ref.py +++ b/fields/ref.py @@ -1129,6 +1129,36 @@ def autoref(klass, field): class A: attr1 = Ref(None) autoref(A, A.attr1) + + This function can also be used to avoid circular imports between 2 + classes from 2 different packages. Imagine class P1 in package p1 has a + Ref to class P2 in package p2; and class P2 has another Ref to p1.P1 + (which is not the back Ref of the previous one: it is another, + independent Ref). + + In p1, you have + + from p2 import P2 + class P1: + ref1 = Ref(P2) + + Then, if you write the following in p2, python will complain because of a + circular import: + + from p1 import P1 + class P2: + ref2 = Ref(P1) + + The solution is to write this. In p1: + + from p2 import P2 + class P1: + ref1 = Ref(P2) + autoref(P1, P2.ref2) + + And, in p2: + class P2: + ref2 = Ref(None) ''' field.klass = klass setattr(klass, field.back.attribute, field.back) diff --git a/fields/workflow.py b/fields/workflow.py index 7fd1a6d..f450eb8 100644 --- a/fields/workflow.py +++ b/fields/workflow.py @@ -332,23 +332,21 @@ class Transition: if msgPart: msg += msgPart return msg - def trigger(self, transitionName, obj, wf, comment, doAction=True, - doNotify=True, doHistory=True, doSay=True, reindex=True, - noSecurity=False): - '''This method triggers this transition on p_obj. The transition is - supposed to be triggerable (call to self.isTriggerable must have been - performed before calling this method). If p_doAction is False, the - action that must normally be executed after the transition has been - triggered will not be executed. If p_doNotify is False, the - email notifications that must normally be launched after the - transition has been triggered will not be launched. If p_doHistory is - False, there will be no trace from this transition triggering in the - workflow history. If p_doSay is False, we consider the transition is - trigger programmatically, and no message is returned to the user. - If p_reindex is False, object reindexing will be performed by the - calling method.''' - # Security check - if not noSecurity and not self.isTriggerable(obj, wf): + def trigger(self, name, obj, wf, comment, doAction=True, doNotify=True, + doHistory=True, doSay=True, reindex=True, noSecurity=False): + '''This method triggers this transition (named p_name) on p_obj. If + p_doAction is False, the action that must normally be executed after + the transition has been triggered will not be executed. If p_doNotify + is False, the email notifications that must normally be launched + after the transition has been triggered will not be launched. If + p_doHistory is False, there will be no trace from this transition + triggering in the workflow history. If p_doSay is False, we consider + the transition is triggered programmatically, and no message is + returned to the user. If p_reindex is False, object reindexing will + be performed by the calling method.''' + # "Triggerability" and security checks. + if (name != '_init_') and \ + not self.isTriggerable(obj, wf, noSecurity=noSecurity): raise Exception('Transition "%s" can\'t be triggered.' % name) # Create the workflow_history dict if it does not exist. if not hasattr(obj.aq_base, 'workflow_history'): @@ -371,8 +369,8 @@ class Transition: targetStateName = targetState.getName(wf) break # Create the event and add it in the object history - action = transitionName - if transitionName == '_init_': action = None + action = name + if name == '_init_': action = None if not doHistory: comment = '_invisible_' obj.addHistoryEvent(action, review_state=targetStateName, comments=comment) @@ -384,9 +382,9 @@ class Transition: if reindex and not obj.isTemporary(): obj.reindex() # Send notifications if needed if doNotify and self.notify and obj.getTool(True).mailEnabled: - sendNotification(obj.appy(), self, transitionName, wf) + sendNotification(obj.appy(), self, name, wf) # Return a message to the user if needed - if not doSay or (transitionName == '_init_'): return + if not doSay or (name == '_init_'): return if not msg: msg = obj.translate('object_saved') obj.say(msg) diff --git a/gen/mixins/ToolMixin.py b/gen/mixins/ToolMixin.py index aef6be8..7e04e2f 100644 --- a/gen/mixins/ToolMixin.py +++ b/gen/mixins/ToolMixin.py @@ -324,7 +324,6 @@ class ToolMixin(BaseMixin): If p_refObject and p_refField are given, the query is limited to the objects that are referenced from p_refObject through p_refField.''' - params = {'ClassName': className} appyClass = self.getAppyClass(className, wrapper=True) if not brainsOnly: params['batch'] = True diff --git a/gen/wrappers/__init__.py b/gen/wrappers/__init__.py index d58702d..670f86a 100644 --- a/gen/wrappers/__init__.py +++ b/gen/wrappers/__init__.py @@ -874,19 +874,22 @@ class AbstractWrapper(object): replaced with normal chars.''' return normalizeString(s, usage) - def search(self, klass, sortBy='', maxResults=None, noSecurity=False, - **fields): + def search(self, klass, sortBy='', sortOrder='asc', maxResults=None, + noSecurity=False, **fields): '''Searches objects of p_klass. p_sortBy must be the name of an indexed - field (declared with indexed=True); every param in p_fields must - take the name of an indexed field and take a possible value of this - field. You can optionally specify a maximum number of results in - p_maxResults. If p_noSecurity is specified, you get all objects, - even if the logged user does not have the permission to view it.''' + field (declared with indexed=True); p_sortOrder can be "asc" + (ascending, the defaut) or "desc" (descending); every param in + p_fields must take the name of an indexed field and take a possible + value of this field. You can optionally specify a maximum number of + results in p_maxResults. If p_noSecurity is specified, you get all + objects, even if the logged user does not have the permission to + view it.''' # Find the content type corresponding to p_klass tool = self.tool.o contentType = tool.getPortalType(klass) # Create the Search object - search = Search('customSearch', sortBy=sortBy, **fields) + search = Search('customSearch', sortBy=sortBy, sortOrder=sortOrder, + **fields) if not maxResults: maxResults = 'NO_LIMIT' # If I let maxResults=None, only a subset of the results will be