From b6dcc420382d357dad686a08af75e5c8049ea831 Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Wed, 14 Sep 2011 21:01:58 +0200 Subject: [PATCH] appy.gen: use new index 'getState' for indexing object states; reduced size of generated file config.py; optimized debug mode: class reload is not done automatically: a 'refresh' icon is available on view and edit views. --- gen/__init__.py | 37 ++++++++++++------------- gen/plone25/generator.py | 31 +++++---------------- gen/plone25/installer.py | 41 ++++++++++++++++++---------- gen/plone25/mixins/ToolMixin.py | 28 +++++++++---------- gen/plone25/mixins/__init__.py | 45 ++++++++++++++++--------------- gen/plone25/model.py | 6 ++--- gen/plone25/skin/page.pt | 26 +++++++++++------- gen/plone25/skin/refresh.png | Bin 0 -> 1636 bytes gen/plone25/templates/config.py | 3 --- gen/plone25/wrappers/__init__.py | 10 ++----- 10 files changed, 106 insertions(+), 121 deletions(-) create mode 100644 gen/plone25/skin/refresh.png diff --git a/gen/__init__.py b/gen/__init__.py index 55a5a02..815587c 100644 --- a/gen/__init__.py +++ b/gen/__init__.py @@ -326,7 +326,6 @@ class Search: elif fieldName == 'description': if usage == 'search': return 'Description' else: return None - elif fieldName == 'state': return 'review_state' else: return 'get%s%s'% (fieldName[0].upper(),fieldName[1:]) @staticmethod @@ -487,24 +486,27 @@ class Type: called for storing the name of the Appy field (p_name) and other attributes that are based on the name of the Appy p_klass, and the application name (p_appName).''' + if hasattr(self, 'name'): return # Already initialized self.name = name + # Determine prefix for this class + if not klass: prefix = appName + else: prefix = getClassName(klass, appName) # Recompute the ID (and derived attributes) that may have changed if # we are in debug mode (because we recreate new Type instances). self.id = id(self) if self.slaves: self.master_css = 'appyMaster master_%s' % self.id # Determine ids of i18n labels for this field labelName = name - prefix = None + trPrefix = None if self.label: - if isinstance(self.label, basestring): prefix = self.label - else: # It is a tuple (prefix, name) + if isinstance(self.label, basestring): trPrefix = self.label + else: # It is a tuple (trPrefix, name) if self.label[1]: labelName = self.label[1] - if self.label[0]: prefix = self.label[0] - if not prefix: - if not klass: prefix = appName - else: prefix = getClassName(klass, appName) + if self.label[0]: trPrefix = self.label[0] + if not trPrefix: + trPrefix = prefix # Determine name to use for i18n - self.labelId = '%s_%s' % (prefix, labelName) + self.labelId = '%s_%s' % (trPrefix, labelName) self.descrId = self.labelId + '_descr' self.helpId = self.labelId + '_help' # Determine read and write permissions for this field @@ -522,9 +524,10 @@ class Type: self.writePermission = wp else: self.writePermission = 'Modify portal content' - if isinstance(self, Ref): - self.backd = self.back.__dict__ if isinstance(self, Ref) and not self.isBack: + # We must initialise the corresponding back reference + self.back.klass = klass + self.back.init(self.back.attribute, self.klass, appName) self.back.relationship = '%s_%s_rel' % (prefix, name) def reload(self, klass, obj): @@ -534,6 +537,7 @@ class Type: module has been performed.''' res = getattr(klass, self.name, None) if not res: return self + if isinstance(self, Ref) and self.isBack: return self res.init(self.name, klass, obj.getProductConfig().PROJECTNAME) return res @@ -1693,6 +1697,7 @@ class Ref(Type): back.isBack = True back.back = self back.backd = self.__dict__ + setattr(klass, back.attribute, back) # When displaying a tabular list of referenced objects, must we show # the table headers? self.showHeaders = showHeaders @@ -2487,7 +2492,7 @@ class Transition: targetState.updatePermissions(wf, obj) # Refresh catalog-related security if required if not obj.isTemporary(): - obj.reindexObject(idxs=('allowedRolesAndUsers','review_state')) + obj.reindexObject(idxs=('allowedRolesAndUsers', 'getState')) # Execute the related action if needed msg = '' if doAction and self.action: msg = self.executeAction(obj, wf) @@ -2654,12 +2659,4 @@ class Config: # Language that will be used as a basis for translating to other # languages. self.sourceLanguage = 'en' - -# ------------------------------------------------------------------------------ -# Special field "type" is mandatory for every class. If one class does not -# define it, we will add a copy of the instance defined below. -title = String(multiplicity=(1,1), show='edit') -title.init('title', None, 'appy') -state = String() -state.init('state', None, 'appy') # ------------------------------------------------------------------------------ diff --git a/gen/plone25/generator.py b/gen/plone25/generator.py index 1dc4de3..cb497d4 100644 --- a/gen/plone25/generator.py +++ b/gen/plone25/generator.py @@ -370,40 +370,21 @@ class Generator(AbstractGenerator): # Compute the list of ordered attributes (forward and backward, # inherited included) for every Appy class. attributes = [] - attributesDict = [] for classDescr in classesAll: titleFound = False - attrs = [] - attrNames = [] + names = [] for name, appyType, klass in classDescr.getOrderedAppyAttributes(): - attrs.append(self.getAppyTypePath(name, appyType, klass)) - attrNames.append(name) + names.append(name) if name == 'title': titleFound = True # Add the "title" mandatory field if not found - if not titleFound: - attrs.insert(0, 'copy.deepcopy(appy.gen.title)') - attrNames.insert(0, 'title') + if not titleFound: names.insert(0, 'title') # Any backward attributes to append? if classDescr.name in self.referers: for field, rel in self.referers[classDescr.name]: - try: - getattr(field.classDescr.klass, field.fieldName) - klass = field.classDescr.klass - except AttributeError: - klass = field.classDescr.modelClass - attrs.append(self.getAppyTypePath(field.fieldName, - field.appyType, klass, isBack=True)) - attrNames.append(field.appyType.back.attribute) - attributes.append('"%s":[%s]' % (classDescr.name,','.join(attrs))) - aDict = '' - i = -1 - for attr in attrs: - i += 1 - aDict += '"%s":attributes["%s"][%d],' % \ - (attrNames[i], classDescr.name, i) - attributesDict.append('"%s":{%s}' % (classDescr.name, aDict)) + names.append(field.appyType.back.attribute) + qNames = ['"%s"' % name for name in names] + attributes.append('"%s":[%s]' % (classDescr.name, ','.join(qNames))) repls['attributes'] = ',\n '.join(attributes) - repls['attributesDict'] = ',\n '.join(attributesDict) # Compute list of used roles for registering them if needed specificRoles = self.getAllUsedRoles(plone=False) repls['roles'] = ','.join(['"%s"' % r.name for r in specificRoles]) diff --git a/gen/plone25/installer.py b/gen/plone25/installer.py index d93a63d..c5c3a2f 100644 --- a/gen/plone25/installer.py +++ b/gen/plone25/installer.py @@ -6,7 +6,7 @@ import os, os.path, time from StringIO import StringIO from sets import Set import appy -from appy.gen import Type, Ref +from appy.gen import Type, Ref, String from appy.gen.po import PoParser from appy.gen.utils import produceNiceMessage from appy.gen.plone25.utils import updateRolesForPermission @@ -231,10 +231,11 @@ class PloneInstaller: '''Creates or updates the POD templates in the tool according to pod declarations in the application classes.''' # Creates the templates for Pod fields if they do not exist. - for contentType, appyTypes in self.attributes.iteritems(): + for contentType in self.attributes.iterkeys(): appyClass = self.tool.getAppyClass(contentType) if not appyClass: continue # May be an abstract class - for appyType in appyTypes: + wrapperClass = self.tool.getAppyClass(contentType, wrapper=True) + for appyType in wrapperClass.__fields__: if appyType.type != 'Pod': continue # Find the attribute that stores the template, and store on # it the default one specified in the appyType if no @@ -290,6 +291,7 @@ class PloneInstaller: nvProps.manage_changeProperties(**{'idsNotToList': current}) self.tool = getattr(self.ploneSite, self.toolInstanceName) + self.tool.refreshSecurity() self.appyTool = self.tool.appy() if self.reinstall: self.tool.createOrUpdate(False, None) @@ -413,9 +415,12 @@ class PloneInstaller: def manageIndexes(self): '''For every indexed field, this method installs and updates the corresponding index if it does not exist yet.''' - indexInfo = {} - for className, appyTypes in self.attributes.iteritems(): - for appyType in appyTypes: + # Create a special index for object state, that does not correspond to + # a field. + indexInfo = {'getState': 'FieldIndex'} + for className in self.attributes.iterkeys(): + wrapperClass = self.tool.getAppyClass(className, wrapper=True) + for appyType in wrapperClass.__fields__: if not appyType.indexed or (appyType.name == 'title'): continue n = appyType.name indexName = 'get%s%s' % (n[0].upper(), n[1:]) @@ -553,17 +558,25 @@ class ZopeInstaller: def completeAppyTypes(self): '''We complete here the initialisation process of every Appy type of every gen-class of the application.''' + appName = self.productName for klass in self.classes: + # Store on wrapper class the ordered list of Appy types + wrapperClass = klass.wrapperClass + if not hasattr(wrapperClass, 'title'): + # Special field "type" is mandatory for every class. + title = String(multiplicity=(1,1), show='edit', indexed=True) + title.init('title', None, 'appy') + setattr(wrapperClass, 'title', title) + names = self.config.attributes[wrapperClass.__name__[:-8]] + wrapperClass.__fields__ = [getattr(wrapperClass, n) for n in names] + # Post-initialise every Appy type for baseClass in klass.wrapperClass.__bases__: + if baseClass.__name__ == 'AbstractWrapper': continue for name, appyType in baseClass.__dict__.iteritems(): - if isinstance(appyType, Type): - appyType.init(name, baseClass, self.productName) - # Do not forget back references - if isinstance(appyType, Ref): - bAppyType = appyType.back - bAppyType.init(bAppyType.attribute, appyType.klass, - self.productName) - bAppyType.klass = baseClass + if not isinstance(appyType, Type) or \ + (isinstance(appyType, Ref) and appyType.isBack): + continue # Back refs are initialised within fw refs + appyType.init(name, baseClass, appName) def installApplication(self): '''Performs some application-wide installation steps.''' diff --git a/gen/plone25/mixins/ToolMixin.py b/gen/plone25/mixins/ToolMixin.py index 6aa02bf..8bb89a0 100644 --- a/gen/plone25/mixins/ToolMixin.py +++ b/gen/plone25/mixins/ToolMixin.py @@ -79,10 +79,9 @@ class ToolMixin(BaseMixin): def _appy_getAllFields(self, contentType): '''Returns the (translated) names of fields of p_contentType.''' res = [] - for appyType in self.getProductConfig().attributes[contentType]: - if appyType.name != 'title': # Will be included by default. - label = '%s_%s' % (contentType, appyType.name) - res.append((appyType.name, self.translate(label))) + for appyType in self.getAllAppyTypes(className=contentType): + if appyType.name == 'title': continue # Will be included by default. + res.append((appyType.name, self.translate(appyType.labelId))) # Add object state res.append(('workflowState', self.translate('workflow_state'))) return res @@ -91,7 +90,7 @@ class ToolMixin(BaseMixin): '''Returns the (translated) names of fields that may be searched on objects of type p_contentType (=indexed fields).''' res = [] - for appyType in self.getProductConfig().attributes[contentType]: + for appyType in self.getAllAppyTypes(className=contentType): if appyType.indexed: res.append((appyType.name, self.translate(appyType.labelId))) return res @@ -343,26 +342,23 @@ class ToolMixin(BaseMixin): def getPublishedObject(self): '''Gets the currently published object, if its meta_class is among application classes.''' - rq = self.REQUEST - obj = rq['PUBLISHED'] + obj = self.REQUEST['PUBLISHED'] parent = obj.getParentNode() - if parent.id == 'skyn': - obj = parent.getParentNode() - if obj.meta_type in self.getProductConfig().attributes: - return obj - return None + if parent.id == 'skyn': obj = parent.getParentNode() + if obj.meta_type in self.getProductConfig().attributes: return obj - def getAppyClass(self, contentType): + def getAppyClass(self, contentType, wrapper=False): '''Gets the Appy Python class that is related to p_contentType.''' # Retrieve first the Archetypes class corresponding to p_ContentType portalType = self.portal_types.get(contentType) - if not portalType: return None + if not portalType: return atClassName = portalType.getProperty('content_meta_type') appName = self.getProductConfig().PROJECTNAME exec 'from Products.%s.%s import %s as atClass' % \ (appName, atClassName, atClassName) # Get then the Appy Python class - return atClass.wrapperClass.__bases__[-1] + if wrapper: return atClass.wrapperClass + else: return atClass.wrapperClass.__bases__[-1] def getCreateMeans(self, contentTypeOrAppyClass): '''Gets the different ways objects of p_contentTypeOrAppyClass (which @@ -395,6 +391,8 @@ class ToolMixin(BaseMixin): '''This method checks if the currently logged user can trigger searches on a given p_rootClass. This is done by calling method "maySearch" on the class. If no such method exists, we return True.''' + # When editign a form, one should avoid annoying the user with this. + if self.REQUEST['ACTUAL_URL'].endswith('/edit'): return pythonClass = self.getAppyClass(rootClass) if 'maySearch' in pythonClass.__dict__: return pythonClass.maySearch(self.appy()) diff --git a/gen/plone25/mixins/__init__.py b/gen/plone25/mixins/__init__.py index 3d923f1..9fca152 100644 --- a/gen/plone25/mixins/__init__.py +++ b/gen/plone25/mixins/__init__.py @@ -51,10 +51,7 @@ class BaseMixin: for appyType in self.getAppyTypes('edit', rq.get('page')): value = getattr(values, appyType.name, None) appyType.store(obj, value) - if created: - # Now we have a title for the object, so we derive a nice id - obj.unmarkCreationFlag() - obj._renameAfterCreation(check_auto_id=True) + if created: obj.unmarkCreationFlag() if previousData: # Keep in history potential changes on historized fields self.historizeData(previousData) @@ -499,7 +496,6 @@ class BaseMixin: '''Are we in debug mode ?''' for arg in sys.argv: if arg == 'debug-mode=on': return True - return False def getClass(self, reloaded=False): '''Returns the Appy class that dictates self's behaviour.''' @@ -524,30 +520,34 @@ class BaseMixin: def getAppyType(self, name, asDict=False, className=None): '''Returns the Appy type named p_name. If no p_className is defined, the field is supposed to belong to self's class.''' - className = className or self.__class__.__name__ - attrs = self.getProductConfig().attributesDict[className] - appyType = attrs.get(name, None) - if appyType and asDict: return appyType.__dict__ - return appyType + if not className: + klass = self.__class__.wrapperClass + else: + klass = self.getTool().getAppyClass(className, wrapper=True) + res = getattr(klass, name, None) + if res and asDict: return res.__dict__ + return res def getAllAppyTypes(self, className=None): '''Returns the ordered list of all Appy types for self's class if p_className is not specified, or for p_className else.''' - className = className or self.__class__.__name__ - return self.getProductConfig().attributes[className] + if not className: + klass = self.__class__.wrapperClass + else: + klass = self.getTool().getAppyClass(className, wrapper=True) + return klass.__fields__ def getGroupedAppyTypes(self, layoutType, pageName): '''Returns the fields sorted by group. For every field, the appyType (dict version) is given.''' res = [] groups = {} # The already encountered groups - # In debug mode, reload the module containing self's class. - debug = self.isDebug() - if debug: + # If param "refresh" is there, we must reload the Python class + refresh = ('refresh' in self.REQUEST) + if refresh: klass = self.getClass(reloaded=True) for appyType in self.getAllAppyTypes(): - if debug: - appyType = appyType.reload(klass, self) + if refresh: appyType = appyType.reload(klass, self) if appyType.page.name != pageName: continue if not appyType.isShowable(self, layoutType): continue if not appyType.group: @@ -594,16 +594,17 @@ class BaseMixin: the result.''' res = [] for name in fieldNames: - appyType = self.getAppyType(name, asDict) - if appyType: res.append(appyType) - elif name == 'state': + if name == 'state': # We do not return a appyType if the attribute is not a *real* # attribute, but the workfow state. res.append({'name': name, 'labelId': 'workflow_state', 'filterable': False}) else: - self.appy().log('Field "%s", used as shownInfo in a Ref, ' \ - 'was not found.' % name, type='warning') + appyType = self.getAppyType(name, asDict) + if appyType: res.append(appyType) + else: + self.appy().log('Field "%s", used as shownInfo in a Ref, ' \ + 'was not found.' % name, type='warning') if addTitle and ('title' not in fieldNames): res.insert(0, self.getAppyType('title', asDict)) return res diff --git a/gen/plone25/model.py b/gen/plone25/model.py index 0b908c5..678508a 100644 --- a/gen/plone25/model.py +++ b/gen/plone25/model.py @@ -183,10 +183,8 @@ class Tool(ModelClass): listBoxesMaximumWidth = Integer(default=100) def refreshSecurity(self): pass # Real method in the wrapper refreshSecurity = Action(action=refreshSecurity, confirm=True) - # First arg of Ref field below is None because we don't know yet if it will - # link to the predefined User class or a custom class defined in the - # application. - users = Ref(None, multiplicity=(0,None), add=True, link=False, + # Ref(User) will maybe be transformed into Ref(CustomUserClass). + users = Ref(User, multiplicity=(0,None), add=True, link=False, back=Ref(attribute='toTool'), page='users', queryable=True, queryFields=('login',), showHeaders=True, shownInfo=('login', 'title', 'roles')) diff --git a/gen/plone25/skin/page.pt b/gen/plone25/skin/page.pt index 76db618..a40441c 100644 --- a/gen/plone25/skin/page.pt +++ b/gen/plone25/skin/page.pt @@ -670,7 +670,7 @@ isEdit python: layoutType == 'edit'; pageInfo python: phaseInfo['pagesInfo'][page]">
- + - + - + - + - + - + - + - + - + + + + + - + diff --git a/gen/plone25/skin/refresh.png b/gen/plone25/skin/refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..b64a4c5ca5ba50c9772edc8eafdc5f95fe94e617 GIT binary patch literal 1636 zcmV-q2AlbbP)z@;j(q!3lK=n!AY({UO#lFTB>(_`g8%^e{{R4h=>PzA zFaQARU;qF*m;eA5Z<1fdMgRZ>+DSw~RCwBA{Qv(y0}N<}tYctgWCXH6_&o5xY+;tADtiP{O{+RcMM1X*-U@{Vqzc?{0D0U;Xe$2fB$BXl;rnw za4?>~lLzyERqO;%=RW^!O=Vsv0;Wenir;?{Za>>Go*TYMI` zw50Pg45uC|AA)yQ;`(oO_P!k^kd`Tkp1(Yne#VL{NMk7|C!i0m>$0T&Y&PE zz+viB)_ZV%qSOChA3ma+0}w!1E%^QC9|JEJYmSMYOd~swfY8g2{~4Zt2Kodj{tp-@ zz<6T(^XWgsZ(y|CdH$V2M#?7>?WhS%?ZG4Qf8GH@|{{m0Gl`7bZa9|mq_Mh12^W(HOe=3r*v zQu&{A3 z{Q3Ev;q8sXm!F?nyypGgeVhOP{qYppOn?AF^})ZNX8ShH-f-*iqy~mRU#OO5K5#jh*55myiFSU)p)}<%N|%gZFM@`2Xi2Q0Xu9I0XnGMwDE~ z$|L>%&sUIZUxHlC!oklVq~XsX48+Xre4tEb%_StZ^ZmVJCtjUjzUb5AWBY;Xo`F&| zL_a6Pzdt{K4*dr=6Ci+4EPxafprBx35Y>wY8_dcp#qjU32@pUmXayQS^S>`4z|@qf%?q#=X2Niu_ox(CDeFCRd@bk$T7X*4ubwp=%T@|OR9zkfqE6Ci+4 zED#kIJS;9Ito!9Z8^fpHj0{Y`$YN$=WZ+~4#>@ZzY~n`o?!e%<#lj=O&GhG^uCgMZ zzoMFmi;B7+!<947zhBxqX%^6#Z;{Oe2p|*-zPx|_fJ;D9_Z!e)P&#K~f)oeee*R)m zv2-&~we(ub@b}wqR(2*9eql~V4t{P1PF@Cv=P&*-Y?;|Q{ny7khoB`p8~_9mN>Oor zSAG7+cduTtv9JIQhm<+M!js|qum241zyD$Q^8YWxFK%wu-#mPbpO`oq7}~3B9_~_J$KqD|pQ-A{tlfB?c-Wj;GLaVEdKmG75l=RX0tfHW#<6~ ioIH@pO0Jmz0R{l}^I5dK$XrDL0000MiCbP literal 0 HcmV?d00001 diff --git a/gen/plone25/templates/config.py b/gen/plone25/templates/config.py index 9b19c2e..d0e6feb 100644 --- a/gen/plone25/templates/config.py +++ b/gen/plone25/templates/config.py @@ -59,9 +59,6 @@ catalogMap = {} # In the following dict, we store, for every Appy class, the ordered list of # appy types (included inherited ones). attributes = {} -# In the following dict, we store, for every Appy class, a dict of appy types -# keyed by their names. -attributesDict = {} # Application roles applicationRoles = [] diff --git a/gen/plone25/wrappers/__init__.py b/gen/plone25/wrappers/__init__.py index be340dd..59ffa14 100644 --- a/gen/plone25/wrappers/__init__.py +++ b/gen/plone25/wrappers/__init__.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ import os, os.path, time, mimetypes, random import appy.pod -from appy.gen import Type, Search, Ref +from appy.gen import Type, Search, Ref, String from appy.gen.utils import sequenceTypes from appy.shared.utils import getOsTempFolder, executeCommand, normalizeString from appy.shared.xml_parser import XmlMarshaller @@ -41,7 +41,6 @@ class AbstractWrapper(object): elif name == 'typeName': return self.__class__.__bases__[-1].__name__ elif name == 'id': return self.o.id elif name == 'uid': return self.o.UID() - elif name == 'title': return self.o.Title() elif name == 'klass': return self.__class__.__bases__[-1] elif name == 'url': return self.o.absolute_url() elif name == 'state': return self.o.getState() @@ -57,12 +56,7 @@ class AbstractWrapper(object): return self.o.portal_membership.getAuthenticatedMember() elif name == 'fields': return self.o.getAllAppyTypes() # Now, let's try to return a real attribute. - try: - res = object.__getattribute__(self, name) - except AttributeError, ae: - # Maybe a back reference? - res = self.o.getAppyType(name) - if not res: raise ae + res = object.__getattribute__(self, name) # If we got an Appy type, return the value of this type for this object if isinstance(res, Type): o = self.o