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 import sys, os.path
from optparse import OptionParser from optparse import OptionParser
from appy.gen.generator import GeneratorError from appy.gen.generator import GeneratorError, ZopeGenerator
from appy.shared.utils import LinesCounter from appy.shared.utils import LinesCounter
import appy.version import appy.version
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
ERROR_CODE = 1 ERROR_CODE = 1
VALID_PRODUCT_TYPES = ('zope', 'odt')
APP_NOT_FOUND = 'Application not found at %s.' APP_NOT_FOUND = 'Application not found at %s.'
WRONG_NG_OF_ARGS = 'Wrong number of arguments.' WRONG_NG_OF_ARGS = 'Wrong number of arguments.'
WRONG_OUTPUT_FOLDER = 'Output folder not found. Please create it first.' 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 ' \ C_OPTION = 'Removes from i18n files all labels that are not automatically ' \
'generated from your gen-application. It can be useful during ' \ 'generated from your gen-application. It can be useful during ' \
'development, when you do lots of name changes (classes, ' \ '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.' 'set of translation files.'
class GeneratorScript: 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 "app" is the path to your Appy application, which must be a
Python package (= a folder containing a file named Python package (= a folder containing a file named
@ -47,44 +44,29 @@ class GeneratorScript:
generated product, stored or symlinked in generated product, stored or symlinked in
<yourZopeInstance>/Products. <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. "outputFolder" is the folder where the Zope product will be generated.
For example, if you develop your application in For example, if you develop your application in
/home/gdy/MyProject/MyProject, you typically specify /home/gdy/MyProject/MyProject, you typically specify
"/home/gdy/MyProject/zope" as outputFolder. "/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): def manageArgs(self, parser, options, args):
# Check number of args # Check number of args
if len(args) != 3: if len(args) != 2:
print WRONG_NG_OF_ARGS print WRONG_NG_OF_ARGS
parser.print_help() parser.print_help()
sys.exit(ERROR_CODE) 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 # Check existence of application
if not os.path.exists(args[0]): if not os.path.exists(args[0]):
print APP_NOT_FOUND % args[0] print APP_NOT_FOUND % args[0]
sys.exit(ERROR_CODE) sys.exit(ERROR_CODE)
# Check existence of outputFolder basic type # Check existence of outputFolder
if not os.path.exists(args[2]): if not os.path.exists(args[1]):
print WRONG_OUTPUT_FOLDER print WRONG_OUTPUT_FOLDER
sys.exit(ERROR_CODE) sys.exit(ERROR_CODE)
# Convert all paths in absolute paths # Convert all paths in absolute paths
for i in (0,2): for i in (0,1):
args[i] = os.path.abspath(args[i]) args[i] = os.path.abspath(args[i])
def run(self): def run(self):
optParser = OptionParser(usage=GeneratorScript.__doc__) optParser = OptionParser(usage=GeneratorScript.__doc__)
optParser.add_option("-c", "--i18n-clean", action='store_true', optParser.add_option("-c", "--i18n-clean", action='store_true',
@ -95,8 +77,8 @@ class GeneratorScript:
try: try:
self.manageArgs(optParser, options, args) self.manageArgs(optParser, options, args)
print 'Appy version:', appy.version.verbose print 'Appy version:', appy.version.verbose
print 'Generating %s product in %s...' % (args[1], args[2]) print 'Generating Zope product in %s...' % args[1]
self.generateProduct(options, *args) ZopeGenerator(args[0], args[1], options).run()
# Give the user some statistics about its code # Give the user some statistics about its code
LinesCounter(args[0]).run() LinesCounter(args[0]).run()
except GeneratorError, ge: 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 it will be given by the Appy validation machinery, so it must be
specified as parameter. The function returns True if the check is specified as parameter. The function returns True if the check is
successful.''' successful.'''
if not value: return True # Plone calls me erroneously for if not value: return True
# non-mandatory fields.
# First, remove any non-digit char # First, remove any non-digit char
v = '' v = ''
for c in value: for c in value:
@ -1058,8 +1057,7 @@ class String(Type):
'''Checks that p_value corresponds to a valid IBAN number. IBAN stands '''Checks that p_value corresponds to a valid IBAN number. IBAN stands
for International Bank Account Number (ISO 13616). If the number is for International Bank Account Number (ISO 13616). If the number is
valid, the method returns True.''' valid, the method returns True.'''
if not value: return True # Plone calls me erroneously for if not value: return True
# non-mandatory fields.
# First, remove any non-digit or non-letter char # First, remove any non-digit or non-letter char
v = '' v = ''
for c in value: for c in value:
@ -1088,8 +1086,7 @@ class String(Type):
'''Checks that p_value corresponds to a valid BIC number. BIC stands '''Checks that p_value corresponds to a valid BIC number. BIC stands
for Bank Identifier Code (ISO 9362). If the number is valid, the for Bank Identifier Code (ISO 9362). If the number is valid, the
method returns True.''' method returns True.'''
if not value: return True # Plone calls me erroneously for if not value: return True
# non-mandatory fields.
# BIC number must be 8 or 11 chars # BIC number must be 8 or 11 chars
if len(value) not in (8, 11): return False if len(value) not in (8, 11): return False
# 4 first chars, representing bank name, must be letters # 4 first chars, representing bank name, must be letters
@ -1176,15 +1173,7 @@ class String(Type):
else: return value else: return value
if isinstance(value, basestring) and self.isMultiValued(): if isinstance(value, basestring) and self.isMultiValued():
value = [value] 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): elif isinstance(value, tuple):
# When Appy storage was based on Archetype, multivalued string
# fields stored values as tuples of unicode strings.
value = list(value) value = list(value)
return value return value
@ -1207,12 +1196,6 @@ class String(Type):
res = [t('%s_list_%s' % (self.labelId, v)) for v in value] res = [t('%s_list_%s' % (self.labelId, v)) for v in value]
else: else:
res = t('%s_list_%s' % (self.labelId, value)) 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 # If value starts with a carriage return, add a space; else, it will
# be ignored. # be ignored.
if isinstance(res, basestring) and \ if isinstance(res, basestring) and \
@ -1384,8 +1367,8 @@ class Boolean(Type):
return value return value
def getFormattedValue(self, obj, value): def getFormattedValue(self, obj, value):
if value: res = obj.translate('yes', domain='plone') if value: res = obj.translate('yes')
else: res = obj.translate('no', domain='plone') else: res = obj.translate('no')
return res return res
def getStorableValue(self, value): def getStorableValue(self, value):
@ -1517,7 +1500,7 @@ class File(Type):
def getFormattedValue(self, obj, value): def getFormattedValue(self, obj, value):
if not value: return value if not value: return value
return value._atFile return value._zopeFile
def getRequestValue(self, request): def getRequestValue(self, request):
return request.get('%s_file' % self.name) return request.get('%s_file' % self.name)
@ -1591,7 +1574,7 @@ class File(Type):
elif isinstance(value, OFSImageFile): elif isinstance(value, OFSImageFile):
setattr(obj, self.name, value) setattr(obj, self.name, value)
elif isinstance(value, FileWrapper): elif isinstance(value, FileWrapper):
setattr(obj, self.name, value._atFile) setattr(obj, self.name, value._zopeFile)
elif isinstance(value, basestring): elif isinstance(value, basestring):
setattr(obj, self.name, File.getFileObject(value, zope=True)) setattr(obj, self.name, File.getFileObject(value, zope=True))
elif type(value) in sequenceTypes: elif type(value) in sequenceTypes:
@ -2189,7 +2172,7 @@ class Pod(Type):
def store(self, obj, value): def store(self, obj, value):
'''Stores (=freezes) a document (in p_value) in the field.''' '''Stores (=freezes) a document (in p_value) in the field.'''
if isinstance(value, FileWrapper): if isinstance(value, FileWrapper):
value = value._atFile value = value._zopeFile
setattr(obj, self.name, value) setattr(obj, self.name, value)
class List(Type): class List(Type):
@ -2283,19 +2266,18 @@ appyToZopePermissions = {
class Role: class Role:
'''Represents a role.''' '''Represents a role.'''
ploneRoles = ('Manager', 'Member', 'Owner', 'Reviewer', 'Anonymous', zopeRoles = ('Manager', 'Owner', 'Anonymous', 'Authenticated')
'Authenticated') zopeLocalRoles = ('Owner',)
ploneLocalRoles = ('Owner',) zopeUngrantableRoles = ('Anonymous', 'Authenticated')
ploneUngrantableRoles = ('Anonymous', 'Authenticated')
def __init__(self, name, local=False, grantable=True): def __init__(self, name, local=False, grantable=True):
self.name = name self.name = name
self.local = local # True if it can be used as local role only. self.local = local # True if it can be used as local role only.
# It is a standard Plone role or an application-specific one? # It is a standard Zope role or an application-specific one?
self.plone = name in self.ploneRoles self.zope = name in self.zopeRoles
if self.plone and (name in self.ploneLocalRoles): if self.zope and (name in self.zopeLocalRoles):
self.local = True self.local = True
self.grantable = grantable self.grantable = grantable
if self.plone and (name in self.ploneUngrantableRoles): if self.zope and (name in self.zopeUngrantableRoles):
self.grantable = False self.grantable = False
# An ungrantable role is one that is, like the Anonymous or # An ungrantable role is one that is, like the Anonymous or
# Authenticated roles, automatically attributed to a user. # Authenticated roles, automatically attributed to a user.
@ -2575,8 +2557,7 @@ class Transition:
# Return a message to the user if needed # Return a message to the user if needed
if not doSay or (transitionName == '_init_'): return if not doSay or (transitionName == '_init_'): return
if not msg: if not msg:
msg = obj.translate(u'Your content\'s status has been modified.', msg = obj.translate(u'Changes saved.')
domain='plone')
obj.say(msg) obj.say(msg)
class Permission: class Permission:

View file

@ -4,7 +4,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import types, copy import types, copy
from appy.gen import State, Transition, Type import appy.gen as gen
from po import PoMessage from po import PoMessage
from model import ModelClass, toolFieldPrefixes from model import ModelClass, toolFieldPrefixes
from utils import produceNiceMessage, getClassName from utils import produceNiceMessage, getClassName
@ -27,8 +27,7 @@ class ClassDescriptor(Descriptor):
'''This class gives information about an Appy class.''' '''This class gives information about an Appy class.'''
def __init__(self, klass, orderedAttributes, generator): def __init__(self, klass, orderedAttributes, generator):
appy.gen.descriptors.ClassDescriptor.__init__(self, klass, Descriptor.__init__(self, klass, orderedAttributes, generator)
orderedAttributes, generator)
self.methods = '' # Needed method definitions will be generated here self.methods = '' # Needed method definitions will be generated here
# We remember here encountered pages and groups defined in the Appy # We remember here encountered pages and groups defined in the Appy
# type. Indeed, after having parsed all application classes, we will # type. Indeed, after having parsed all application classes, we will
@ -70,7 +69,7 @@ class ClassDescriptor(Descriptor):
except AttributeError: except AttributeError:
attrValue = getattr(self.modelClass, attrName) attrValue = getattr(self.modelClass, attrName)
hookClass = self.modelClass hookClass = self.modelClass
if isinstance(attrValue, Type): if isinstance(attrValue, gen.Type):
if not condition or eval(condition): if not condition or eval(condition):
attrs.append( (attrName, attrValue, hookClass) ) attrs.append( (attrName, attrValue, hookClass) )
# Then, add attributes from parent classes # Then, add attributes from parent classes
@ -142,7 +141,7 @@ class ClassDescriptor(Descriptor):
attrValue = getattr(self.klass, attrName) attrValue = getattr(self.klass, attrName)
except AttributeError: except AttributeError:
attrValue = getattr(self.modelClass, attrName) attrValue = getattr(self.modelClass, attrName)
if isinstance(attrValue, Type): if isinstance(attrValue, gen.Type):
if configClass: if configClass:
attrValue = copy.copy(attrValue) attrValue = copy.copy(attrValue)
attrValue.optional = False attrValue.optional = False
@ -184,13 +183,13 @@ class ClassDescriptor(Descriptor):
res = [] res = []
if self.klass.__dict__.has_key('creators') and self.klass.creators: if self.klass.__dict__.has_key('creators') and self.klass.creators:
for creator in self.klass.creators: for creator in self.klass.creators:
if isinstance(creator, Role): if isinstance(creator, gen.Role):
if creator.local: if creator.local:
raise 'Local role "%s" cannot be used as a creator.' % \ raise 'Local role "%s" cannot be used as a creator.' % \
creator.name creator.name
res.append(creator) res.append(creator)
else: else:
res.append(Role(creator)) res.append(gen.Role(creator))
return res return res
def getCreateMean(self, type='Import'): def getCreateMean(self, type='Import'):
@ -213,13 +212,17 @@ class ClassDescriptor(Descriptor):
res = [] res = []
if klass.__dict__.has_key('search'): if klass.__dict__.has_key('search'):
searches = klass.__dict__['search'] searches = klass.__dict__['search']
if isinstance(searches, basestring): res.append(Search(searches)) if isinstance(searches, basestring):
elif isinstance(searches, Search): res.append(searches) res.append(gen.Search(searches))
elif isinstance(searches, gen.Search):
res.append(searches)
else: else:
# It must be a list of searches. # It must be a list of searches.
for search in searches: for search in searches:
if isinstance(search, basestring):res.append(Search(search)) if isinstance(search, basestring):
else: res.append(search) res.append(gen.Search(search))
else:
res.append(search)
return res return res
@staticmethod @staticmethod
@ -268,11 +271,10 @@ 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.'''
singleValuedTypes = ('Integer', 'Float', 'Boolean', 'Date', 'File')
# Although Appy allows to specify a multiplicity[0]>1 for those types, it is # 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 # not currently. So we will always generate single-valued type definitions
# definitions for them. # for them.
specialParams = ('title', 'description') singleValuedTypes = ('Integer', 'Float', 'Boolean', 'Date', 'File')
def __init__(self, fieldName, appyType, classDescriptor): def __init__(self, fieldName, appyType, classDescriptor):
self.appyType = appyType self.appyType = appyType
@ -387,8 +389,7 @@ class FieldDescriptor:
if self.appyType.editDefault: if self.appyType.editDefault:
self.generator.tool.addDefaultField(self) self.generator.tool.addDefaultField(self)
# - put an index on this field? # - put an index on this field?
if self.appyType.indexed and \ if self.appyType.indexed and (self.fieldName != 'title'):
(self.fieldName not in ('title', 'description')):
self.classDescr.addIndexMethod(self) self.classDescr.addIndexMethod(self)
# i18n labels # i18n labels
messages = self.generator.labels messages = self.generator.labels
@ -477,7 +478,7 @@ class ToolClassDescriptor(ClassDescriptor):
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)
fieldType.validator.append(fieldDescr.fieldName) fieldType.validator.append(fieldDescr.fieldName)
fieldType.page.name = 'data' fieldType.page.name = 'data'
fieldType.group = Group(fieldDescr.classDescr.klass.__name__) fieldType.group = gen.Group(fieldDescr.classDescr.klass.__name__)
def addDefaultField(self, fieldDescr): def addDefaultField(self, fieldDescr):
className = fieldDescr.classDescr.name className = fieldDescr.classDescr.name
@ -485,22 +486,22 @@ class ToolClassDescriptor(ClassDescriptor):
fieldType = fieldDescr.appyType.clone() fieldType = fieldDescr.appyType.clone()
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)
fieldType.page.name = 'data' fieldType.page.name = 'data'
fieldType.group = Group(fieldDescr.classDescr.klass.__name__) fieldType.group = gen.Group(fieldDescr.classDescr.klass.__name__)
def addPodRelatedFields(self, fieldDescr): def addPodRelatedFields(self, fieldDescr):
'''Adds the fields needed in the Tool for configuring a Pod field.''' '''Adds the fields needed in the Tool for configuring a Pod field.'''
className = fieldDescr.classDescr.name className = fieldDescr.classDescr.name
# On what page and group to display those fields ? # On what page and group to display those fields ?
pg = {'page': 'documentGeneration', 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. # Add the field that will store the pod template.
fieldName = 'podTemplateFor%s_%s' % (className, fieldDescr.fieldName) fieldName = 'podTemplateFor%s_%s' % (className, fieldDescr.fieldName)
fieldType = File(**pg) fieldType = gen.File(**pg)
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)
# Add the field that will store the output format(s) # Add the field that will store the output format(s)
fieldName = 'formatsFor%s_%s' % (className, fieldDescr.fieldName) 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) multiplicity=(1,None), default=('odt',), **pg)
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)
def addQueryResultColumns(self, classDescr): def addQueryResultColumns(self, classDescr):
@ -508,7 +509,7 @@ class ToolClassDescriptor(ClassDescriptor):
to select what default columns will be shown on query results.''' to select what default columns will be shown on query results.'''
className = classDescr.name className = classDescr.name
fieldName = 'resultColumnsFor%s' % className 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', '_appy_getAllFields*%s' % className), page='userInterface',
group=classDescr.klass.__name__) group=classDescr.klass.__name__)
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)
@ -520,21 +521,21 @@ class ToolClassDescriptor(ClassDescriptor):
# Field that defines if advanced search is enabled for class # Field that defines if advanced search is enabled for class
# p_classDescr or not. # p_classDescr or not.
fieldName = 'enableAdvancedSearchFor%s' % className fieldName = 'enableAdvancedSearchFor%s' % className
fieldType = Boolean(default=True, page='userInterface', fieldType = gen.Boolean(default=True, page='userInterface',
group=classDescr.klass.__name__) group=classDescr.klass.__name__)
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)
# Field that defines how many columns are shown on the custom search # Field that defines how many columns are shown on the custom search
# screen. # screen.
fieldName = 'numberOfSearchColumnsFor%s' % className fieldName = 'numberOfSearchColumnsFor%s' % className
fieldType = Integer(default=3, page='userInterface', fieldType = gen.Integer(default=3, page='userInterface',
group=classDescr.klass.__name__) group=classDescr.klass.__name__)
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)
# Field that allows to select, among all indexed fields, what fields # Field that allows to select, among all indexed fields, what fields
# must really be used in the search screen. # must really be used in the search screen.
fieldName = 'searchFieldsFor%s' % className fieldName = 'searchFieldsFor%s' % className
defaultValue = [a[0] for a in classDescr.getOrderedAppyAttributes( defaultValue = [a[0] for a in classDescr.getOrderedAppyAttributes(
condition='attrValue.indexed')] 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, '_appy_getSearchableFields*%s' % className), default=defaultValue,
page='userInterface', group=classDescr.klass.__name__) page='userInterface', group=classDescr.klass.__name__)
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)
@ -546,8 +547,8 @@ class ToolClassDescriptor(ClassDescriptor):
# Field that defines the path of the files to import. # Field that defines the path of the files to import.
fieldName = 'importPathFor%s' % className fieldName = 'importPathFor%s' % className
defValue = classDescr.getCreateMean('Import').path defValue = classDescr.getCreateMean('Import').path
fieldType = String(page='data', multiplicity=(1,1), default=defValue, fieldType = gen.String(page='data', multiplicity=(1,1),
group=classDescr.klass.__name__) default=defValue,group=classDescr.klass.__name__)
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)
def addWorkflowFields(self, classDescr): def addWorkflowFields(self, classDescr):
@ -560,13 +561,13 @@ class ToolClassDescriptor(ClassDescriptor):
if classDescr.isRoot() or issubclass(classDescr.klass, ModelClass): if classDescr.isRoot() or issubclass(classDescr.klass, ModelClass):
defaultValue = True defaultValue = True
fieldName = 'showWorkflowFor%s' % className fieldName = 'showWorkflowFor%s' % className
fieldType = Boolean(default=defaultValue, page='userInterface', fieldType = gen.Boolean(default=defaultValue, page='userInterface',
group=groupName) group=groupName)
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)
# Adds the boolean field for showing or not the field "enter comments". # Adds the boolean field for showing or not the field "enter comments".
fieldName = 'showWorkflowCommentFieldFor%s' % className fieldName = 'showWorkflowCommentFieldFor%s' % className
fieldType = Boolean(default=defaultValue, page='userInterface', fieldType = gen.Boolean(default=defaultValue, page='userInterface',
group=groupName) group=groupName)
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)
# Adds the boolean field for showing all states in current state or not. # Adds the boolean field for showing all states in current state or not.
# If this boolean is True but the current phase counts only one state, # If this boolean is True but the current phase counts only one state,
@ -577,13 +578,12 @@ class ToolClassDescriptor(ClassDescriptor):
if len(classDescr.getPhases()) > 1: if len(classDescr.getPhases()) > 1:
defaultValue = True defaultValue = True
fieldName = 'showAllStatesInPhaseFor%s' % className fieldName = 'showAllStatesInPhaseFor%s' % className
fieldType = Boolean(default=defaultValue, page='userInterface', fieldType = gen.Boolean(default=defaultValue, page='userInterface',
group=groupName) group=groupName)
self.addField(fieldName, fieldType) self.addField(fieldName, fieldType)
class UserClassDescriptor(ClassDescriptor): class UserClassDescriptor(ClassDescriptor):
'''Represents an Archetypes-compliant class that corresponds to the User '''Appy-specific class for representing a user.'''
for the generated application.'''
def __init__(self, klass, generator): def __init__(self, klass, generator):
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator) ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
self.modelClass = self.klass self.modelClass = self.klass
@ -649,8 +649,8 @@ class TranslationClassDescriptor(ClassDescriptor):
def addLabelField(self, messageId, page): def addLabelField(self, messageId, page):
'''Adds a Computed field that will display, in the source language, the '''Adds a Computed field that will display, in the source language, the
content of the text to translate.''' 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') page=page, show=self.modelClass.show, layouts='f')
self.addField('%s_label' % messageId, field) self.addField('%s_label' % messageId, field)
def addMessageField(self, messageId, page, i18nFiles): def addMessageField(self, messageId, page, i18nFiles):
@ -683,7 +683,7 @@ class TranslationClassDescriptor(ClassDescriptor):
params['width'] = width params['width'] = width
else: else:
# This is a multi-line field, or a very-long-single-lined field # 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 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 os, os.path, re, sys, parser, symbol, token, types
import appy.pod, appy.pod.renderer import appy.pod, appy.pod.renderer
from appy.shared.utils import FolderDeleter from appy.shared.utils import FolderDeleter
#from appy.gen import * import appy.gen as gen
from po import PoMessage, PoFile, PoParser from po import PoMessage, PoFile, PoParser
from descriptors import * from descriptors import *
from utils import produceNiceMessage, getClassName from utils import produceNiceMessage, getClassName
@ -140,7 +140,7 @@ class Generator:
self.user = None self.user = None
self.workflows = [] self.workflows = []
self.initialize() self.initialize()
self.config = Config.getDefault() self.config = gen.Config.getDefault()
self.modulesWithTests = set() self.modulesWithTests = set()
self.totalNumberOfTests = 0 self.totalNumberOfTests = 0
@ -152,9 +152,9 @@ class Generator:
workflow.''' workflow.'''
res = 'none' res = 'none'
for attrValue in klass.__dict__.itervalues(): for attrValue in klass.__dict__.itervalues():
if isinstance(attrValue, Type): if isinstance(attrValue, gen.Type):
res = 'class' res = 'class'
elif isinstance(attrValue, State): elif isinstance(attrValue, gen.State):
res = 'workflow' res = 'workflow'
if not res: if not res:
for baseClass in klass.__bases__: for baseClass in klass.__bases__:
@ -219,13 +219,13 @@ class Generator:
attrs = astClasses[moduleElem.__name__].attributes attrs = astClasses[moduleElem.__name__].attributes
if appyType == 'class': if appyType == 'class':
# Determine the class type (standard, tool, user...) # Determine the class type (standard, tool, user...)
if issubclass(moduleElem, Tool): if issubclass(moduleElem, gen.Tool):
if not self.tool: if not self.tool:
klass = self.descriptorClasses['tool'] klass = self.descriptorClasses['tool']
self.tool = klass(moduleElem, attrs, self) self.tool = klass(moduleElem, attrs, self)
else: else:
self.tool.update(moduleElem, attrs) self.tool.update(moduleElem, attrs)
elif issubclass(moduleElem, User): elif issubclass(moduleElem, gen.User):
if not self.user: if not self.user:
klass = self.descriptorClasses['user'] klass = self.descriptorClasses['user']
self.user = klass(moduleElem, attrs, self) self.user = klass(moduleElem, attrs, self)
@ -244,7 +244,7 @@ class Generator:
self.workflows.append(descriptor) self.workflows.append(descriptor)
if self.containsTests(moduleElem): if self.containsTests(moduleElem):
self.modulesWithTests.add(moduleObj.__name__) self.modulesWithTests.add(moduleObj.__name__)
elif isinstance(moduleElem, Config): elif isinstance(moduleElem, gen.Config):
self.config = moduleElem self.config = moduleElem
# Walk potential sub-modules # Walk potential sub-modules
@ -461,7 +461,6 @@ class ZopeGenerator(Generator):
self.generateTool() self.generateTool()
self.generateInit() self.generateInit()
self.generateTests() self.generateTests()
self.generateConfigureZcml()
# Create version.txt # Create version.txt
f = open(os.path.join(self.outputFolder, 'version.txt'), 'w') f = open(os.path.join(self.outputFolder, 'version.txt'), 'w')
f.write(self.version) f.write(self.version)
@ -536,13 +535,13 @@ class ZopeGenerator(Generator):
self.generateWrappers() self.generateWrappers()
self.generateConfig() 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 '''Produces a list of all the roles used within all workflows and
classes defined in this application. 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; 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 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" 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 wfDescr in self.workflows:
for attr in dir(wfDescr.klass): for attr in dir(wfDescr.klass):
attrValue = getattr(wfDescr.klass, attr) attrValue = getattr(wfDescr.klass, attr)
if isinstance(attrValue, State) or \ if isinstance(attrValue, gen.State) or \
isinstance(attrValue, Transition): isinstance(attrValue, gen.Transition):
for role in attrValue.getUsedRoles(): for role in attrValue.getUsedRoles():
if role.name not in allRoles: if role.name not in allRoles:
allRoles[role.name] = role allRoles[role.name] = role
@ -569,7 +568,7 @@ class ZopeGenerator(Generator):
allRoles[role.name] = role allRoles[role.name] = role
res = allRoles.values() res = allRoles.values()
# Filter the result according to parameters # Filter the result according to parameters
for p in ('plone', 'local', 'grantable'): for p in ('zope', 'local', 'grantable'):
if eval(p) != None: if eval(p) != None:
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
@ -613,17 +612,6 @@ class ZopeGenerator(Generator):
res = configClasses res = configClasses
return res 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): def generateConfig(self):
repls = self.repls.copy() repls = self.repls.copy()
# Get some lists of classes # Get some lists of classes
@ -677,9 +665,9 @@ class ZopeGenerator(Generator):
attributes.append('"%s":[%s]' % (classDescr.name, ','.join(qNames))) attributes.append('"%s":[%s]' % (classDescr.name, ','.join(qNames)))
repls['attributes'] = ',\n '.join(attributes) repls['attributes'] = ',\n '.join(attributes)
# Compute list of used roles for registering them if needed # 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]) 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]) repls['gRoles'] = ','.join(['"%s"' % r.name for r in globalRoles])
grantableRoles = self.getAllUsedRoles(local=False, grantable=True) grantableRoles = self.getAllUsedRoles(local=False, grantable=True)
repls['grRoles'] = ','.join(['"%s"' % r.name for r in grantableRoles]) repls['grRoles'] = ','.join(['"%s"' % r.name for r in grantableRoles])
@ -780,7 +768,7 @@ class ZopeGenerator(Generator):
self.copyFile('testAll.py', repls, destFolder='tests') self.copyFile('testAll.py', repls, destFolder='tests')
def generateTool(self): def generateTool(self):
'''Generates the Plone tool that corresponds to this application.''' '''Generates the tool that corresponds to this application.'''
Msg = PoMessage Msg = PoMessage
# Create Tool-related i18n-related messages # Create Tool-related i18n-related messages
msg = Msg(self.tool.name, '', Msg.CONFIG % self.applicationName) msg = Msg(self.tool.name, '', Msg.CONFIG % self.applicationName)
@ -874,7 +862,7 @@ class ZopeGenerator(Generator):
poMsg.produceNiceDefault() poMsg.produceNiceDefault()
if poMsg not in self.labels: if poMsg not in self.labels:
self.labels.append(poMsg) self.labels.append(poMsg)
# Generate the resulting Archetypes class. # Generate the resulting Zope class.
self.copyFile('Class.py', repls, destName=fileName) self.copyFile('Class.py', repls, destName=fileName)
def generateWorkflow(self, wfDescr): def generateWorkflow(self, wfDescr):
@ -886,14 +874,14 @@ class ZopeGenerator(Generator):
wfName = WorkflowDescriptor.getWorkflowName(wfDescr.klass) wfName = WorkflowDescriptor.getWorkflowName(wfDescr.klass)
# Add i18n messages for states # Add i18n messages for states
for name in dir(wfDescr.klass): 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 = PoMessage('%s_%s' % (wfName, name), '', name)
poMsg.produceNiceDefault() poMsg.produceNiceDefault()
self.labels.append(poMsg) self.labels.append(poMsg)
# Add i18n messages for transitions # Add i18n messages for transitions
for name in dir(wfDescr.klass): for name in dir(wfDescr.klass):
transition = getattr(wfDescr.klass, name) 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 = PoMessage('%s_%s' % (wfName, name), '', name)
poMsg.produceNiceDefault() poMsg.produceNiceDefault()
self.labels.append(poMsg) self.labels.append(poMsg)

View file

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

View file

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

View file

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

View file

@ -6,9 +6,9 @@ from appy.shared.data import languages
import appy.gen import appy.gen
from appy.gen import Type, Search, Selection from appy.gen import Type, Search, Selection
from appy.gen.utils import SomeObjects, sequenceTypes, getClassName from appy.gen.utils import SomeObjects, sequenceTypes, getClassName
from appy.gen.plone25.mixins import BaseMixin from appy.gen.mixins import BaseMixin
from appy.gen.plone25.wrappers import AbstractWrapper from appy.gen.wrappers import AbstractWrapper
from appy.gen.plone25.descriptors import ClassDescriptor from appy.gen.descriptors import ClassDescriptor
try: try:
from AccessControl.ZopeSecurityPolicy import _noroles from AccessControl.ZopeSecurityPolicy import _noroles
except ImportError: except ImportError:
@ -411,7 +411,7 @@ class ToolMixin(BaseMixin):
def getCreateMeans(self, contentTypeOrAppyClass): def getCreateMeans(self, contentTypeOrAppyClass):
'''Gets the different ways objects of p_contentTypeOrAppyClass (which '''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 (via a web form, by importing external data, etc). Result is a
dict whose keys are strings (ie "form", "import"...) and whose dict whose keys are strings (ie "form", "import"...) and whose
values are additional data bout the particular mean.''' 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'} 9: 'month_sep', 10: 'month_oct', 11: 'month_nov', 12: 'month_dec'}
def getMonthName(self, monthNumber): def getMonthName(self, monthNumber):
'''Gets the translated month name of month numbered p_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 # Authentication-related methods
@ -824,7 +824,7 @@ class ToolMixin(BaseMixin):
if jsEnabled and not cookiesEnabled: if jsEnabled and not cookiesEnabled:
msg = self.translate(u'You must enable cookies before you can ' \ 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')) return self.goto(urlBack, msg.encode('utf-8'))
# Perform the Zope-level authentication # Perform the Zope-level authentication
login = rq.get('__ac_name', '') login = rq.get('__ac_name', '')
@ -835,11 +835,10 @@ class ToolMixin(BaseMixin):
user = self.acl_users.validate(rq) user = self.acl_users.validate(rq)
if self.userIsAnon(): if self.userIsAnon():
rq.RESPONSE.expireCookie('__ac', path='/') 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 logMsg = 'Authentication failed (tried with login "%s")' % login
else: else:
msg = self.translate(u'Welcome! You are now logged in.', msg = self.translate(u'Welcome! You are now logged in.')
domain='plone')
logMsg = 'User "%s" has been logged in.' % login logMsg = 'User "%s" has been logged in.' % login
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self.log(logMsg) self.log(logMsg)
@ -865,7 +864,7 @@ class ToolMixin(BaseMixin):
session.invalidate() session.invalidate()
self.log('User "%s" has been logged out.' % userId) self.log('User "%s" has been logged out.' % userId)
# Remove user from variable "loggedUsers" # 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] if loggedUsers.has_key(userId): del loggedUsers[userId]
return self.goto(self.getApp().absolute_url()) 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: '''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.''' - mixins/ToolMixin is mixed in with the generated application Tool class.'''
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import os, os.path, sys, types, mimetypes, urllib, cgi import os, os.path, sys, types, mimetypes, urllib, cgi
from appy import Object from appy import Object
import appy.gen import appy.gen as gen
from appy.gen import Type, String, Selection, Role, No, WorkflowAnonymous, \
Transition, Permission
from appy.gen.utils import * from appy.gen.utils import *
from appy.gen.layout import Table, defaultPageLayouts from appy.gen.layout import Table, defaultPageLayouts
from appy.gen.descriptors import WorkflowDescriptor from appy.gen.descriptors import WorkflowDescriptor, ClassDescriptor
from appy.gen.plone25.descriptors import ClassDescriptor
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class BaseMixin: class BaseMixin:
@ -187,8 +184,7 @@ class BaseMixin:
fields in the database.''' fields in the database.'''
rq = self.REQUEST rq = self.REQUEST
tool = self.getTool() tool = self.getTool()
errorMessage = self.translate( errorMessage = self.translate('Please correct the indicated errors.')
'Please correct the indicated errors.', domain='plone')
isNew = rq.get('is_new') == 'True' isNew = rq.get('is_new') == 'True'
# If this object is created from an initiator, get info about him. # If this object is created from an initiator, get info about him.
initiator = None initiator = None
@ -209,7 +205,7 @@ class BaseMixin:
urlBack = tool.getSiteUrl() urlBack = tool.getSiteUrl()
else: else:
urlBack = self.getUrl() urlBack = self.getUrl()
self.say(self.translate('Changes canceled.', domain='plone')) self.say(self.translate('Changes canceled.'))
return self.goto(urlBack) return self.goto(urlBack)
# Object for storing validation errors # Object for storing validation errors
@ -245,7 +241,7 @@ class BaseMixin:
obj, msg = self.createOrUpdate(isNew, values, initiator, initiatorField) obj, msg = self.createOrUpdate(isNew, values, initiator, initiatorField)
# Redirect the user to the appropriate page # 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 # 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 # object like a one-shot form and has already been deleted in method
# onEdit), redirect to the main site page. # onEdit), redirect to the main site page.
@ -711,7 +707,7 @@ class BaseMixin:
if not includeFake: if not includeFake:
includeIt = mayTrigger includeIt = mayTrigger
else: else:
includeIt = mayTrigger or isinstance(mayTrigger, No) includeIt = mayTrigger or isinstance(mayTrigger, gen.No)
if not includeNotShowable: if not includeNotShowable:
includeIt = includeIt and transition.isShowable(wf, self) includeIt = includeIt and transition.isShowable(wf, self)
if not includeIt: continue if not includeIt: continue
@ -864,7 +860,7 @@ class BaseMixin:
# Get the initial workflow state # Get the initial workflow state
initialState = self.State(name=False) initialState = self.State(name=False)
# Create a Transition instance representing the initial transition. # Create a Transition instance representing the initial transition.
initialTransition = Transition((initialState, initialState)) initialTransition = gen.Transition((initialState, initialState))
initialTransition.trigger('_init_', self, wf, '') initialTransition.trigger('_init_', self, wf, '')
def getWorkflow(self, name=False, className=None): def getWorkflow(self, name=False, className=None):
@ -875,7 +871,7 @@ class BaseMixin:
else: else:
appyClass = self.getTool().getAppyClass(className) appyClass = self.getTool().getAppyClass(className)
if hasattr(appyClass, 'workflow'): wf = appyClass.workflow if hasattr(appyClass, 'workflow'): wf = appyClass.workflow
else: wf = WorkflowAnonymous else: wf = gen.WorkflowAnonymous
if not name: return wf if not name: return wf
return WorkflowDescriptor.getWorkflowName(wf) return WorkflowDescriptor.getWorkflowName(wf)

View file

@ -1,14 +1,10 @@
'''This file contains basic classes that will be added into any user '''This file contains basic classes that will be added into any user
application for creating the basic structure of the application "Tool" which 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 is the set of web pages used for configuring the application.'''
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.)'''
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import types import types
from appy.gen import * import appy.gen as gen
Grp=Group # Avoid name clash between appy.gen.Group and class Group below
# Prototypical instances of every type ----------------------------------------- # Prototypical instances of every type -----------------------------------------
class Protos: class Protos:
@ -73,7 +69,7 @@ class ModelClass:
value = appyType.getInputLayouts() value = appyType.getInputLayouts()
elif isinstance(value, basestring): elif isinstance(value, basestring):
value = '"%s"' % value value = '"%s"' % value
elif isinstance(value, Ref): elif isinstance(value, gen.Ref):
if not value.isBack: continue if not value.isBack: continue
value = klass._appy_getTypeBody(value, wrapperName) value = klass._appy_getTypeBody(value, wrapperName)
elif type(value) == type(ModelClass): elif type(value) == type(ModelClass):
@ -82,11 +78,11 @@ class ModelClass:
value = value.__name__ value = value.__name__
else: else:
value = '%s.%s' % (moduleName, value.__name__) value = '%s.%s' % (moduleName, value.__name__)
elif isinstance(value, Selection): elif isinstance(value, gen.Selection):
value = 'Selection("%s")' % value.methodName value = 'Selection("%s")' % value.methodName
elif isinstance(value, Grp): elif isinstance(value, gen.Group):
value = 'Grp("%s")' % value.name value = 'Grp("%s")' % value.name
elif isinstance(value, Page): elif isinstance(value, gen.Page):
value = 'pages["%s"]' % value.name value = 'pages["%s"]' % value.name
elif callable(value): elif callable(value):
value = '%s.%s' % (wrapperName, value.__name__) value = '%s.%s' % (wrapperName, value.__name__)
@ -135,20 +131,22 @@ class User(ModelClass):
_appy_attributes = ['title', 'name', 'firstName', 'login', 'password1', _appy_attributes = ['title', 'name', 'firstName', 'login', 'password1',
'password2', 'roles'] 'password2', 'roles']
# All methods defined below are fake. Real versions are in the wrapper. # 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} gm = {'group': 'main', 'multiplicity': (1,1), 'width': 25}
name = String(**gm) name = gen.String(**gm)
firstName = String(**gm) firstName = gen.String(**gm)
def showLogin(self): pass def showLogin(self): pass
def validateLogin(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 showPassword(self): pass
def validatePassword(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) 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) 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 -------------------------------------------------------------- # The Group class --------------------------------------------------------------
class Group(ModelClass): class Group(ModelClass):
@ -156,25 +154,25 @@ class Group(ModelClass):
_appy_attributes = ['title', 'login', 'roles', 'users'] _appy_attributes = ['title', 'login', 'roles', 'users']
# All methods defined below are fake. Real versions are in the wrapper. # All methods defined below are fake. Real versions are in the wrapper.
m = {'group': 'main', 'width': 25, 'indexed': True} 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 showLogin(self): pass
def validateLogin(self): pass def validateLogin(self): pass
login = String(show=showLogin, validator=validateLogin, login = gen.String(show=showLogin, validator=validateLogin,
multiplicity=(1,1), **m) multiplicity=(1,1), **m)
roles = String(validator=Selection('getGrantableRoles'), roles = gen.String(validator=gen.Selection('getGrantableRoles'),
multiplicity=(0,None), **m) multiplicity=(0,None), **m)
users = Ref(User, multiplicity=(0,None), add=False, link=True, users = gen.Ref(User, multiplicity=(0,None), add=False, link=True,
back=Ref(attribute='groups', show=True), back=gen.Ref(attribute='groups', show=True),
showHeaders=True, shownInfo=('title', 'login')) showHeaders=True, shownInfo=('title', 'login'))
# The Translation class -------------------------------------------------------- # The Translation class --------------------------------------------------------
class Translation(ModelClass): class Translation(ModelClass):
_appy_attributes = ['po', 'title'] _appy_attributes = ['po', 'title']
# All methods defined below are fake. Real versions are in the wrapper. # All methods defined below are fake. Real versions are in the wrapper.
def getPoFile(self): pass 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') result='filetmp')
title = String(show=False, indexed=True) title = gen.String(show=False, indexed=True)
def label(self): pass def label(self): pass
def show(self, name): pass def show(self, name): pass
@ -195,30 +193,31 @@ class Tool(ModelClass):
# Tool attributes # Tool attributes
def validPythonWithUno(self, value): pass # Real method in the wrapper def validPythonWithUno(self, value): pass # Real method in the wrapper
unoEnabledPython = String(group="connectionToOpenOffice", unoEnabledPython = gen.String(group="connectionToOpenOffice",
validator=validPythonWithUno) validator=validPythonWithUno)
openOfficePort = Integer(default=2002, group="connectionToOpenOffice") openOfficePort = gen.Integer(default=2002, group="connectionToOpenOffice")
numberOfResultsPerPage = Integer(default=30, show=False) numberOfResultsPerPage = gen.Integer(default=30, show=False)
listBoxesMaximumWidth = Integer(default=100, show=False) listBoxesMaximumWidth = gen.Integer(default=100, show=False)
appyVersion = String(show=False, layouts='f') appyVersion = gen.String(show=False, layouts='f')
def refreshSecurity(self): pass # Real method in the wrapper 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). # Ref(User) will maybe be transformed into Ref(CustomUserClass).
users = Ref(User, multiplicity=(0,None), add=True, link=False, users = gen.Ref(User, multiplicity=(0,None), add=True, link=False,
back=Ref(attribute='toTool', show=False), back=gen.Ref(attribute='toTool', show=False),
page=Page('users', show='view'), page=gen.Page('users', show='view'),
queryable=True, queryFields=('title', 'login'), queryable=True, queryFields=('title', 'login'),
showHeaders=True, shownInfo=('title', 'login', 'roles')) showHeaders=True, shownInfo=('title', 'login', 'roles'))
groups = Ref(Group, multiplicity=(0,None), add=True, link=False, groups = gen.Ref(Group, multiplicity=(0,None), add=True, link=False,
back=Ref(attribute='toTool2', show=False), back=gen.Ref(attribute='toTool2', show=False),
page=Page('groups', show='view'), page=gen.Page('groups', show='view'),
queryable=True, queryFields=('title', 'login'), queryable=True, queryFields=('title', 'login'),
showHeaders=True, shownInfo=('title', 'login', 'roles')) showHeaders=True, shownInfo=('title', 'login', 'roles'))
translations = Ref(Translation, multiplicity=(0,None),add=False,link=False, translations = gen.Ref(Translation, multiplicity=(0,None), add=False,
back=Ref(attribute='trToTool', show=False), show='view', link=False, show='view',
page=Page('translations', show='view')) back=gen.Ref(attribute='trToTool', show=False),
enableNotifications = Boolean(default=True, page=gen.Page('translations', show='view'))
page=Page('notifications', show=False)) enableNotifications = gen.Boolean(default=True,
page=gen.Page('notifications', show=False))
@classmethod @classmethod
def _appy_clean(klass): 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 '''Sends mail about p_transition that has been triggered on p_obj that is
controlled by p_workflow.''' controlled by p_workflow.'''
wfName = WorkflowDescriptor.getWorkflowName(workflow.__class__) wfName = WorkflowDescriptor.getWorkflowName(workflow.__class__)
ploneObj = obj.o zopeObj = obj.o
portal = ploneObj.portal_url.getPortalObject() tool = zopeObj.getTool()
mailInfo = transition.notify(workflow, obj) mailInfo = transition.notify(workflow, obj)
if not mailInfo[0]: return # Send a mail to nobody. if not mailInfo[0]: return # Send a mail to nobody.
# mailInfo may be one of the following: # 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. # address or one role) or sequences of strings.
# Determine mail subject and body. # Determine mail subject and body.
if len(mailInfo) <= 2: if len(mailInfo) <= 2:
# The user didn't mention mail body and subject. We will use # The user didn't mention mail body and subject. We will use those
# those defined from i18n labels. # defined from i18n labels.
wfHistory = ploneObj.getWorkflowHistory() wfHistory = zopeObj.getHistory()
labelPrefix = '%s_%s' % (wfName, transitionName) labelPrefix = '%s_%s' % (wfName, transitionName)
tName = obj.translate(labelPrefix) tName = obj.translate(labelPrefix)
keys = {'siteUrl': portal.absolute_url(), keys = {'siteUrl': tool.getPath('/').absolute_url(),
'siteTitle': portal.Title(), 'siteTitle': tool.getAppName(),
'objectUrl': ploneObj.absolute_url(), 'objectUrl': zopeObj.absolute_url(),
'objectTitle': ploneObj.Title(), 'objectTitle': zopeObj.Title(),
'transitionName': tName, 'transitionName': tName,
'transitionComment': wfHistory[0]['comments']} 'transitionComment': wfHistory[0]['comments']}
mailSubject = obj.translate(labelPrefix + '_mail_subject', keys) 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 appy.gen.utils import createObject
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
import Products.<!applicationName!>.config as cfg import Products.<!applicationName!>.config as cfg
from appy.gen.plone25.mixins import BaseMixin from appy.gen.mixins import BaseMixin
from appy.gen.plone25.mixins.ToolMixin import ToolMixin from appy.gen.mixins.ToolMixin import ToolMixin
from wrappers import <!genClassName!>_Wrapper as Wrapper from wrappers import <!genClassName!>_Wrapper as Wrapper
def manage_add<!genClassName!>(self, id, title='', REQUEST=None): 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!> <!codeHeader!>
# Test coverage-related stuff -------------------------------------------------- # Test coverage-related stuff --------------------------------------------------
import sys import sys
from appy.gen.plone25.mixins.TestMixin import TestMixin from appy.gen.mixins.TestMixin import TestMixin
covFolder = TestMixin.getCovFolder() covFolder = TestMixin.getCovFolder()
# The previous method checks in sys.argv whether Zope was lauched for performing # The previous method checks in sys.argv whether Zope was lauched for performing
# coverage tests or not. # coverage tests or not.
@ -26,7 +26,7 @@ def countTest():
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import config import config
from appy.gen.plone25.installer import ZopeInstaller from appy.gen.installer import ZopeInstaller
# Zope-level installation of the generated product. ---------------------------- # Zope-level installation of the generated product. ----------------------------
def initialize(context): def initialize(context):

View file

@ -5,9 +5,7 @@ import wrappers
<!imports!> <!imports!>
# The following imports are here for allowing mixin classes to access those # The following imports are here for allowing mixin classes to access those
# elements without being statically dependent on Plone/Zope packages. Indeed, # elements without being statically dependent on Zope packages.
# every Archetype instance has a method "getProductConfig" that returns this
# module.
from persistent.list import PersistentList from persistent.list import PersistentList
from zExceptions import BadRequest from zExceptions import BadRequest
from ZPublisher.HTTPRequest import BaseRequest 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 unittest import TestSuite
from Testing import ZopeTestCase from Testing import ZopeTestCase
from Testing.ZopeTestCase import ZopeDocTestSuite from Testing.ZopeTestCase import ZopeDocTestSuite
from Products.PloneTestCase import PloneTestCase from appy.gen.mixins.TestMixin import TestMixin, beforeTest, afterTest
from appy.gen.plone25.mixins.TestMixin import TestMixin, beforeTest, afterTest
<!imports!> <!imports!>
# Initialize Zope & Plone test systems ----------------------------------------- # Initialize the Zope test system ----------------------------------------------
ZopeTestCase.installProduct('PloneLanguageTool')
ZopeTestCase.installProduct('<!applicationName!>') 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.''' '''Base test class for <!applicationName!> test cases.'''
# Data needed for defining the tests ------------------------------------------- # Data needed for defining the tests -------------------------------------------

View file

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

View file

@ -1,23 +1,9 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" <tal:main define="tool context/config">
lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal" <html metal:use-macro="context/ui/template/macros/main">
xmlns:metal="http://xml.zope.org/namespaces/metal" <metal:fill fill-slot="content"
xmlns:i18n="http://xml.zope.org/namespaces/i18n" tal:define="className request/className;
metal:use-macro="here/main_template/macros/master"> importElems python: tool.getImportElements(className);
global allAreImported python:True">
<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());
importElems python: tool.getImportElements(className);
global allAreImported python:True">
<div metal:use-macro="context/ui/page/macros/prologue"/> <div metal:use-macro="context/ui/page/macros/prologue"/>
<script language="javascript"> <script language="javascript">
@ -68,7 +54,7 @@
</script> </script>
<tal:comment replace="nothing">Form for importing several meetings at once.</tal:comment> <tal:comment replace="nothing">Form for importing several meetings at once.</tal:comment>
<form name="importElements" <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="action" value="ImportObjects"/>
<input type="hidden" name="className" tal:attributes="value className"/> <input type="hidden" name="className" tal:attributes="value className"/>
<input type="hidden" name="importPath" value=""/> <input type="hidden" name="importPath" value=""/>
@ -120,6 +106,6 @@
tal:condition="python: importElems[1] and not allAreImported" tal:condition="python: importElems[1] and not allAreImported"
tal:attributes="value python:tool.translate('import_many')"/> tal:attributes="value python:tool.translate('import_many')"/>
</p> </p>
</metal:fill> </metal:fill>
</body>
</html> </html>
</tal:main>

View file

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

View file

@ -117,7 +117,7 @@
tal:content="structure python: _(label)"></a> tal:content="structure python: _(label)"></a>
</td> </td>
<td align="right"> <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); tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=pageName);
src string: $appUrl/ui/edit.gif" src string: $appUrl/ui/edit.gif"
tal:condition="python: contextObj.allows('Modify portal content') and phase['pagesInfo'][pageName]['showOnEdit']"/> tal:condition="python: contextObj.allows('Modify portal content') and phase['pagesInfo'][pageName]['showOnEdit']"/>
@ -136,7 +136,7 @@
</a> </a>
</td> </td>
<td align="right"> <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); tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=aPage);
src string: $appUrl/ui/edit.gif" src string: $appUrl/ui/edit.gif"
tal:condition="python: user.has_permission('Modify portal content', contextObj) and phase['pagesInfo'][aPage]['showOnEdit']"/> 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);" <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:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)"
tal:condition="python: obj.allows('Modify portal content')"> tal:condition="python: obj.allows('Modify portal content')">
<img title="Edit" i18n:domain="plone" i18n:attributes="title" <img title="Edit" tal:attributes="src string: $appUrl/ui/edit.gif"/>
tal:attributes="src string: $appUrl/ui/edit.gif"/>
</a></td> </a></td>
<tal:comment replace="nothing">Delete the element</tal:comment> <tal:comment replace="nothing">Delete the element</tal:comment>
<td> <td>
<img tal:condition="python: obj.allows('Delete objects') and obj.mayDelete()" <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; tal:attributes="src string: $appUrl/ui/delete.png;
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/> onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>
</td> </td>

View file

@ -14,10 +14,7 @@
onClick python: 'askConfirm(\'form\', \'%s\', &quot;%s&quot;)' % (formId, labelConfirm)"/> onClick python: 'askConfirm(\'form\', \'%s\', &quot;%s&quot;)' % (formId, labelConfirm)"/>
</tal:confirm> </tal:confirm>
<input type="submit" name="do" tal:condition="not: widget/confirm" <input type="submit" name="do" tal:condition="not: widget/confirm"
tal:attributes="value label" onClick="javascript:;"/> tal:attributes="value label"/>
<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>
</form> </form>
</metal:view> </metal:view>

View file

@ -29,11 +29,11 @@
<label tal:attributes="for widgetName" tal:content="python: tool.translate(widget['labelId'])"></label><br>&nbsp;&nbsp; <label tal:attributes="for widgetName" tal:content="python: tool.translate(widget['labelId'])"></label><br>&nbsp;&nbsp;
<tal:yes define="valueId python:'%s_yes' % name"> <tal:yes define="valueId python:'%s_yes' % name">
<input type="radio" value="True" tal:attributes="name typedWidget; id valueId"/> <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:yes>
<tal:no define="valueId python:'%s_no' % name"> <tal:no define="valueId python:'%s_no' % name">
<input type="radio" value="False" tal:attributes="name typedWidget; id valueId"/> <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:no>
<tal:whatever define="valueId python:'%s_whatever' % name"> <tal:whatever define="valueId python:'%s_whatever' % name">
<input type="radio" value="" tal:attributes="name typedWidget; id valueId" checked="checked"/> <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/> <metal:call use-macro="app/ui/widgets/file/macros/view"/><br/>
</tal:showFile> </tal:showFile>
<tal:editButtons condition="not: empty"> <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" <input type="radio" value="nochange"
tal:attributes="checked python:test(info['size']!=0, 'checked', None); tal:attributes="checked python:test(info['size']!=0, 'checked', None);
name string:${name}_delete; name string:${name}_delete;
id string:${name}_nochange; id string:${name}_nochange;
onclick string:document.getElementById('${name}_file').disabled=true;"/> onclick string:document.getElementById('${name}_file').disabled=true;"/>
<label tal:attributes="for string:${name}_nochange" <label tal:attributes="for string:${name}_nochange">Keep the file unchanged</label>
i18n:translate="nochange_file" i18n:domain="plone">Keep the file unchanged</label>
<br/> <br/>
<tal:comment replace="nothing">Delete the file.</tal:comment> <tal:comment replace="nothing">Delete the file.</tal:comment>
<tal:delete condition="not: widget/required"> <tal:delete condition="not: widget/required">
@ -39,8 +38,7 @@
tal:attributes="name string:${name}_delete; tal:attributes="name string:${name}_delete;
id string:${name}_delete; id string:${name}_delete;
onclick string:document.getElementById('${name}_file').disabled=true;"/> onclick string:document.getElementById('${name}_file').disabled=true;"/>
<label tal:attributes="for string:${name}_delete" <label tal:attributes="for string:${name}_delete">Delete the file</label>
i18n:translate="delete_file" i18n:domain="plone">Delete the file</label>
<br/> <br/>
</tal:delete> </tal:delete>
<tal:comment replace="nothing">Replace with a new file.</tal:comment> <tal:comment replace="nothing">Replace with a new file.</tal:comment>
@ -49,8 +47,7 @@
name string:${name}_delete; name string:${name}_delete;
id string:${name}_upload; id string:${name}_upload;
onclick string:document.getElementById('${name}_file').disabled=false"/> onclick string:document.getElementById('${name}_file').disabled=false"/>
<label tal:attributes="for string:${name}_upload;" <label tal:attributes="for string:${name}_upload;">Replace it with a new file</label>
i18n:translate="upload_file" i18n:domain="plone">Replace it with a new file</label>
<br/> <br/>
</tal:editButtons> </tal:editButtons>
<tal:comment replace="nothing">The upload field.</tal:comment> <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']"> <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);" <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)"> tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)">
<img title="label_edit" i18n:domain="plone" i18n:attributes="title" <img title="Edit" tal:attributes="src string: $appUrl/ui/edit.gif"/>
tal:attributes="src string: $appUrl/ui/edit.gif"/>
</a> </a>
</td> </td>
<tal:comment replace="nothing">Delete the element</tal:comment> <tal:comment replace="nothing">Delete the element</tal:comment>
<td> <td>
<img tal:condition="python: not appyType['isBack'] and obj.allows('Delete objects') and obj.mayDelete()" <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; tal:attributes="src string: $appUrl/ui/delete.png;
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/> onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>
</td> </td>

View file

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

View file

@ -2,7 +2,7 @@
import os.path import os.path
import appy import appy
from appy.shared.utils import executeCommand 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 ' \ _PY = 'Please specify a file corresponding to a Python interpreter ' \

View file

@ -1,6 +1,6 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import os.path 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.gen.po import PoFile, PoMessage
from appy.shared.utils import getOsTempFolder 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): class UserWrapper(AbstractWrapper):