[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
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):
self.appyType = appyType
self.classDescr = classDescriptor
@ -321,14 +316,6 @@ class FieldDescriptor:
def walkRef(self):
'''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
if self.appyType.addConfirm:
label = '%s_%s_addConfirm' % (self.classDescr.name, self.fieldName)

View file

@ -190,87 +190,70 @@ class Generator:
self.totalNumberOfTests += 1
return res
IMPORT_ERROR = 'Warning: error while importing module %s (%s)'
SYNTAX_ERROR = 'Warning: error while parsing module %s (%s)'
noVisit = ('tr', 'zope')
def walkModule(self, moduleName):
'''Visits a given (sub-*)module into the application.'''
# Some sub-modules must not be visited
for nv in self.noVisit:
nvName = '%s.%s' % (self.applicationName, nv)
if moduleName == nvName: return
try:
exec 'import %s' % moduleName
exec 'moduleObj = %s' % moduleName
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__)
def walkModule(self, moduleName, module):
'''Visits a given module of the application.'''
# Create the AST for this module. Producing an AST allows us to retrieve
# class attributes in the order of their definition, which is not
# possible by introspecting dict-based class objects.
moduleFile = module.__file__
if moduleFile.endswith('.pyc'):
moduleFile = moduleFile[:-1]
astClasses = Ast(moduleFile).classes
# Check if tests are present in this module
if self.containsTests(module):
self.modulesWithTests.add(module.__name__)
classType = type(Generator)
# Find all classes in this module
for moduleElemName in moduleObj.__dict__.keys():
exec 'moduleElem = moduleObj.%s' % moduleElemName
for name in module.__dict__.keys():
exec 'moduleElem = module.%s' % name
if (type(moduleElem) == classType) and \
(moduleElem.__module__ == moduleObj.__name__):
(moduleElem.__module__ == module.__name__):
# We have found a Python class definition in this module.
appyType = self.determineAppyType(moduleElem)
if appyType != 'none':
# Produce a list of static class attributes (in the order
# of their definition).
attrs = astClasses[moduleElem.__name__].attributes
if appyType == 'class':
# Determine the class type (standard, tool, user...)
if issubclass(moduleElem, gen.Tool):
if not self.tool:
klass = self.descriptorClasses['tool']
self.tool = klass(moduleElem, attrs, self)
else:
self.tool.update(moduleElem, attrs)
elif issubclass(moduleElem, gen.User):
if not self.user:
klass = self.descriptorClasses['user']
self.user = klass(moduleElem, attrs, self)
else:
self.user.update(moduleElem, attrs)
if appyType == 'none': continue
# Produce a list of static class attributes (in the order
# of their definition).
attrs = astClasses[moduleElem.__name__].attributes
# Collect non-parsable attrs = back references added
# programmatically
moreAttrs = []
for eName, eValue in moduleElem.__dict__.iteritems():
if isinstance(eValue, gen.Type) and (eName not in attrs):
moreAttrs.append(eName)
# Sort them in alphabetical order: else, order would be random
moreAttrs.sort()
if moreAttrs: attrs += moreAttrs
# Add attributes added as back references
if appyType == 'class':
# Determine the class type (standard, tool, user...)
if issubclass(moduleElem, gen.Tool):
if not self.tool:
klass = self.descriptorClasses['tool']
self.tool = klass(moduleElem, attrs, self)
else:
descriptorClass = self.descriptorClasses['class']
descriptor = descriptorClass(moduleElem,attrs, self)
self.classes.append(descriptor)
# Manage classes containing tests
if self.containsTests(moduleElem):
self.modulesWithTests.add(moduleObj.__name__)
elif appyType == 'workflow':
descriptorClass = self.descriptorClasses['workflow']
descriptor = descriptorClass(moduleElem, attrs, self)
self.workflows.append(descriptor)
if self.containsTests(moduleElem):
self.modulesWithTests.add(moduleObj.__name__)
self.tool.update(moduleElem, attrs)
elif issubclass(moduleElem, gen.User):
if not self.user:
klass = self.descriptorClasses['user']
self.user = klass(moduleElem, attrs, self)
else:
self.user.update(moduleElem, attrs)
else:
descriptorClass = self.descriptorClasses['class']
descriptor = descriptorClass(moduleElem,attrs, self)
self.classes.append(descriptor)
# 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):
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):
'''This method walks into the application and creates the corresponding
meta-classes in self.classes, self.workflows, etc.'''
@ -279,7 +262,21 @@ class Generator:
sys.path.append(containingFolder)
# What is the name of the 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()
def generateClass(self, classDescr):
@ -366,7 +363,6 @@ class ZopeGenerator(Generator):
self.page = PageClassDescriptor(Page, self)
# i18n labels to generate
self.labels = po.PoMessages()
self.referers = {}
def i18n(self, id, default, nice=True):
'''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))]
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):
'''Gets the path to the p_appyType when a direct reference to an
appyType must be generated in a Python file.'''
@ -599,10 +587,6 @@ class ZopeGenerator(Generator):
if name == 'title': titleFound = True
# Add the "title" mandatory field if not found
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
names.append('state')
qNames = ['"%s"' % name for name in names]