[gen] Bugfix: it is now possible to generate indexes on back references.
This commit is contained in:
parent
4db5e9d995
commit
3f75d14e92
|
@ -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)
|
||||||
|
|
|
@ -190,44 +190,40 @@ 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:
|
|
||||||
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'):
|
if moduleFile.endswith('.pyc'):
|
||||||
moduleFile = moduleFile[:-1]
|
moduleFile = moduleFile[:-1]
|
||||||
astClasses = Ast(moduleFile).classes
|
astClasses = Ast(moduleFile).classes
|
||||||
except ImportError, ie:
|
# Check if tests are present in this module
|
||||||
# True import error or, simply, this is a simple folder within
|
if self.containsTests(module):
|
||||||
# the application, not a sub-module.
|
self.modulesWithTests.add(module.__name__)
|
||||||
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
|
||||||
|
# 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':
|
if appyType == 'class':
|
||||||
# Determine the class type (standard, tool, user...)
|
# Determine the class type (standard, tool, user...)
|
||||||
if issubclass(moduleElem, gen.Tool):
|
if issubclass(moduleElem, gen.Tool):
|
||||||
|
@ -248,29 +244,16 @@ class Generator:
|
||||||
self.classes.append(descriptor)
|
self.classes.append(descriptor)
|
||||||
# Manage classes containing tests
|
# Manage classes containing tests
|
||||||
if self.containsTests(moduleElem):
|
if self.containsTests(moduleElem):
|
||||||
self.modulesWithTests.add(moduleObj.__name__)
|
self.modulesWithTests.add(module.__name__)
|
||||||
elif appyType == 'workflow':
|
elif appyType == 'workflow':
|
||||||
descriptorClass = self.descriptorClasses['workflow']
|
descriptorClass = self.descriptorClasses['workflow']
|
||||||
descriptor = descriptorClass(moduleElem, attrs, self)
|
descriptor = descriptorClass(moduleElem, attrs, self)
|
||||||
self.workflows.append(descriptor)
|
self.workflows.append(descriptor)
|
||||||
if self.containsTests(moduleElem):
|
if self.containsTests(moduleElem):
|
||||||
self.modulesWithTests.add(moduleObj.__name__)
|
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]
|
||||||
|
|
Loading…
Reference in a new issue