appy.gen: Refactoring due to De-Plonization.

This commit is contained in:
Gaetan Delannay 2011-12-05 15:11:29 +01:00
parent d934f49a99
commit c5a8968bd3
35 changed files with 237 additions and 480 deletions

View file

@ -3,18 +3,15 @@
# ------------------------------------------------------------------------------
import sys, os.path
from optparse import OptionParser
from appy.gen.generator import GeneratorError
from appy.gen.generator import GeneratorError, ZopeGenerator
from appy.shared.utils import LinesCounter
import appy.version
# ------------------------------------------------------------------------------
ERROR_CODE = 1
VALID_PRODUCT_TYPES = ('zope', 'odt')
APP_NOT_FOUND = 'Application not found at %s.'
WRONG_NG_OF_ARGS = 'Wrong number of arguments.'
WRONG_OUTPUT_FOLDER = 'Output folder not found. Please create it first.'
PRODUCT_TYPE_ERROR = 'Wrong product type. Product type may be one of the ' \
'following: %s' % str(VALID_PRODUCT_TYPES)
C_OPTION = 'Removes from i18n files all labels that are not automatically ' \
'generated from your gen-application. It can be useful during ' \
'development, when you do lots of name changes (classes, ' \
@ -37,7 +34,7 @@ S_OPTION = 'Sorts all i18n labels. If you use this option, among the ' \
'set of translation files.'
class GeneratorScript:
'''usage: %prog [options] app productType outputFolder
'''usage: %prog [options] app outputFolder
"app" is the path to your Appy application, which must be a
Python package (= a folder containing a file named
@ -47,44 +44,29 @@ class GeneratorScript:
generated product, stored or symlinked in
<yourZopeInstance>/Products.
"productType" is the kind of product you want to generate. "zope" is
the only available production-ready target.
"odt" is experimental.
"outputFolder" is the folder where the Zope product will be generated.
For example, if you develop your application in
/home/gdy/MyProject/MyProject, you typically specify
"/home/gdy/MyProject/zope" as outputFolder.
'''
def generateProduct(self, options, application, productType, outputFolder):
if productType == 'odt':
exec 'from appy.gen.odt.generator import Generator'
else:
from appy.gen.generator import ZopeGenerator as Generator
Generator(application, outputFolder, options).run()
def manageArgs(self, parser, options, args):
# Check number of args
if len(args) != 3:
if len(args) != 2:
print WRONG_NG_OF_ARGS
parser.print_help()
sys.exit(ERROR_CODE)
# Check productType
if args[1] not in VALID_PRODUCT_TYPES:
print PRODUCT_TYPE_ERROR
sys.exit(ERROR_CODE)
# Check existence of application
if not os.path.exists(args[0]):
print APP_NOT_FOUND % args[0]
sys.exit(ERROR_CODE)
# Check existence of outputFolder basic type
if not os.path.exists(args[2]):
# Check existence of outputFolder
if not os.path.exists(args[1]):
print WRONG_OUTPUT_FOLDER
sys.exit(ERROR_CODE)
# Convert all paths in absolute paths
for i in (0,2):
for i in (0,1):
args[i] = os.path.abspath(args[i])
def run(self):
optParser = OptionParser(usage=GeneratorScript.__doc__)
optParser.add_option("-c", "--i18n-clean", action='store_true',
@ -95,8 +77,8 @@ class GeneratorScript:
try:
self.manageArgs(optParser, options, args)
print 'Appy version:', appy.version.verbose
print 'Generating %s product in %s...' % (args[1], args[2])
self.generateProduct(options, *args)
print 'Generating Zope product in %s...' % args[1]
ZopeGenerator(args[0], args[1], options).run()
# Give the user some statistics about its code
LinesCounter(args[0]).run()
except GeneratorError, ge:

View file

@ -1026,8 +1026,7 @@ class String(Type):
it will be given by the Appy validation machinery, so it must be
specified as parameter. The function returns True if the check is
successful.'''
if not value: return True # Plone calls me erroneously for
# non-mandatory fields.
if not value: return True
# First, remove any non-digit char
v = ''
for c in value:
@ -1058,8 +1057,7 @@ class String(Type):
'''Checks that p_value corresponds to a valid IBAN number. IBAN stands
for International Bank Account Number (ISO 13616). If the number is
valid, the method returns True.'''
if not value: return True # Plone calls me erroneously for
# non-mandatory fields.
if not value: return True
# First, remove any non-digit or non-letter char
v = ''
for c in value:
@ -1088,8 +1086,7 @@ class String(Type):
'''Checks that p_value corresponds to a valid BIC number. BIC stands
for Bank Identifier Code (ISO 9362). If the number is valid, the
method returns True.'''
if not value: return True # Plone calls me erroneously for
# non-mandatory fields.
if not value: return True
# BIC number must be 8 or 11 chars
if len(value) not in (8, 11): return False
# 4 first chars, representing bank name, must be letters
@ -1176,15 +1173,7 @@ class String(Type):
else: return value
if isinstance(value, basestring) and self.isMultiValued():
value = [value]
# Some backward compatibilities with Archetypes.
elif value.__class__.__name__ == 'BaseUnit':
try:
value = unicode(value)
except UnicodeDecodeError:
value = str(value)
elif isinstance(value, tuple):
# When Appy storage was based on Archetype, multivalued string
# fields stored values as tuples of unicode strings.
value = list(value)
return value
@ -1207,12 +1196,6 @@ class String(Type):
res = [t('%s_list_%s' % (self.labelId, v)) for v in value]
else:
res = t('%s_list_%s' % (self.labelId, value))
elif not isinstance(value, basestring):
# Archetypes "Description" fields may hold a BaseUnit instance.
try:
res = unicode(value)
except UnicodeDecodeError:
res = str(value)
# If value starts with a carriage return, add a space; else, it will
# be ignored.
if isinstance(res, basestring) and \
@ -1384,8 +1367,8 @@ class Boolean(Type):
return value
def getFormattedValue(self, obj, value):
if value: res = obj.translate('yes', domain='plone')
else: res = obj.translate('no', domain='plone')
if value: res = obj.translate('yes')
else: res = obj.translate('no')
return res
def getStorableValue(self, value):
@ -1517,7 +1500,7 @@ class File(Type):
def getFormattedValue(self, obj, value):
if not value: return value
return value._atFile
return value._zopeFile
def getRequestValue(self, request):
return request.get('%s_file' % self.name)
@ -1591,7 +1574,7 @@ class File(Type):
elif isinstance(value, OFSImageFile):
setattr(obj, self.name, value)
elif isinstance(value, FileWrapper):
setattr(obj, self.name, value._atFile)
setattr(obj, self.name, value._zopeFile)
elif isinstance(value, basestring):
setattr(obj, self.name, File.getFileObject(value, zope=True))
elif type(value) in sequenceTypes:
@ -2189,7 +2172,7 @@ class Pod(Type):
def store(self, obj, value):
'''Stores (=freezes) a document (in p_value) in the field.'''
if isinstance(value, FileWrapper):
value = value._atFile
value = value._zopeFile
setattr(obj, self.name, value)
class List(Type):
@ -2283,19 +2266,18 @@ appyToZopePermissions = {
class Role:
'''Represents a role.'''
ploneRoles = ('Manager', 'Member', 'Owner', 'Reviewer', 'Anonymous',
'Authenticated')
ploneLocalRoles = ('Owner',)
ploneUngrantableRoles = ('Anonymous', 'Authenticated')
zopeRoles = ('Manager', 'Owner', 'Anonymous', 'Authenticated')
zopeLocalRoles = ('Owner',)
zopeUngrantableRoles = ('Anonymous', 'Authenticated')
def __init__(self, name, local=False, grantable=True):
self.name = name
self.local = local # True if it can be used as local role only.
# It is a standard Plone role or an application-specific one?
self.plone = name in self.ploneRoles
if self.plone and (name in self.ploneLocalRoles):
# It is a standard Zope role or an application-specific one?
self.zope = name in self.zopeRoles
if self.zope and (name in self.zopeLocalRoles):
self.local = True
self.grantable = grantable
if self.plone and (name in self.ploneUngrantableRoles):
if self.zope and (name in self.zopeUngrantableRoles):
self.grantable = False
# An ungrantable role is one that is, like the Anonymous or
# Authenticated roles, automatically attributed to a user.
@ -2575,8 +2557,7 @@ class Transition:
# Return a message to the user if needed
if not doSay or (transitionName == '_init_'): return
if not msg:
msg = obj.translate(u'Your content\'s status has been modified.',
domain='plone')
msg = obj.translate(u'Changes saved.')
obj.say(msg)
class Permission:

View file

@ -4,7 +4,7 @@
# ------------------------------------------------------------------------------
import types, copy
from appy.gen import State, Transition, Type
import appy.gen as gen
from po import PoMessage
from model import ModelClass, toolFieldPrefixes
from utils import produceNiceMessage, getClassName
@ -27,8 +27,7 @@ class ClassDescriptor(Descriptor):
'''This class gives information about an Appy class.'''
def __init__(self, klass, orderedAttributes, generator):
appy.gen.descriptors.ClassDescriptor.__init__(self, klass,
orderedAttributes, generator)
Descriptor.__init__(self, klass, orderedAttributes, generator)
self.methods = '' # Needed method definitions will be generated here
# We remember here encountered pages and groups defined in the Appy
# type. Indeed, after having parsed all application classes, we will
@ -70,7 +69,7 @@ class ClassDescriptor(Descriptor):
except AttributeError:
attrValue = getattr(self.modelClass, attrName)
hookClass = self.modelClass
if isinstance(attrValue, Type):
if isinstance(attrValue, gen.Type):
if not condition or eval(condition):
attrs.append( (attrName, attrValue, hookClass) )
# Then, add attributes from parent classes
@ -142,7 +141,7 @@ class ClassDescriptor(Descriptor):
attrValue = getattr(self.klass, attrName)
except AttributeError:
attrValue = getattr(self.modelClass, attrName)
if isinstance(attrValue, Type):
if isinstance(attrValue, gen.Type):
if configClass:
attrValue = copy.copy(attrValue)
attrValue.optional = False
@ -184,13 +183,13 @@ class ClassDescriptor(Descriptor):
res = []
if self.klass.__dict__.has_key('creators') and self.klass.creators:
for creator in self.klass.creators:
if isinstance(creator, Role):
if isinstance(creator, gen.Role):
if creator.local:
raise 'Local role "%s" cannot be used as a creator.' % \
creator.name
res.append(creator)
else:
res.append(Role(creator))
res.append(gen.Role(creator))
return res
def getCreateMean(self, type='Import'):
@ -213,13 +212,17 @@ class ClassDescriptor(Descriptor):
res = []
if klass.__dict__.has_key('search'):
searches = klass.__dict__['search']
if isinstance(searches, basestring): res.append(Search(searches))
elif isinstance(searches, Search): res.append(searches)
if isinstance(searches, basestring):
res.append(gen.Search(searches))
elif isinstance(searches, gen.Search):
res.append(searches)
else:
# It must be a list of searches.
for search in searches:
if isinstance(search, basestring):res.append(Search(search))
else: res.append(search)
if isinstance(search, basestring):
res.append(gen.Search(search))
else:
res.append(search)
return res
@staticmethod
@ -268,11 +271,10 @@ class FieldDescriptor:
'''This class gathers information about a specific typed attribute defined
in a gen-class.'''
singleValuedTypes = ('Integer', 'Float', 'Boolean', 'Date', 'File')
# Although Appy allows to specify a multiplicity[0]>1 for those types, it is
# not supported by Archetypes. So we will always generate single-valued type
# definitions for them.
specialParams = ('title', 'description')
# 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
@ -387,8 +389,7 @@ class FieldDescriptor:
if self.appyType.editDefault:
self.generator.tool.addDefaultField(self)
# - put an index on this field?
if self.appyType.indexed and \
(self.fieldName not in ('title', 'description')):
if self.appyType.indexed and (self.fieldName != 'title'):
self.classDescr.addIndexMethod(self)
# i18n labels
messages = self.generator.labels
@ -477,7 +478,7 @@ class ToolClassDescriptor(ClassDescriptor):
self.addField(fieldName, fieldType)
fieldType.validator.append(fieldDescr.fieldName)
fieldType.page.name = 'data'
fieldType.group = Group(fieldDescr.classDescr.klass.__name__)
fieldType.group = gen.Group(fieldDescr.classDescr.klass.__name__)
def addDefaultField(self, fieldDescr):
className = fieldDescr.classDescr.name
@ -485,21 +486,21 @@ class ToolClassDescriptor(ClassDescriptor):
fieldType = fieldDescr.appyType.clone()
self.addField(fieldName, fieldType)
fieldType.page.name = 'data'
fieldType.group = Group(fieldDescr.classDescr.klass.__name__)
fieldType.group = gen.Group(fieldDescr.classDescr.klass.__name__)
def addPodRelatedFields(self, fieldDescr):
'''Adds the fields needed in the Tool for configuring a Pod field.'''
className = fieldDescr.classDescr.name
# On what page and group to display those fields ?
pg = {'page': 'documentGeneration',
'group': Group(fieldDescr.classDescr.klass.__name__, ['50%']*2)}
'group':gen.Group(fieldDescr.classDescr.klass.__name__,['50%']*2)}
# Add the field that will store the pod template.
fieldName = 'podTemplateFor%s_%s' % (className, fieldDescr.fieldName)
fieldType = File(**pg)
fieldType = gen.File(**pg)
self.addField(fieldName, fieldType)
# Add the field that will store the output format(s)
fieldName = 'formatsFor%s_%s' % (className, fieldDescr.fieldName)
fieldType = String(validator=Selection('getPodOutputFormats'),
fieldType = gen.String(validator=gen.Selection('getPodOutputFormats'),
multiplicity=(1,None), default=('odt',), **pg)
self.addField(fieldName, fieldType)
@ -508,7 +509,7 @@ class ToolClassDescriptor(ClassDescriptor):
to select what default columns will be shown on query results.'''
className = classDescr.name
fieldName = 'resultColumnsFor%s' % className
fieldType = String(multiplicity=(0,None), validator=Selection(
fieldType = gen.String(multiplicity=(0,None), validator=gen.Selection(
'_appy_getAllFields*%s' % className), page='userInterface',
group=classDescr.klass.__name__)
self.addField(fieldName, fieldType)
@ -520,13 +521,13 @@ class ToolClassDescriptor(ClassDescriptor):
# Field that defines if advanced search is enabled for class
# p_classDescr or not.
fieldName = 'enableAdvancedSearchFor%s' % className
fieldType = Boolean(default=True, page='userInterface',
fieldType = gen.Boolean(default=True, page='userInterface',
group=classDescr.klass.__name__)
self.addField(fieldName, fieldType)
# Field that defines how many columns are shown on the custom search
# screen.
fieldName = 'numberOfSearchColumnsFor%s' % className
fieldType = Integer(default=3, page='userInterface',
fieldType = gen.Integer(default=3, page='userInterface',
group=classDescr.klass.__name__)
self.addField(fieldName, fieldType)
# Field that allows to select, among all indexed fields, what fields
@ -534,7 +535,7 @@ class ToolClassDescriptor(ClassDescriptor):
fieldName = 'searchFieldsFor%s' % className
defaultValue = [a[0] for a in classDescr.getOrderedAppyAttributes(
condition='attrValue.indexed')]
fieldType = String(multiplicity=(0,None), validator=Selection(
fieldType = gen.String(multiplicity=(0,None), validator=gen.Selection(
'_appy_getSearchableFields*%s' % className), default=defaultValue,
page='userInterface', group=classDescr.klass.__name__)
self.addField(fieldName, fieldType)
@ -546,8 +547,8 @@ class ToolClassDescriptor(ClassDescriptor):
# Field that defines the path of the files to import.
fieldName = 'importPathFor%s' % className
defValue = classDescr.getCreateMean('Import').path
fieldType = String(page='data', multiplicity=(1,1), default=defValue,
group=classDescr.klass.__name__)
fieldType = gen.String(page='data', multiplicity=(1,1),
default=defValue,group=classDescr.klass.__name__)
self.addField(fieldName, fieldType)
def addWorkflowFields(self, classDescr):
@ -560,12 +561,12 @@ class ToolClassDescriptor(ClassDescriptor):
if classDescr.isRoot() or issubclass(classDescr.klass, ModelClass):
defaultValue = True
fieldName = 'showWorkflowFor%s' % className
fieldType = Boolean(default=defaultValue, page='userInterface',
fieldType = gen.Boolean(default=defaultValue, page='userInterface',
group=groupName)
self.addField(fieldName, fieldType)
# Adds the boolean field for showing or not the field "enter comments".
fieldName = 'showWorkflowCommentFieldFor%s' % className
fieldType = Boolean(default=defaultValue, page='userInterface',
fieldType = gen.Boolean(default=defaultValue, page='userInterface',
group=groupName)
self.addField(fieldName, fieldType)
# Adds the boolean field for showing all states in current state or not.
@ -577,13 +578,12 @@ class ToolClassDescriptor(ClassDescriptor):
if len(classDescr.getPhases()) > 1:
defaultValue = True
fieldName = 'showAllStatesInPhaseFor%s' % className
fieldType = Boolean(default=defaultValue, page='userInterface',
fieldType = gen.Boolean(default=defaultValue, page='userInterface',
group=groupName)
self.addField(fieldName, fieldType)
class UserClassDescriptor(ClassDescriptor):
'''Represents an Archetypes-compliant class that corresponds to the User
for the generated application.'''
'''Appy-specific class for representing a user.'''
def __init__(self, klass, generator):
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
self.modelClass = self.klass
@ -649,7 +649,7 @@ class TranslationClassDescriptor(ClassDescriptor):
def addLabelField(self, messageId, page):
'''Adds a Computed field that will display, in the source language, the
content of the text to translate.'''
field = Computed(method=self.modelClass.label, plainText=False,
field = gen.Computed(method=self.modelClass.label, plainText=False,
page=page, show=self.modelClass.show, layouts='f')
self.addField('%s_label' % messageId, field)
@ -683,7 +683,7 @@ class TranslationClassDescriptor(ClassDescriptor):
params['width'] = width
else:
# This is a multi-line field, or a very-long-single-lined field
params['format'] = String.TEXT
params['format'] = gen.String.TEXT
params['height'] = height
self.addField(messageId, String(**params))
self.addField(messageId, gen.String(**params))
# ------------------------------------------------------------------------------

View file

@ -2,7 +2,7 @@
import os, os.path, re, sys, parser, symbol, token, types
import appy.pod, appy.pod.renderer
from appy.shared.utils import FolderDeleter
#from appy.gen import *
import appy.gen as gen
from po import PoMessage, PoFile, PoParser
from descriptors import *
from utils import produceNiceMessage, getClassName
@ -140,7 +140,7 @@ class Generator:
self.user = None
self.workflows = []
self.initialize()
self.config = Config.getDefault()
self.config = gen.Config.getDefault()
self.modulesWithTests = set()
self.totalNumberOfTests = 0
@ -152,9 +152,9 @@ class Generator:
workflow.'''
res = 'none'
for attrValue in klass.__dict__.itervalues():
if isinstance(attrValue, Type):
if isinstance(attrValue, gen.Type):
res = 'class'
elif isinstance(attrValue, State):
elif isinstance(attrValue, gen.State):
res = 'workflow'
if not res:
for baseClass in klass.__bases__:
@ -219,13 +219,13 @@ class Generator:
attrs = astClasses[moduleElem.__name__].attributes
if appyType == 'class':
# Determine the class type (standard, tool, user...)
if issubclass(moduleElem, Tool):
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, User):
elif issubclass(moduleElem, gen.User):
if not self.user:
klass = self.descriptorClasses['user']
self.user = klass(moduleElem, attrs, self)
@ -244,7 +244,7 @@ class Generator:
self.workflows.append(descriptor)
if self.containsTests(moduleElem):
self.modulesWithTests.add(moduleObj.__name__)
elif isinstance(moduleElem, Config):
elif isinstance(moduleElem, gen.Config):
self.config = moduleElem
# Walk potential sub-modules
@ -461,7 +461,6 @@ class ZopeGenerator(Generator):
self.generateTool()
self.generateInit()
self.generateTests()
self.generateConfigureZcml()
# Create version.txt
f = open(os.path.join(self.outputFolder, 'version.txt'), 'w')
f.write(self.version)
@ -536,13 +535,13 @@ class ZopeGenerator(Generator):
self.generateWrappers()
self.generateConfig()
def getAllUsedRoles(self, plone=None, local=None, grantable=None):
def getAllUsedRoles(self, zope=None, local=None, grantable=None):
'''Produces a list of all the roles used within all workflows and
classes defined in this application.
If p_plone is True, it keeps only Plone-standard roles; if p_plone
If p_zope is True, it keeps only Zope-standard roles; if p_zope
is False, it keeps only roles which are specific to this application;
if p_plone is None it has no effect (so it keeps both roles).
if p_zope is None it has no effect (so it keeps both roles).
If p_local is True, it keeps only local roles (ie, roles that can
only be granted locally); if p_local is False, it keeps only "global"
@ -557,8 +556,8 @@ class ZopeGenerator(Generator):
for wfDescr in self.workflows:
for attr in dir(wfDescr.klass):
attrValue = getattr(wfDescr.klass, attr)
if isinstance(attrValue, State) or \
isinstance(attrValue, Transition):
if isinstance(attrValue, gen.State) or \
isinstance(attrValue, gen.Transition):
for role in attrValue.getUsedRoles():
if role.name not in allRoles:
allRoles[role.name] = role
@ -569,7 +568,7 @@ class ZopeGenerator(Generator):
allRoles[role.name] = role
res = allRoles.values()
# Filter the result according to parameters
for p in ('plone', 'local', 'grantable'):
for p in ('zope', 'local', 'grantable'):
if eval(p) != None:
res = [r for r in res if eval('r.%s == %s' % (p, p))]
return res
@ -613,17 +612,6 @@ class ZopeGenerator(Generator):
res = configClasses
return res
def generateConfigureZcml(self):
'''Generates file configure.zcml.'''
repls = self.repls.copy()
# Note every class as "deprecated".
depr = ''
for klass in self.getClasses(include='all'):
depr += '<five:deprecatedManageAddDelete class=".%s.%s"/>\n' % \
(klass.name, klass.name)
repls['deprecated'] = depr
self.copyFile('configure.zcml', repls)
def generateConfig(self):
repls = self.repls.copy()
# Get some lists of classes
@ -677,9 +665,9 @@ class ZopeGenerator(Generator):
attributes.append('"%s":[%s]' % (classDescr.name, ','.join(qNames)))
repls['attributes'] = ',\n '.join(attributes)
# Compute list of used roles for registering them if needed
specificRoles = self.getAllUsedRoles(plone=False)
specificRoles = self.getAllUsedRoles(zope=False)
repls['roles'] = ','.join(['"%s"' % r.name for r in specificRoles])
globalRoles = self.getAllUsedRoles(plone=False, local=False)
globalRoles = self.getAllUsedRoles(zope=False, local=False)
repls['gRoles'] = ','.join(['"%s"' % r.name for r in globalRoles])
grantableRoles = self.getAllUsedRoles(local=False, grantable=True)
repls['grRoles'] = ','.join(['"%s"' % r.name for r in grantableRoles])
@ -780,7 +768,7 @@ class ZopeGenerator(Generator):
self.copyFile('testAll.py', repls, destFolder='tests')
def generateTool(self):
'''Generates the Plone tool that corresponds to this application.'''
'''Generates the tool that corresponds to this application.'''
Msg = PoMessage
# Create Tool-related i18n-related messages
msg = Msg(self.tool.name, '', Msg.CONFIG % self.applicationName)
@ -874,7 +862,7 @@ class ZopeGenerator(Generator):
poMsg.produceNiceDefault()
if poMsg not in self.labels:
self.labels.append(poMsg)
# Generate the resulting Archetypes class.
# Generate the resulting Zope class.
self.copyFile('Class.py', repls, destName=fileName)
def generateWorkflow(self, wfDescr):
@ -886,14 +874,14 @@ class ZopeGenerator(Generator):
wfName = WorkflowDescriptor.getWorkflowName(wfDescr.klass)
# Add i18n messages for states
for name in dir(wfDescr.klass):
if not isinstance(getattr(wfDescr.klass, name), State): continue
if not isinstance(getattr(wfDescr.klass, name), gen.State): continue
poMsg = PoMessage('%s_%s' % (wfName, name), '', name)
poMsg.produceNiceDefault()
self.labels.append(poMsg)
# Add i18n messages for transitions
for name in dir(wfDescr.klass):
transition = getattr(wfDescr.klass, name)
if not isinstance(transition, Transition): continue
if not isinstance(transition, gen.Transition): continue
poMsg = PoMessage('%s_%s' % (wfName, name), '', name)
poMsg.produceNiceDefault()
self.labels.append(poMsg)

View file

@ -5,7 +5,7 @@
import os, os.path, time
import appy
import appy.version
from appy.gen import Type, Ref, String, File
import appy.gen as gen
from appy.gen.po import PoParser
from appy.gen.utils import updateRolesForPermission, createObject
from appy.shared.data import languages
@ -110,7 +110,7 @@ class ZopeInstaller:
for name in files:
baseName, ext = os.path.splitext(name)
f = file(j(root, name))
if ext in File.imageExts:
if ext in gen.File.imageExts:
zopeFolder.manage_addImage(name, f)
elif ext == '.pt':
manage_addPageTemplate(zopeFolder, baseName, '', f.read())
@ -302,13 +302,11 @@ class ZopeInstaller:
# "po" file on disk.
appFolder = self.config.diskFolder
appName = self.config.PROJECTNAME
dn = os.path.dirname
jn = os.path.join
i18nFolder = jn(jn(jn(dn(dn(dn(appFolder))),'Products'),appName),'i18n')
i18nFolder = os.path.join(appFolder, 'tr')
for translation in appyTool.translations:
# Get the "po" file
poName = '%s-%s.po' % (appName, translation.id)
poFile = PoParser(jn(i18nFolder, poName)).parse()
poFile = PoParser(os.path.join(i18nFolder, poName)).parse()
for message in poFile.messages:
setattr(translation, message.id, message.getMessage())
appyTool.log('Translation "%s" updated from "%s".' % \
@ -359,7 +357,7 @@ class ZopeInstaller:
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 = gen.String(multiplicity=(1,1), show='edit',indexed=True)
title.init('title', None, 'appy')
setattr(wrapperClass, 'title', title)
names = self.config.attributes[wrapperClass.__name__[:-8]]
@ -368,8 +366,8 @@ class ZopeInstaller:
for baseClass in klass.wrapperClass.__bases__:
if baseClass.__name__ == 'AbstractWrapper': continue
for name, appyType in baseClass.__dict__.iteritems():
if not isinstance(appyType, Type) or \
(isinstance(appyType, Ref) and appyType.isBack):
if not isinstance(appyType, gen.Type) or \
(isinstance(appyType, gen.Ref) and appyType.isBack):
continue # Back refs are initialised within fw refs
appyType.init(name, baseClass, appName)

View file

@ -9,8 +9,7 @@ class Migrator:
self.installer = installer
def migrateTo_0_7_1(self):
'''Appy 0.7.1 has its own management of Ref fields and does not use
Archetypes references and the reference catalog anymore. So we must
'''Appy 0.7.1 has its own management of Ref fields. So we must
update data structures that store Ref info on instances.'''
ins = self.installer
ins.info('Migrating to Appy 0.7.1...')

View file

@ -3,7 +3,7 @@ import os, os.path, sys
# ------------------------------------------------------------------------------
class TestMixin:
'''This class is mixed in with any PloneTestCase.'''
'''This class is mixed in with any ZopeTestCase.'''
def createUser(self, userId, roles):
'''Creates a user with id p_userId with some p_roles.'''
self.acl_users.addMember(userId, 'password', [], [])
@ -59,7 +59,7 @@ class TestMixin:
def beforeTest(test):
'''Is executed before every test.'''
g = test.globs
g['tool'] = test.app.plone.get('portal_%s' % g['appName'].lower()).appy()
g['tool'] = test.app.config.appy()
cfg = g['tool'].o.getProductConfig()
g['appFolder'] = cfg.diskFolder
moduleOrClassName = g['test'].name # Not used yet.

View file

@ -6,9 +6,9 @@ from appy.shared.data import languages
import appy.gen
from appy.gen import Type, Search, Selection
from appy.gen.utils import SomeObjects, sequenceTypes, getClassName
from appy.gen.plone25.mixins import BaseMixin
from appy.gen.plone25.wrappers import AbstractWrapper
from appy.gen.plone25.descriptors import ClassDescriptor
from appy.gen.mixins import BaseMixin
from appy.gen.wrappers import AbstractWrapper
from appy.gen.descriptors import ClassDescriptor
try:
from AccessControl.ZopeSecurityPolicy import _noroles
except ImportError:
@ -411,7 +411,7 @@ class ToolMixin(BaseMixin):
def getCreateMeans(self, contentTypeOrAppyClass):
'''Gets the different ways objects of p_contentTypeOrAppyClass (which
can be a Plone content type or a Appy class) can be created
can be a Zope content type or a Appy class) can be created
(via a web form, by importing external data, etc). Result is a
dict whose keys are strings (ie "form", "import"...) and whose
values are additional data bout the particular mean.'''
@ -810,7 +810,7 @@ class ToolMixin(BaseMixin):
9: 'month_sep', 10: 'month_oct', 11: 'month_nov', 12: 'month_dec'}
def getMonthName(self, monthNumber):
'''Gets the translated month name of month numbered p_monthNumber.'''
return self.translate(self.monthsIds[int(monthNumber)], domain='plone')
return self.translate(self.monthsIds[int(monthNumber)])
# --------------------------------------------------------------------------
# Authentication-related methods
@ -824,7 +824,7 @@ class ToolMixin(BaseMixin):
if jsEnabled and not cookiesEnabled:
msg = self.translate(u'You must enable cookies before you can ' \
'log in.', domain='plone')
'log in.')
return self.goto(urlBack, msg.encode('utf-8'))
# Perform the Zope-level authentication
login = rq.get('__ac_name', '')
@ -835,11 +835,10 @@ class ToolMixin(BaseMixin):
user = self.acl_users.validate(rq)
if self.userIsAnon():
rq.RESPONSE.expireCookie('__ac', path='/')
msg = self.translate(u'Login failed', domain='plone')
msg = self.translate(u'Login failed')
logMsg = 'Authentication failed (tried with login "%s")' % login
else:
msg = self.translate(u'Welcome! You are now logged in.',
domain='plone')
msg = self.translate(u'Welcome! You are now logged in.')
logMsg = 'User "%s" has been logged in.' % login
msg = msg.encode('utf-8')
self.log(logMsg)
@ -865,7 +864,7 @@ class ToolMixin(BaseMixin):
session.invalidate()
self.log('User "%s" has been logged out.' % userId)
# Remove user from variable "loggedUsers"
from appy.gen.plone25.installer import loggedUsers
from appy.gen.installer import loggedUsers
if loggedUsers.has_key(userId): del loggedUsers[userId]
return self.goto(self.getApp().absolute_url())

View file

@ -1,17 +1,14 @@
'''This package contains mixin classes that are mixed in with generated classes:
- mixins/BaseMixin is mixed in with Standard Archetypes classes;
- mixins/BaseMixin is mixed in with standard Zope classes;
- mixins/ToolMixin is mixed in with the generated application Tool class.'''
# ------------------------------------------------------------------------------
import os, os.path, sys, types, mimetypes, urllib, cgi
from appy import Object
import appy.gen
from appy.gen import Type, String, Selection, Role, No, WorkflowAnonymous, \
Transition, Permission
import appy.gen as gen
from appy.gen.utils import *
from appy.gen.layout import Table, defaultPageLayouts
from appy.gen.descriptors import WorkflowDescriptor
from appy.gen.plone25.descriptors import ClassDescriptor
from appy.gen.descriptors import WorkflowDescriptor, ClassDescriptor
# ------------------------------------------------------------------------------
class BaseMixin:
@ -187,8 +184,7 @@ class BaseMixin:
fields in the database.'''
rq = self.REQUEST
tool = self.getTool()
errorMessage = self.translate(
'Please correct the indicated errors.', domain='plone')
errorMessage = self.translate('Please correct the indicated errors.')
isNew = rq.get('is_new') == 'True'
# If this object is created from an initiator, get info about him.
initiator = None
@ -209,7 +205,7 @@ class BaseMixin:
urlBack = tool.getSiteUrl()
else:
urlBack = self.getUrl()
self.say(self.translate('Changes canceled.', domain='plone'))
self.say(self.translate('Changes canceled.'))
return self.goto(urlBack)
# Object for storing validation errors
@ -245,7 +241,7 @@ class BaseMixin:
obj, msg = self.createOrUpdate(isNew, values, initiator, initiatorField)
# Redirect the user to the appropriate page
if not msg: msg = obj.translate('Changes saved.', domain='plone')
if not msg: msg = obj.translate('Changes saved.')
# If the object has already been deleted (ie, it is a kind of transient
# object like a one-shot form and has already been deleted in method
# onEdit), redirect to the main site page.
@ -711,7 +707,7 @@ class BaseMixin:
if not includeFake:
includeIt = mayTrigger
else:
includeIt = mayTrigger or isinstance(mayTrigger, No)
includeIt = mayTrigger or isinstance(mayTrigger, gen.No)
if not includeNotShowable:
includeIt = includeIt and transition.isShowable(wf, self)
if not includeIt: continue
@ -864,7 +860,7 @@ class BaseMixin:
# Get the initial workflow state
initialState = self.State(name=False)
# Create a Transition instance representing the initial transition.
initialTransition = Transition((initialState, initialState))
initialTransition = gen.Transition((initialState, initialState))
initialTransition.trigger('_init_', self, wf, '')
def getWorkflow(self, name=False, className=None):
@ -875,7 +871,7 @@ class BaseMixin:
else:
appyClass = self.getTool().getAppyClass(className)
if hasattr(appyClass, 'workflow'): wf = appyClass.workflow
else: wf = WorkflowAnonymous
else: wf = gen.WorkflowAnonymous
if not name: return wf
return WorkflowDescriptor.getWorkflowName(wf)

View file

@ -1,14 +1,10 @@
'''This file contains basic classes that will be added into any user
application for creating the basic structure of the application "Tool" which
is the set of web pages used for configuring the application. The "Tool" is
available to administrators under the standard Plone link "site setup". Plone
itself is shipped with several tools used for conguring the various parts of
Plone (content types, catalogs, workflows, etc.)'''
is the set of web pages used for configuring the application.'''
# ------------------------------------------------------------------------------
import types
from appy.gen import *
Grp=Group # Avoid name clash between appy.gen.Group and class Group below
import appy.gen as gen
# Prototypical instances of every type -----------------------------------------
class Protos:
@ -73,7 +69,7 @@ class ModelClass:
value = appyType.getInputLayouts()
elif isinstance(value, basestring):
value = '"%s"' % value
elif isinstance(value, Ref):
elif isinstance(value, gen.Ref):
if not value.isBack: continue
value = klass._appy_getTypeBody(value, wrapperName)
elif type(value) == type(ModelClass):
@ -82,11 +78,11 @@ class ModelClass:
value = value.__name__
else:
value = '%s.%s' % (moduleName, value.__name__)
elif isinstance(value, Selection):
elif isinstance(value, gen.Selection):
value = 'Selection("%s")' % value.methodName
elif isinstance(value, Grp):
elif isinstance(value, gen.Group):
value = 'Grp("%s")' % value.name
elif isinstance(value, Page):
elif isinstance(value, gen.Page):
value = 'pages["%s"]' % value.name
elif callable(value):
value = '%s.%s' % (wrapperName, value.__name__)
@ -135,20 +131,22 @@ class User(ModelClass):
_appy_attributes = ['title', 'name', 'firstName', 'login', 'password1',
'password2', 'roles']
# All methods defined below are fake. Real versions are in the wrapper.
title = String(show=False, indexed=True)
title = gen.String(show=False, indexed=True)
gm = {'group': 'main', 'multiplicity': (1,1), 'width': 25}
name = String(**gm)
firstName = String(**gm)
name = gen.String(**gm)
firstName = gen.String(**gm)
def showLogin(self): pass
def validateLogin(self): pass
login = String(show=showLogin, validator=validateLogin, indexed=True, **gm)
login = gen.String(show=showLogin, validator=validateLogin,
indexed=True, **gm)
def showPassword(self): pass
def validatePassword(self): pass
password1 = String(format=String.PASSWORD, show=showPassword,
password1 = gen.String(format=gen.String.PASSWORD, show=showPassword,
validator=validatePassword, **gm)
password2 = String(format=String.PASSWORD, show=showPassword, **gm)
password2 = gen.String(format=gen.String.PASSWORD, show=showPassword, **gm)
gm['multiplicity'] = (0, None)
roles = String(validator=Selection('getGrantableRoles'), indexed=True, **gm)
roles = gen.String(validator=gen.Selection('getGrantableRoles'),
indexed=True, **gm)
# The Group class --------------------------------------------------------------
class Group(ModelClass):
@ -156,15 +154,15 @@ class Group(ModelClass):
_appy_attributes = ['title', 'login', 'roles', 'users']
# All methods defined below are fake. Real versions are in the wrapper.
m = {'group': 'main', 'width': 25, 'indexed': True}
title = String(multiplicity=(1,1), **m)
title = gen.String(multiplicity=(1,1), **m)
def showLogin(self): pass
def validateLogin(self): pass
login = String(show=showLogin, validator=validateLogin,
login = gen.String(show=showLogin, validator=validateLogin,
multiplicity=(1,1), **m)
roles = String(validator=Selection('getGrantableRoles'),
roles = gen.String(validator=gen.Selection('getGrantableRoles'),
multiplicity=(0,None), **m)
users = Ref(User, multiplicity=(0,None), add=False, link=True,
back=Ref(attribute='groups', show=True),
users = gen.Ref(User, multiplicity=(0,None), add=False, link=True,
back=gen.Ref(attribute='groups', show=True),
showHeaders=True, shownInfo=('title', 'login'))
# The Translation class --------------------------------------------------------
@ -172,9 +170,9 @@ class Translation(ModelClass):
_appy_attributes = ['po', 'title']
# All methods defined below are fake. Real versions are in the wrapper.
def getPoFile(self): pass
po = Action(action=getPoFile, page=Page('actions', show='view'),
po = gen.Action(action=getPoFile, page=gen.Page('actions', show='view'),
result='filetmp')
title = String(show=False, indexed=True)
title = gen.String(show=False, indexed=True)
def label(self): pass
def show(self, name): pass
@ -195,30 +193,31 @@ class Tool(ModelClass):
# Tool attributes
def validPythonWithUno(self, value): pass # Real method in the wrapper
unoEnabledPython = String(group="connectionToOpenOffice",
unoEnabledPython = gen.String(group="connectionToOpenOffice",
validator=validPythonWithUno)
openOfficePort = Integer(default=2002, group="connectionToOpenOffice")
numberOfResultsPerPage = Integer(default=30, show=False)
listBoxesMaximumWidth = Integer(default=100, show=False)
appyVersion = String(show=False, layouts='f')
openOfficePort = gen.Integer(default=2002, group="connectionToOpenOffice")
numberOfResultsPerPage = gen.Integer(default=30, show=False)
listBoxesMaximumWidth = gen.Integer(default=100, show=False)
appyVersion = gen.String(show=False, layouts='f')
def refreshSecurity(self): pass # Real method in the wrapper
refreshSecurity = Action(action=refreshSecurity, confirm=True)
refreshSecurity = gen.Action(action=refreshSecurity, confirm=True)
# Ref(User) will maybe be transformed into Ref(CustomUserClass).
users = Ref(User, multiplicity=(0,None), add=True, link=False,
back=Ref(attribute='toTool', show=False),
page=Page('users', show='view'),
users = gen.Ref(User, multiplicity=(0,None), add=True, link=False,
back=gen.Ref(attribute='toTool', show=False),
page=gen.Page('users', show='view'),
queryable=True, queryFields=('title', 'login'),
showHeaders=True, shownInfo=('title', 'login', 'roles'))
groups = Ref(Group, multiplicity=(0,None), add=True, link=False,
back=Ref(attribute='toTool2', show=False),
page=Page('groups', show='view'),
groups = gen.Ref(Group, multiplicity=(0,None), add=True, link=False,
back=gen.Ref(attribute='toTool2', show=False),
page=gen.Page('groups', show='view'),
queryable=True, queryFields=('title', 'login'),
showHeaders=True, shownInfo=('title', 'login', 'roles'))
translations = Ref(Translation, multiplicity=(0,None),add=False,link=False,
back=Ref(attribute='trToTool', show=False), show='view',
page=Page('translations', show='view'))
enableNotifications = Boolean(default=True,
page=Page('notifications', show=False))
translations = gen.Ref(Translation, multiplicity=(0,None), add=False,
link=False, show='view',
back=gen.Ref(attribute='trToTool', show=False),
page=gen.Page('translations', show='view'))
enableNotifications = gen.Boolean(default=True,
page=gen.Page('notifications', show=False))
@classmethod
def _appy_clean(klass):

View file

@ -41,8 +41,8 @@ def sendMail(obj, transition, transitionName, workflow):
'''Sends mail about p_transition that has been triggered on p_obj that is
controlled by p_workflow.'''
wfName = WorkflowDescriptor.getWorkflowName(workflow.__class__)
ploneObj = obj.o
portal = ploneObj.portal_url.getPortalObject()
zopeObj = obj.o
tool = zopeObj.getTool()
mailInfo = transition.notify(workflow, obj)
if not mailInfo[0]: return # Send a mail to nobody.
# mailInfo may be one of the following:
@ -54,15 +54,15 @@ def sendMail(obj, transition, transitionName, workflow):
# address or one role) or sequences of strings.
# Determine mail subject and body.
if len(mailInfo) <= 2:
# The user didn't mention mail body and subject. We will use
# those defined from i18n labels.
wfHistory = ploneObj.getWorkflowHistory()
# The user didn't mention mail body and subject. We will use those
# defined from i18n labels.
wfHistory = zopeObj.getHistory()
labelPrefix = '%s_%s' % (wfName, transitionName)
tName = obj.translate(labelPrefix)
keys = {'siteUrl': portal.absolute_url(),
'siteTitle': portal.Title(),
'objectUrl': ploneObj.absolute_url(),
'objectTitle': ploneObj.Title(),
keys = {'siteUrl': tool.getPath('/').absolute_url(),
'siteTitle': tool.getAppName(),
'objectUrl': zopeObj.absolute_url(),
'objectTitle': zopeObj.Title(),
'transitionName': tName,
'transitionComment': wfHistory[0]['comments']}
mailSubject = obj.translate(labelPrefix + '_mail_subject', keys)

View file

View file

@ -1,54 +0,0 @@
'''This file contains the main Generator class used for generating an
ODT file from an Appy application.'''
# ------------------------------------------------------------------------------
import os, os.path
from appy.gen import Page
from appy.gen.utils import produceNiceMessage
from appy.gen.generator import Generator as AbstractGenerator
# ------------------------------------------------------------------------------
class Generator(AbstractGenerator):
'''This generator generates ODT files from an Appy application.'''
def __init__(self, *args, **kwargs):
AbstractGenerator.__init__(self, *args, **kwargs)
self.repls = {'generator': self}
def finalize(self):
pass
def getOdtFieldLabel(self, fieldName):
'''Given a p_fieldName, this method creates the label as it will appear
in the ODT file.'''
return '<text:p><text:bookmark text:name="%s"/>%s</text:p>' % \
(fieldName, produceNiceMessage(fieldName))
def generateClass(self, classDescr):
'''Is called each time an Appy class is found in the application.'''
repls = self.repls.copy()
repls['classDescr'] = classDescr
self.copyFile('basic.odt', repls,
destName='%sEdit.odt' % classDescr.klass.__name__, isPod=True)
def fieldIsStaticallyInvisible(self, field):
'''This method determines if p_field is always invisible. It can be
verified for example if field.type.show is the boolean value False or
if the page where the field must be displayed has a boolean attribute
"show" having the boolean value False.'''
if (type(field.show) == bool) and not field.show: return True
if (type(field.page.show) == bool) and not field.page.show: return True
return False
undumpable = ('Ref', 'Action', 'File', 'Computed')
def getRelevantAttributes(self, classDescr):
'''Some fields, like computed fields or actions, should not be dumped
into the ODT file. This method returns the list of "dumpable"
fields.'''
res = []
for fieldName, field, klass in classDescr.getOrderedAppyAttributes():
if (field.type not in self.undumpable) and \
(not self.fieldIsStaticallyInvisible(field)):
res.append((fieldName, field))
return res
# ------------------------------------------------------------------------------

Binary file not shown.

View file

@ -4,8 +4,8 @@ from OFS.Folder import Folder
from appy.gen.utils import createObject
from AccessControl import ClassSecurityInfo
import Products.<!applicationName!>.config as cfg
from appy.gen.plone25.mixins import BaseMixin
from appy.gen.plone25.mixins.ToolMixin import ToolMixin
from appy.gen.mixins import BaseMixin
from appy.gen.mixins.ToolMixin import ToolMixin
from wrappers import <!genClassName!>_Wrapper as Wrapper
def manage_add<!genClassName!>(self, id, title='', REQUEST=None):

View file

@ -1,90 +0,0 @@
#importedElem { color: grey; font-style: italic; }
.appyPod { float:right; }
.appyFocus { color: #900101; }
.appyChanges th {
font-style: italic;
background-color: transparent;
border: 0 none transparent;
padding: 0.1em 0.1em 0.1em 0.1em;
}
.appyChanges td {
padding: 0.1em 0.2em 0.1em 0.2em !important;
border-top: 1px dashed #8CACBB !important;
border-right: 0 none transparent !important;
border-left: 0 none transparent !important;
border-bottom: 0 none transparent !important;
}
/* Tooltip */
a.tooltip span {
display:none;
padding:2px 3px;
margin-top: 25px;
}
a.rtip span { margin-left:3px; }
a.ltip span { margin-left:-150px }
a.tooltip:hover span {
display: inline;
position: absolute;
border: 1px solid grey;
background-color: white;
color: #dd;
}
/* Table styles */
fieldset {
line-height: 1em;
border: 2px solid #8CACBB;
margin: 0.5em 0em 0.5em 0em;
padding: 0 0.7em 0.5em;
}
.noPadding {
padding-right: 0em !important;
padding-left: 0em !important;
padding-top: 0em !important;
padding-bottom: 0em !important;
}
.appyButton {
background: &dtml-globalBackgroundColor; url(&dtml-portal_url;/linkOpaque.gif) 5px 1px no-repeat;
cursor: pointer;
font-size: &dtml-fontSmallSize;;
padding: 1px 1px 1px 12px;
text-transform: &dtml-textTransform;;
/* overflow: visible; IE produces ugly results with this */
}
.fakeButton {
background: #ffd5c0 url(&dtml-portal_url;/ui/fakeTransition.gif) 5px 1px no-repeat;
padding: 3px 4px 3px 12px;
}
/* Portlet elements */
.portletHeader {
text-transform: none;
padding: 1px 0.5em;
}
.portletSearch {
padding: 0 0 0 0.6em;
font-style: normal;
font-size: 95%;
}
.portletGroup {
font-variant: small-caps;
font-weight: bold;
font-style: normal;
}
.portletGroupItem { padding-left: 0.8em; font-style: italic; }
.portletMenu { margin-bottom: 0.4em; }
/* image-right, but without border */
.image-right {
border:0px solid Black;
clear:both;
float:right;
margin:0.5em;
}

View file

@ -1,7 +1,7 @@
<!codeHeader!>
# Test coverage-related stuff --------------------------------------------------
import sys
from appy.gen.plone25.mixins.TestMixin import TestMixin
from appy.gen.mixins.TestMixin import TestMixin
covFolder = TestMixin.getCovFolder()
# The previous method checks in sys.argv whether Zope was lauched for performing
# coverage tests or not.
@ -26,7 +26,7 @@ def countTest():
# ------------------------------------------------------------------------------
import config
from appy.gen.plone25.installer import ZopeInstaller
from appy.gen.installer import ZopeInstaller
# Zope-level installation of the generated product. ----------------------------
def initialize(context):

View file

@ -5,9 +5,7 @@ import wrappers
<!imports!>
# The following imports are here for allowing mixin classes to access those
# elements without being statically dependent on Plone/Zope packages. Indeed,
# every Archetype instance has a method "getProductConfig" that returns this
# module.
# elements without being statically dependent on Zope packages.
from persistent.list import PersistentList
from zExceptions import BadRequest
from ZPublisher.HTTPRequest import BaseRequest

View file

@ -1,6 +0,0 @@
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five"
i18n_domain="<!applicationName!>">
<!deprecated!>
</configure>

View file

@ -3,17 +3,13 @@
from unittest import TestSuite
from Testing import ZopeTestCase
from Testing.ZopeTestCase import ZopeDocTestSuite
from Products.PloneTestCase import PloneTestCase
from appy.gen.plone25.mixins.TestMixin import TestMixin, beforeTest, afterTest
from appy.gen.mixins.TestMixin import TestMixin, beforeTest, afterTest
<!imports!>
# Initialize Zope & Plone test systems -----------------------------------------
ZopeTestCase.installProduct('PloneLanguageTool')
# Initialize the Zope test system ----------------------------------------------
ZopeTestCase.installProduct('<!applicationName!>')
PloneTestCase.setupPloneSite(products=['PloneLanguageTool',
'<!applicationName!>'])
class Test(PloneTestCase.PloneTestCase, TestMixin):
class Test(ZopeTestCase.ZopeTestCase, TestMixin):
'''Base test class for <!applicationName!> test cases.'''
# Data needed for defining the tests -------------------------------------------

View file

@ -1,11 +1,11 @@
# ------------------------------------------------------------------------------
from appy.gen import *
Grp = Group # Avoid name clashes with the Group class below and appy.gen.Group
from appy.gen.plone25.wrappers import AbstractWrapper
from appy.gen.plone25.wrappers.ToolWrapper import ToolWrapper as WTool
from appy.gen.plone25.wrappers.UserWrapper import UserWrapper as WUser
from appy.gen.plone25.wrappers.GroupWrapper import GroupWrapper as WGroup
from appy.gen.plone25.wrappers.TranslationWrapper import TranslationWrapper as WT
from appy.gen.wrappers import AbstractWrapper
from appy.gen.wrappers.ToolWrapper import ToolWrapper as WTool
from appy.gen.wrappers.UserWrapper import UserWrapper as WUser
from appy.gen.wrappers.GroupWrapper import GroupWrapper as WGroup
from appy.gen.wrappers.TranslationWrapper import TranslationWrapper as WT
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
tfw = {"edit":"f","cell":"f","view":"f"} # Layout for Translation fields

View file

@ -1,21 +1,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
metal:use-macro="here/main_template/macros/master">
<tal:comment replace="nothing">Disable standard Plone green tabs</tal:comment>
<div metal:fill-slot="top_slot">
<metal:block metal:use-macro="here/global_defines/macros/defines" />
<div tal:define="dummy python:request.set('disable_border', 1)" />
</div>
<tal:comment replace="nothing">Fill main slot of Plone main_template</tal:comment>
<body>
<metal:fill fill-slot="main"
tal:define="appFolder context/getParentNode;
className request/className;
tool python: portal.get('portal_%s' % appFolder.id.lower());
<tal:main define="tool context/config">
<html metal:use-macro="context/ui/template/macros/main">
<metal:fill fill-slot="content"
tal:define="className request/className;
importElems python: tool.getImportElements(className);
global allAreImported python:True">
@ -68,7 +54,7 @@
</script>
<tal:comment replace="nothing">Form for importing several meetings at once.</tal:comment>
<form name="importElements"
tal:attributes="action python: appFolder.absolute_url()+'/do'" method="post">
tal:attributes="action python: tool.absolute_url()+'/do'" method="post">
<input type="hidden" name="action" value="ImportObjects"/>
<input type="hidden" name="className" tal:attributes="value className"/>
<input type="hidden" name="importPath" value=""/>
@ -121,5 +107,5 @@
tal:attributes="value python:tool.translate('import_many')"/>
</p>
</metal:fill>
</body>
</html>
</tal:main>

View file

@ -70,11 +70,11 @@
<tal:history condition="objs">
<metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/>
<table width="100%" class="history">
<tr i18n:domain="plone">
<th align="left" i18n:translate="listingheader_action">Action</th>
<th align="left" i18n:translate="listingheader_performed_by">By</th>
<th align="left" i18n:translate="listingheader_date_and_time">Date</th>
<th align="left" i18n:translate="listingheader_comment">Comment</th>
<tr>
<th align="left">Action</th>
<th align="left">By</th>
<th align="left">Date</th>
<th align="left">Comment</th>
</tr>
<tal:event repeat="event objs">
<tr tal:define="odd repeat/event/odd;
@ -202,7 +202,7 @@
<img align="left" style="cursor:pointer" onClick="toggleCookie('appyHistory')"
tal:attributes="src python:test(historyExpanded, 'ui/collapse.gif', 'ui/expand.gif');"
id="appyHistory_img"/>&nbsp;
<span i18n:translate="label_history" i18n:domain="plone" class="historyLabel">History</span> ||&nbsp;
<span>History</span> ||&nbsp;
</tal:accessHistory>
<tal:comment replace="nothing">Show document creator</tal:comment>
@ -263,32 +263,28 @@
<tal:previous condition="python: previousPage and pageInfo['showPrevious']">
<tal:button condition="isEdit">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonPrevious"
title="label_previous" i18n:attributes="title" i18n:domain="plone"
tal:attributes="src string:$appUrl/ui/previous.png"/>
title="Previous" tal:attributes="src string:$appUrl/ui/previous.png"/>
<input type="hidden" name="previousPage" tal:attributes="value previousPage"/>
</tal:button>
<tal:link condition="not: isEdit">
<a tal:attributes="href python: contextObj.getUrl(page=previousPage)">
<img tal:attributes="src string:$appUrl/ui/previous.png"
title="label_previous" i18n:attributes="title" i18n:domain="plone"/>
<img tal:attributes="src string:$appUrl/ui/previous.png" title="Previous"/>
</a>
</tal:link>
</tal:previous>
<tal:save condition="python: isEdit and pageInfo['showSave']">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonOk"
title="label_save" i18n:attributes="title" i18n:domain="plone"
tal:attributes="src string:$appUrl/ui/save.png"/>
title="Save" tal:attributes="src string:$appUrl/ui/save.png"/>
</tal:save>
<tal:cancel condition="python: isEdit and pageInfo['showCancel']">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonCancel"
title="label_cancel" i18n:attributes="title" i18n:domain="plone"
tal:attributes="src string:$appUrl/ui/cancel.png"/>
title="Cancel" tal:attributes="src string:$appUrl/ui/cancel.png"/>
</tal:cancel>
<tal:edit condition="python: not isEdit and pageInfo['showOnEdit']">
<img title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
<img title="Edit" style="cursor:pointer"
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=page);
src string: $appUrl/ui/editBig.png"
tal:condition="python: contextObj.allows('Modify portal content')"/>
@ -303,14 +299,12 @@
<tal:next condition="python: nextPage and pageInfo['showNext']">
<tal:button condition="isEdit">
<input type="image" class="imageInput" style="cursor:pointer" name="buttonNext"
title="label_next" i18n:attributes="title" i18n:domain="plone"
tal:attributes="src string:$appUrl/ui/next.png"/>
title="Next" tal:attributes="src string:$appUrl/ui/next.png"/>
<input type="hidden" name="nextPage" tal:attributes="value nextPage"/>
</tal:button>
<tal:link condition="not: isEdit">
<a tal:attributes="href python: contextObj.getUrl(page=nextPage)">
<img tal:attributes="src string:$appUrl/ui/next.png"
title="label_next" i18n:attributes="title" i18n:domain="plone"/>
<img tal:attributes="src string:$appUrl/ui/next.png" title="Next"/>
</a>
</tal:link>
</tal:next>

View file

@ -117,7 +117,7 @@
tal:content="structure python: _(label)"></a>
</td>
<td align="right">
<img title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
<img title="Edit" style="cursor:pointer"
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=pageName);
src string: $appUrl/ui/edit.gif"
tal:condition="python: contextObj.allows('Modify portal content') and phase['pagesInfo'][pageName]['showOnEdit']"/>
@ -136,7 +136,7 @@
</a>
</td>
<td align="right">
<img title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
<img title="Edit" style="cursor:pointer"
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=aPage);
src string: $appUrl/ui/edit.gif"
tal:condition="python: user.has_permission('Modify portal content', contextObj) and phase['pagesInfo'][aPage]['showOnEdit']"/>

View file

@ -125,13 +125,12 @@
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (className, searchName, repeat['obj'].number()+startNumber, totalNumber);"
tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)"
tal:condition="python: obj.allows('Modify portal content')">
<img title="Edit" i18n:domain="plone" i18n:attributes="title"
tal:attributes="src string: $appUrl/ui/edit.gif"/>
<img title="Edit" tal:attributes="src string: $appUrl/ui/edit.gif"/>
</a></td>
<tal:comment replace="nothing">Delete the element</tal:comment>
<td>
<img tal:condition="python: obj.allows('Delete objects') and obj.mayDelete()"
title="Delete" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
title="Delete" style="cursor:pointer"
tal:attributes="src string: $appUrl/ui/delete.png;
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>
</td>

View file

@ -14,10 +14,7 @@
onClick python: 'askConfirm(\'form\', \'%s\', &quot;%s&quot;)' % (formId, labelConfirm)"/>
</tal:confirm>
<input type="submit" name="do" tal:condition="not: widget/confirm"
tal:attributes="value label" onClick="javascript:;"/>
<tal:comment replace="nothing">The previous onClick is simply used to prevent Plone
from adding a CSS class that displays a popup when the user triggers the form multiple
times.</tal:comment>
tal:attributes="value label"/>
</form>
</metal:view>

View file

@ -29,11 +29,11 @@
<label tal:attributes="for widgetName" tal:content="python: tool.translate(widget['labelId'])"></label><br>&nbsp;&nbsp;
<tal:yes define="valueId python:'%s_yes' % name">
<input type="radio" value="True" tal:attributes="name typedWidget; id valueId"/>
<label tal:attributes="for valueId" i18n:translate="yes" i18n:domain="plone"></label>
<label tal:attributes="for valueId">Yes</label>
</tal:yes>
<tal:no define="valueId python:'%s_no' % name">
<input type="radio" value="False" tal:attributes="name typedWidget; id valueId"/>
<label tal:attributes="for valueId" i18n:translate="no" i18n:domain="plone"></label>
<label tal:attributes="for valueId">No</label>
</tal:no>
<tal:whatever define="valueId python:'%s_whatever' % name">
<input type="radio" value="" tal:attributes="name typedWidget; id valueId" checked="checked"/>

View file

@ -24,14 +24,13 @@
<metal:call use-macro="app/ui/widgets/file/macros/view"/><br/>
</tal:showFile>
<tal:editButtons condition="not: empty">
<tal:comment replace="nothing">Keep the file untouched.</tal:comment>
<tal:comment replace="nothing">Keep the file unchanged.</tal:comment>
<input type="radio" value="nochange"
tal:attributes="checked python:test(info['size']!=0, 'checked', None);
name string:${name}_delete;
id string:${name}_nochange;
onclick string:document.getElementById('${name}_file').disabled=true;"/>
<label tal:attributes="for string:${name}_nochange"
i18n:translate="nochange_file" i18n:domain="plone">Keep the file unchanged</label>
<label tal:attributes="for string:${name}_nochange">Keep the file unchanged</label>
<br/>
<tal:comment replace="nothing">Delete the file.</tal:comment>
<tal:delete condition="not: widget/required">
@ -39,8 +38,7 @@
tal:attributes="name string:${name}_delete;
id string:${name}_delete;
onclick string:document.getElementById('${name}_file').disabled=true;"/>
<label tal:attributes="for string:${name}_delete"
i18n:translate="delete_file" i18n:domain="plone">Delete the file</label>
<label tal:attributes="for string:${name}_delete">Delete the file</label>
<br/>
</tal:delete>
<tal:comment replace="nothing">Replace with a new file.</tal:comment>
@ -49,8 +47,7 @@
name string:${name}_delete;
id string:${name}_upload;
onclick string:document.getElementById('${name}_file').disabled=false"/>
<label tal:attributes="for string:${name}_upload;"
i18n:translate="upload_file" i18n:domain="plone">Replace it with a new file</label>
<label tal:attributes="for string:${name}_upload;">Replace it with a new file</label>
<br/>
</tal:editButtons>
<tal:comment replace="nothing">The upload field.</tal:comment>

View file

@ -43,14 +43,13 @@
<td tal:condition="python: obj.allows('Modify portal content') and not appyType['noForm']">
<a tal:define="navInfo python:'ref.%s.%s:%s.%d.%d' % (contextObj.UID(), fieldName, appyType['pageName'], repeat['obj'].number()+startNumber, totalNumber);"
tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)">
<img title="label_edit" i18n:domain="plone" i18n:attributes="title"
tal:attributes="src string: $appUrl/ui/edit.gif"/>
<img title="Edit" tal:attributes="src string: $appUrl/ui/edit.gif"/>
</a>
</td>
<tal:comment replace="nothing">Delete the element</tal:comment>
<td>
<img tal:condition="python: not appyType['isBack'] and obj.allows('Delete objects') and obj.mayDelete()"
title="Delete" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
title="Delete" style="cursor:pointer"
tal:attributes="src string: $appUrl/ui/delete.png;
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>
</td>

View file

@ -247,30 +247,30 @@ CONVERSION_ERROR = 'An error occurred while executing command "%s". %s'
class FileWrapper:
'''When you get, from an appy object, the value of a File attribute, you
get an instance of this class.'''
def __init__(self, atFile):
def __init__(self, zopeFile):
'''This constructor is only used by Appy to create a nice File instance
from a Plone/Zope corresponding instance (p_atFile). If you need to
from a Zope corresponding instance (p_zopeFile). If you need to
create a new file and assign it to a File attribute, use the
attribute setter, do not create yourself an instance of this
class.'''
d = self.__dict__
d['_atFile'] = atFile # Not for you!
d['name'] = atFile.filename
d['content'] = atFile.data
d['mimeType'] = atFile.content_type
d['size'] = atFile.size # In bytes
d['_zopeFile'] = zopeFile # Not for you!
d['name'] = zopeFile.filename
d['content'] = zopeFile.data
d['mimeType'] = zopeFile.content_type
d['size'] = zopeFile.size # In bytes
def __setattr__(self, name, v):
d = self.__dict__
if name == 'name':
self._atFile.filename = v
self._zopeFile.filename = v
d['name'] = v
elif name == 'content':
self._atFile.update_data(v, self.mimeType, len(v))
self._zopeFile.update_data(v, self.mimeType, len(v))
d['content'] = v
d['size'] = len(v)
elif name == 'mimeType':
self._atFile.content_type = self.mimeType = v
self._zopeFile.content_type = self.mimeType = v
else:
raise 'Impossible to set attribute %s. "Settable" attributes ' \
'are "name", "content" and "mimeType".' % name
@ -326,8 +326,7 @@ def getClassName(klass, appName=None):
Zope class. For some classes, name p_appName is required: it is
part of the class name.'''
moduleName = klass.__module__
if (moduleName == 'appy.gen.plone25.model') or \
moduleName.endswith('.wrappers'):
if (moduleName == 'appy.gen.model') or moduleName.endswith('.wrappers'):
# This is a model (generation time or run time)
res = appName + klass.__name__
elif klass.__bases__ and (klass.__bases__[-1].__module__ == 'appy.gen'):

View file

@ -1,5 +1,5 @@
# ------------------------------------------------------------------------------
from appy.gen.plone25.wrappers import AbstractWrapper
from appy.gen.wrappers import AbstractWrapper
# ------------------------------------------------------------------------------
class GroupWrapper(AbstractWrapper):

View file

@ -2,7 +2,7 @@
import os.path
import appy
from appy.shared.utils import executeCommand
from appy.gen.plone25.wrappers import AbstractWrapper
from appy.gen.wrappers import AbstractWrapper
# ------------------------------------------------------------------------------
_PY = 'Please specify a file corresponding to a Python interpreter ' \

View file

@ -1,6 +1,6 @@
# ------------------------------------------------------------------------------
import os.path
from appy.gen.plone25.wrappers import AbstractWrapper
from appy.gen.wrappers import AbstractWrapper
from appy.gen.po import PoFile, PoMessage
from appy.shared.utils import getOsTempFolder

View file

@ -1,5 +1,5 @@
# ------------------------------------------------------------------------------
from appy.gen.plone25.wrappers import AbstractWrapper
from appy.gen.wrappers import AbstractWrapper
# ------------------------------------------------------------------------------
class UserWrapper(AbstractWrapper):