[gen] Bugfix: it is now possible to generate indexes on back references.

This commit is contained in:
Gaetan Delannay 2012-12-13 10:45:25 +01:00
parent 4db5e9d995
commit 3f75d14e92
2 changed files with 70 additions and 99 deletions

View file

@ -250,11 +250,6 @@ class FieldDescriptor:
'''This class gathers information about a specific typed attribute defined '''This class gathers information about a specific typed attribute defined
in a gen-class.''' in a gen-class.'''
# Although Appy allows to specify a multiplicity[0]>1 for those types, it is
# not currently. So we will always generate single-valued type definitions
# for them.
singleValuedTypes = ('Integer', 'Float', 'Boolean', 'Date', 'File')
def __init__(self, fieldName, appyType, classDescriptor): def __init__(self, fieldName, appyType, classDescriptor):
self.appyType = appyType self.appyType = appyType
self.classDescr = classDescriptor self.classDescr = classDescriptor
@ -321,14 +316,6 @@ class FieldDescriptor:
def walkRef(self): def walkRef(self):
'''How to generate a Ref?''' '''How to generate a Ref?'''
# Update the list of referers
self.generator.addReferer(self)
# Add the widget label for the back reference
back = self.appyType.back
refClassName = getClassName(self.appyType.klass, self.applicationName)
if back.hasLabel:
backName = self.appyType.back.attribute
self.i18n('%s_%s' % (refClassName, backName), backName)
# Add the label for the confirm message if relevant # Add the label for the confirm message if relevant
if self.appyType.addConfirm: if self.appyType.addConfirm:
label = '%s_%s_addConfirm' % (self.classDescr.name, self.fieldName) label = '%s_%s_addConfirm' % (self.classDescr.name, self.fieldName)

View file

@ -190,87 +190,70 @@ class Generator:
self.totalNumberOfTests += 1 self.totalNumberOfTests += 1
return res return res
IMPORT_ERROR = 'Warning: error while importing module %s (%s)' def walkModule(self, moduleName, module):
SYNTAX_ERROR = 'Warning: error while parsing module %s (%s)' '''Visits a given module of the application.'''
noVisit = ('tr', 'zope') # Create the AST for this module. Producing an AST allows us to retrieve
def walkModule(self, moduleName): # class attributes in the order of their definition, which is not
'''Visits a given (sub-*)module into the application.''' # possible by introspecting dict-based class objects.
# Some sub-modules must not be visited moduleFile = module.__file__
for nv in self.noVisit: if moduleFile.endswith('.pyc'):
nvName = '%s.%s' % (self.applicationName, nv) moduleFile = moduleFile[:-1]
if moduleName == nvName: return astClasses = Ast(moduleFile).classes
try: # Check if tests are present in this module
exec 'import %s' % moduleName if self.containsTests(module):
exec 'moduleObj = %s' % moduleName self.modulesWithTests.add(module.__name__)
moduleFile = moduleObj.__file__
if moduleFile.endswith('.pyc'):
moduleFile = moduleFile[:-1]
astClasses = Ast(moduleFile).classes
except ImportError, ie:
# True import error or, simply, this is a simple folder within
# the application, not a sub-module.
print self.IMPORT_ERROR % (moduleName, str(ie))
return
except SyntaxError, se:
print self.SYNTAX_ERROR % (moduleName, str(se))
return
if self.containsTests(moduleObj):
self.modulesWithTests.add(moduleObj.__name__)
classType = type(Generator) classType = type(Generator)
# Find all classes in this module # Find all classes in this module
for moduleElemName in moduleObj.__dict__.keys(): for name in module.__dict__.keys():
exec 'moduleElem = moduleObj.%s' % moduleElemName exec 'moduleElem = module.%s' % name
if (type(moduleElem) == classType) and \ if (type(moduleElem) == classType) and \
(moduleElem.__module__ == moduleObj.__name__): (moduleElem.__module__ == module.__name__):
# We have found a Python class definition in this module. # We have found a Python class definition in this module.
appyType = self.determineAppyType(moduleElem) appyType = self.determineAppyType(moduleElem)
if appyType != 'none': if appyType == 'none': continue
# Produce a list of static class attributes (in the order # Produce a list of static class attributes (in the order
# of their definition). # of their definition).
attrs = astClasses[moduleElem.__name__].attributes attrs = astClasses[moduleElem.__name__].attributes
if appyType == 'class': # Collect non-parsable attrs = back references added
# Determine the class type (standard, tool, user...) # programmatically
if issubclass(moduleElem, gen.Tool): moreAttrs = []
if not self.tool: for eName, eValue in moduleElem.__dict__.iteritems():
klass = self.descriptorClasses['tool'] if isinstance(eValue, gen.Type) and (eName not in attrs):
self.tool = klass(moduleElem, attrs, self) moreAttrs.append(eName)
else: # Sort them in alphabetical order: else, order would be random
self.tool.update(moduleElem, attrs) moreAttrs.sort()
elif issubclass(moduleElem, gen.User): if moreAttrs: attrs += moreAttrs
if not self.user: # Add attributes added as back references
klass = self.descriptorClasses['user'] if appyType == 'class':
self.user = klass(moduleElem, attrs, self) # Determine the class type (standard, tool, user...)
else: if issubclass(moduleElem, gen.Tool):
self.user.update(moduleElem, attrs) if not self.tool:
klass = self.descriptorClasses['tool']
self.tool = klass(moduleElem, attrs, self)
else: else:
descriptorClass = self.descriptorClasses['class'] self.tool.update(moduleElem, attrs)
descriptor = descriptorClass(moduleElem,attrs, self) elif issubclass(moduleElem, gen.User):
self.classes.append(descriptor) if not self.user:
# Manage classes containing tests klass = self.descriptorClasses['user']
if self.containsTests(moduleElem): self.user = klass(moduleElem, attrs, self)
self.modulesWithTests.add(moduleObj.__name__) else:
elif appyType == 'workflow': self.user.update(moduleElem, attrs)
descriptorClass = self.descriptorClasses['workflow'] else:
descriptor = descriptorClass(moduleElem, attrs, self) descriptorClass = self.descriptorClasses['class']
self.workflows.append(descriptor) descriptor = descriptorClass(moduleElem,attrs, self)
if self.containsTests(moduleElem): self.classes.append(descriptor)
self.modulesWithTests.add(moduleObj.__name__) # Manage classes containing tests
if self.containsTests(moduleElem):
self.modulesWithTests.add(module.__name__)
elif appyType == 'workflow':
descriptorClass = self.descriptorClasses['workflow']
descriptor = descriptorClass(moduleElem, attrs, self)
self.workflows.append(descriptor)
if self.containsTests(moduleElem):
self.modulesWithTests.add(module.__name__)
elif isinstance(moduleElem, gen.Config): elif isinstance(moduleElem, gen.Config):
self.config = moduleElem self.config = moduleElem
# Walk potential sub-modules
if moduleFile.find('__init__.py') != -1:
# Potentially, sub-modules exist
moduleFolder = os.path.dirname(moduleFile)
for elem in os.listdir(moduleFolder):
if elem.startswith('.'): continue
subModuleName, ext = os.path.splitext(elem)
if ((ext == '.py') and (subModuleName != '__init__')) or \
os.path.isdir(os.path.join(moduleFolder, subModuleName)):
# Submodules may be sub-folders or Python files
subModuleName = '%s.%s' % (moduleName, subModuleName)
self.walkModule(subModuleName)
def walkApplication(self): def walkApplication(self):
'''This method walks into the application and creates the corresponding '''This method walks into the application and creates the corresponding
meta-classes in self.classes, self.workflows, etc.''' meta-classes in self.classes, self.workflows, etc.'''
@ -279,7 +262,21 @@ class Generator:
sys.path.append(containingFolder) sys.path.append(containingFolder)
# What is the name of the application ? # What is the name of the application ?
appName = os.path.basename(self.application) appName = os.path.basename(self.application)
self.walkModule(appName) # Collect modules (only a the first level) in this application. Import
# them all, to be sure class definitions are complete (ie, back
# references are set from one class to the other). Moreover, potential
# syntax or import errors will raise an exception and abort the
# generation process before we do any undoable action.
modules = []
for fileName in os.listdir(self.application):
# Ignore non Python files
if not fileName.endswith('.py'): continue
moduleName = '%s.%s' % (appName, os.path.splitext(fileName)[0])
exec 'import %s' % moduleName
modules.append(eval(moduleName))
# Parse imported modules
for module in modules:
self.walkModule(moduleName, module)
sys.path.pop() sys.path.pop()
def generateClass(self, classDescr): def generateClass(self, classDescr):
@ -366,7 +363,6 @@ class ZopeGenerator(Generator):
self.page = PageClassDescriptor(Page, self) self.page = PageClassDescriptor(Page, self)
# i18n labels to generate # i18n labels to generate
self.labels = po.PoMessages() self.labels = po.PoMessages()
self.referers = {}
def i18n(self, id, default, nice=True): def i18n(self, id, default, nice=True):
'''Shorthand for adding a new message into self.labels.''' '''Shorthand for adding a new message into self.labels.'''
@ -514,14 +510,6 @@ class ZopeGenerator(Generator):
res = [r for r in res if eval('r.%s == %s' % (p, p))] res = [r for r in res if eval('r.%s == %s' % (p, p))]
return res return res
def addReferer(self, fieldDescr):
'''p_fieldDescr is a Ref type definition.'''
k = fieldDescr.appyType.klass
refClassName = getClassName(k, self.applicationName)
if not self.referers.has_key(refClassName):
self.referers[refClassName] = []
self.referers[refClassName].append(fieldDescr)
def getAppyTypePath(self, name, appyType, klass, isBack=False): def getAppyTypePath(self, name, appyType, klass, isBack=False):
'''Gets the path to the p_appyType when a direct reference to an '''Gets the path to the p_appyType when a direct reference to an
appyType must be generated in a Python file.''' appyType must be generated in a Python file.'''
@ -599,10 +587,6 @@ class ZopeGenerator(Generator):
if name == 'title': titleFound = True if name == 'title': titleFound = True
# Add the "title" mandatory field if not found # Add the "title" mandatory field if not found
if not titleFound: names.insert(0, 'title') if not titleFound: names.insert(0, 'title')
# Any backward attributes to append?
if classDescr.name in self.referers:
for field in self.referers[classDescr.name]:
names.append(field.appyType.back.attribute)
# Add the 'state' attribute # Add the 'state' attribute
names.append('state') names.append('state')
qNames = ['"%s"' % name for name in names] qNames = ['"%s"' % name for name in names]