appy.gen: Refactoring due to De-Plonization.
This commit is contained in:
parent
d934f49a99
commit
c5a8968bd3
|
@ -3,18 +3,15 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import sys, os.path
|
||||
from optparse import OptionParser
|
||||
from appy.gen.generator import GeneratorError
|
||||
from appy.gen.generator import GeneratorError, ZopeGenerator
|
||||
from appy.shared.utils import LinesCounter
|
||||
import appy.version
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
ERROR_CODE = 1
|
||||
VALID_PRODUCT_TYPES = ('zope', 'odt')
|
||||
APP_NOT_FOUND = 'Application not found at %s.'
|
||||
WRONG_NG_OF_ARGS = 'Wrong number of arguments.'
|
||||
WRONG_OUTPUT_FOLDER = 'Output folder not found. Please create it first.'
|
||||
PRODUCT_TYPE_ERROR = 'Wrong product type. Product type may be one of the ' \
|
||||
'following: %s' % str(VALID_PRODUCT_TYPES)
|
||||
C_OPTION = 'Removes from i18n files all labels that are not automatically ' \
|
||||
'generated from your gen-application. It can be useful during ' \
|
||||
'development, when you do lots of name changes (classes, ' \
|
||||
|
@ -37,7 +34,7 @@ S_OPTION = 'Sorts all i18n labels. If you use this option, among the ' \
|
|||
'set of translation files.'
|
||||
|
||||
class GeneratorScript:
|
||||
'''usage: %prog [options] app productType outputFolder
|
||||
'''usage: %prog [options] app outputFolder
|
||||
|
||||
"app" is the path to your Appy application, which must be a
|
||||
Python package (= a folder containing a file named
|
||||
|
@ -47,44 +44,29 @@ class GeneratorScript:
|
|||
generated product, stored or symlinked in
|
||||
<yourZopeInstance>/Products.
|
||||
|
||||
"productType" is the kind of product you want to generate. "zope" is
|
||||
the only available production-ready target.
|
||||
"odt" is experimental.
|
||||
|
||||
"outputFolder" is the folder where the Zope product will be generated.
|
||||
For example, if you develop your application in
|
||||
/home/gdy/MyProject/MyProject, you typically specify
|
||||
"/home/gdy/MyProject/zope" as outputFolder.
|
||||
'''
|
||||
|
||||
def generateProduct(self, options, application, productType, outputFolder):
|
||||
if productType == 'odt':
|
||||
exec 'from appy.gen.odt.generator import Generator'
|
||||
else:
|
||||
from appy.gen.generator import ZopeGenerator as Generator
|
||||
Generator(application, outputFolder, options).run()
|
||||
|
||||
def manageArgs(self, parser, options, args):
|
||||
# Check number of args
|
||||
if len(args) != 3:
|
||||
if len(args) != 2:
|
||||
print WRONG_NG_OF_ARGS
|
||||
parser.print_help()
|
||||
sys.exit(ERROR_CODE)
|
||||
# Check productType
|
||||
if args[1] not in VALID_PRODUCT_TYPES:
|
||||
print PRODUCT_TYPE_ERROR
|
||||
sys.exit(ERROR_CODE)
|
||||
# Check existence of application
|
||||
if not os.path.exists(args[0]):
|
||||
print APP_NOT_FOUND % args[0]
|
||||
sys.exit(ERROR_CODE)
|
||||
# Check existence of outputFolder basic type
|
||||
if not os.path.exists(args[2]):
|
||||
# Check existence of outputFolder
|
||||
if not os.path.exists(args[1]):
|
||||
print WRONG_OUTPUT_FOLDER
|
||||
sys.exit(ERROR_CODE)
|
||||
# Convert all paths in absolute paths
|
||||
for i in (0,2):
|
||||
for i in (0,1):
|
||||
args[i] = os.path.abspath(args[i])
|
||||
|
||||
def run(self):
|
||||
optParser = OptionParser(usage=GeneratorScript.__doc__)
|
||||
optParser.add_option("-c", "--i18n-clean", action='store_true',
|
||||
|
@ -95,8 +77,8 @@ class GeneratorScript:
|
|||
try:
|
||||
self.manageArgs(optParser, options, args)
|
||||
print 'Appy version:', appy.version.verbose
|
||||
print 'Generating %s product in %s...' % (args[1], args[2])
|
||||
self.generateProduct(options, *args)
|
||||
print 'Generating Zope product in %s...' % args[1]
|
||||
ZopeGenerator(args[0], args[1], options).run()
|
||||
# Give the user some statistics about its code
|
||||
LinesCounter(args[0]).run()
|
||||
except GeneratorError, ge:
|
||||
|
|
|
@ -1026,8 +1026,7 @@ class String(Type):
|
|||
it will be given by the Appy validation machinery, so it must be
|
||||
specified as parameter. The function returns True if the check is
|
||||
successful.'''
|
||||
if not value: return True # Plone calls me erroneously for
|
||||
# non-mandatory fields.
|
||||
if not value: return True
|
||||
# First, remove any non-digit char
|
||||
v = ''
|
||||
for c in value:
|
||||
|
@ -1058,8 +1057,7 @@ class String(Type):
|
|||
'''Checks that p_value corresponds to a valid IBAN number. IBAN stands
|
||||
for International Bank Account Number (ISO 13616). If the number is
|
||||
valid, the method returns True.'''
|
||||
if not value: return True # Plone calls me erroneously for
|
||||
# non-mandatory fields.
|
||||
if not value: return True
|
||||
# First, remove any non-digit or non-letter char
|
||||
v = ''
|
||||
for c in value:
|
||||
|
@ -1088,8 +1086,7 @@ class String(Type):
|
|||
'''Checks that p_value corresponds to a valid BIC number. BIC stands
|
||||
for Bank Identifier Code (ISO 9362). If the number is valid, the
|
||||
method returns True.'''
|
||||
if not value: return True # Plone calls me erroneously for
|
||||
# non-mandatory fields.
|
||||
if not value: return True
|
||||
# BIC number must be 8 or 11 chars
|
||||
if len(value) not in (8, 11): return False
|
||||
# 4 first chars, representing bank name, must be letters
|
||||
|
@ -1176,15 +1173,7 @@ class String(Type):
|
|||
else: return value
|
||||
if isinstance(value, basestring) and self.isMultiValued():
|
||||
value = [value]
|
||||
# Some backward compatibilities with Archetypes.
|
||||
elif value.__class__.__name__ == 'BaseUnit':
|
||||
try:
|
||||
value = unicode(value)
|
||||
except UnicodeDecodeError:
|
||||
value = str(value)
|
||||
elif isinstance(value, tuple):
|
||||
# When Appy storage was based on Archetype, multivalued string
|
||||
# fields stored values as tuples of unicode strings.
|
||||
value = list(value)
|
||||
return value
|
||||
|
||||
|
@ -1207,12 +1196,6 @@ class String(Type):
|
|||
res = [t('%s_list_%s' % (self.labelId, v)) for v in value]
|
||||
else:
|
||||
res = t('%s_list_%s' % (self.labelId, value))
|
||||
elif not isinstance(value, basestring):
|
||||
# Archetypes "Description" fields may hold a BaseUnit instance.
|
||||
try:
|
||||
res = unicode(value)
|
||||
except UnicodeDecodeError:
|
||||
res = str(value)
|
||||
# If value starts with a carriage return, add a space; else, it will
|
||||
# be ignored.
|
||||
if isinstance(res, basestring) and \
|
||||
|
@ -1384,8 +1367,8 @@ class Boolean(Type):
|
|||
return value
|
||||
|
||||
def getFormattedValue(self, obj, value):
|
||||
if value: res = obj.translate('yes', domain='plone')
|
||||
else: res = obj.translate('no', domain='plone')
|
||||
if value: res = obj.translate('yes')
|
||||
else: res = obj.translate('no')
|
||||
return res
|
||||
|
||||
def getStorableValue(self, value):
|
||||
|
@ -1517,7 +1500,7 @@ class File(Type):
|
|||
|
||||
def getFormattedValue(self, obj, value):
|
||||
if not value: return value
|
||||
return value._atFile
|
||||
return value._zopeFile
|
||||
|
||||
def getRequestValue(self, request):
|
||||
return request.get('%s_file' % self.name)
|
||||
|
@ -1591,7 +1574,7 @@ class File(Type):
|
|||
elif isinstance(value, OFSImageFile):
|
||||
setattr(obj, self.name, value)
|
||||
elif isinstance(value, FileWrapper):
|
||||
setattr(obj, self.name, value._atFile)
|
||||
setattr(obj, self.name, value._zopeFile)
|
||||
elif isinstance(value, basestring):
|
||||
setattr(obj, self.name, File.getFileObject(value, zope=True))
|
||||
elif type(value) in sequenceTypes:
|
||||
|
@ -2189,7 +2172,7 @@ class Pod(Type):
|
|||
def store(self, obj, value):
|
||||
'''Stores (=freezes) a document (in p_value) in the field.'''
|
||||
if isinstance(value, FileWrapper):
|
||||
value = value._atFile
|
||||
value = value._zopeFile
|
||||
setattr(obj, self.name, value)
|
||||
|
||||
class List(Type):
|
||||
|
@ -2283,19 +2266,18 @@ appyToZopePermissions = {
|
|||
|
||||
class Role:
|
||||
'''Represents a role.'''
|
||||
ploneRoles = ('Manager', 'Member', 'Owner', 'Reviewer', 'Anonymous',
|
||||
'Authenticated')
|
||||
ploneLocalRoles = ('Owner',)
|
||||
ploneUngrantableRoles = ('Anonymous', 'Authenticated')
|
||||
zopeRoles = ('Manager', 'Owner', 'Anonymous', 'Authenticated')
|
||||
zopeLocalRoles = ('Owner',)
|
||||
zopeUngrantableRoles = ('Anonymous', 'Authenticated')
|
||||
def __init__(self, name, local=False, grantable=True):
|
||||
self.name = name
|
||||
self.local = local # True if it can be used as local role only.
|
||||
# It is a standard Plone role or an application-specific one?
|
||||
self.plone = name in self.ploneRoles
|
||||
if self.plone and (name in self.ploneLocalRoles):
|
||||
# It is a standard Zope role or an application-specific one?
|
||||
self.zope = name in self.zopeRoles
|
||||
if self.zope and (name in self.zopeLocalRoles):
|
||||
self.local = True
|
||||
self.grantable = grantable
|
||||
if self.plone and (name in self.ploneUngrantableRoles):
|
||||
if self.zope and (name in self.zopeUngrantableRoles):
|
||||
self.grantable = False
|
||||
# An ungrantable role is one that is, like the Anonymous or
|
||||
# Authenticated roles, automatically attributed to a user.
|
||||
|
@ -2575,8 +2557,7 @@ class Transition:
|
|||
# Return a message to the user if needed
|
||||
if not doSay or (transitionName == '_init_'): return
|
||||
if not msg:
|
||||
msg = obj.translate(u'Your content\'s status has been modified.',
|
||||
domain='plone')
|
||||
msg = obj.translate(u'Changes saved.')
|
||||
obj.say(msg)
|
||||
|
||||
class Permission:
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
# ------------------------------------------------------------------------------
|
||||
import types, copy
|
||||
from appy.gen import State, Transition, Type
|
||||
import appy.gen as gen
|
||||
from po import PoMessage
|
||||
from model import ModelClass, toolFieldPrefixes
|
||||
from utils import produceNiceMessage, getClassName
|
||||
|
@ -27,8 +27,7 @@ class ClassDescriptor(Descriptor):
|
|||
'''This class gives information about an Appy class.'''
|
||||
|
||||
def __init__(self, klass, orderedAttributes, generator):
|
||||
appy.gen.descriptors.ClassDescriptor.__init__(self, klass,
|
||||
orderedAttributes, generator)
|
||||
Descriptor.__init__(self, klass, orderedAttributes, generator)
|
||||
self.methods = '' # Needed method definitions will be generated here
|
||||
# We remember here encountered pages and groups defined in the Appy
|
||||
# type. Indeed, after having parsed all application classes, we will
|
||||
|
@ -70,7 +69,7 @@ class ClassDescriptor(Descriptor):
|
|||
except AttributeError:
|
||||
attrValue = getattr(self.modelClass, attrName)
|
||||
hookClass = self.modelClass
|
||||
if isinstance(attrValue, Type):
|
||||
if isinstance(attrValue, gen.Type):
|
||||
if not condition or eval(condition):
|
||||
attrs.append( (attrName, attrValue, hookClass) )
|
||||
# Then, add attributes from parent classes
|
||||
|
@ -142,7 +141,7 @@ class ClassDescriptor(Descriptor):
|
|||
attrValue = getattr(self.klass, attrName)
|
||||
except AttributeError:
|
||||
attrValue = getattr(self.modelClass, attrName)
|
||||
if isinstance(attrValue, Type):
|
||||
if isinstance(attrValue, gen.Type):
|
||||
if configClass:
|
||||
attrValue = copy.copy(attrValue)
|
||||
attrValue.optional = False
|
||||
|
@ -184,13 +183,13 @@ class ClassDescriptor(Descriptor):
|
|||
res = []
|
||||
if self.klass.__dict__.has_key('creators') and self.klass.creators:
|
||||
for creator in self.klass.creators:
|
||||
if isinstance(creator, Role):
|
||||
if isinstance(creator, gen.Role):
|
||||
if creator.local:
|
||||
raise 'Local role "%s" cannot be used as a creator.' % \
|
||||
creator.name
|
||||
res.append(creator)
|
||||
else:
|
||||
res.append(Role(creator))
|
||||
res.append(gen.Role(creator))
|
||||
return res
|
||||
|
||||
def getCreateMean(self, type='Import'):
|
||||
|
@ -213,13 +212,17 @@ class ClassDescriptor(Descriptor):
|
|||
res = []
|
||||
if klass.__dict__.has_key('search'):
|
||||
searches = klass.__dict__['search']
|
||||
if isinstance(searches, basestring): res.append(Search(searches))
|
||||
elif isinstance(searches, Search): res.append(searches)
|
||||
if isinstance(searches, basestring):
|
||||
res.append(gen.Search(searches))
|
||||
elif isinstance(searches, gen.Search):
|
||||
res.append(searches)
|
||||
else:
|
||||
# It must be a list of searches.
|
||||
for search in searches:
|
||||
if isinstance(search, basestring):res.append(Search(search))
|
||||
else: res.append(search)
|
||||
if isinstance(search, basestring):
|
||||
res.append(gen.Search(search))
|
||||
else:
|
||||
res.append(search)
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
|
@ -268,11 +271,10 @@ class FieldDescriptor:
|
|||
'''This class gathers information about a specific typed attribute defined
|
||||
in a gen-class.'''
|
||||
|
||||
singleValuedTypes = ('Integer', 'Float', 'Boolean', 'Date', 'File')
|
||||
# Although Appy allows to specify a multiplicity[0]>1 for those types, it is
|
||||
# not supported by Archetypes. So we will always generate single-valued type
|
||||
# definitions for them.
|
||||
specialParams = ('title', 'description')
|
||||
# not currently. So we will always generate single-valued type definitions
|
||||
# for them.
|
||||
singleValuedTypes = ('Integer', 'Float', 'Boolean', 'Date', 'File')
|
||||
|
||||
def __init__(self, fieldName, appyType, classDescriptor):
|
||||
self.appyType = appyType
|
||||
|
@ -387,8 +389,7 @@ class FieldDescriptor:
|
|||
if self.appyType.editDefault:
|
||||
self.generator.tool.addDefaultField(self)
|
||||
# - put an index on this field?
|
||||
if self.appyType.indexed and \
|
||||
(self.fieldName not in ('title', 'description')):
|
||||
if self.appyType.indexed and (self.fieldName != 'title'):
|
||||
self.classDescr.addIndexMethod(self)
|
||||
# i18n labels
|
||||
messages = self.generator.labels
|
||||
|
@ -477,7 +478,7 @@ class ToolClassDescriptor(ClassDescriptor):
|
|||
self.addField(fieldName, fieldType)
|
||||
fieldType.validator.append(fieldDescr.fieldName)
|
||||
fieldType.page.name = 'data'
|
||||
fieldType.group = Group(fieldDescr.classDescr.klass.__name__)
|
||||
fieldType.group = gen.Group(fieldDescr.classDescr.klass.__name__)
|
||||
|
||||
def addDefaultField(self, fieldDescr):
|
||||
className = fieldDescr.classDescr.name
|
||||
|
@ -485,21 +486,21 @@ class ToolClassDescriptor(ClassDescriptor):
|
|||
fieldType = fieldDescr.appyType.clone()
|
||||
self.addField(fieldName, fieldType)
|
||||
fieldType.page.name = 'data'
|
||||
fieldType.group = Group(fieldDescr.classDescr.klass.__name__)
|
||||
fieldType.group = gen.Group(fieldDescr.classDescr.klass.__name__)
|
||||
|
||||
def addPodRelatedFields(self, fieldDescr):
|
||||
'''Adds the fields needed in the Tool for configuring a Pod field.'''
|
||||
className = fieldDescr.classDescr.name
|
||||
# On what page and group to display those fields ?
|
||||
pg = {'page': 'documentGeneration',
|
||||
'group': Group(fieldDescr.classDescr.klass.__name__, ['50%']*2)}
|
||||
'group':gen.Group(fieldDescr.classDescr.klass.__name__,['50%']*2)}
|
||||
# Add the field that will store the pod template.
|
||||
fieldName = 'podTemplateFor%s_%s' % (className, fieldDescr.fieldName)
|
||||
fieldType = File(**pg)
|
||||
fieldType = gen.File(**pg)
|
||||
self.addField(fieldName, fieldType)
|
||||
# Add the field that will store the output format(s)
|
||||
fieldName = 'formatsFor%s_%s' % (className, fieldDescr.fieldName)
|
||||
fieldType = String(validator=Selection('getPodOutputFormats'),
|
||||
fieldType = gen.String(validator=gen.Selection('getPodOutputFormats'),
|
||||
multiplicity=(1,None), default=('odt',), **pg)
|
||||
self.addField(fieldName, fieldType)
|
||||
|
||||
|
@ -508,7 +509,7 @@ class ToolClassDescriptor(ClassDescriptor):
|
|||
to select what default columns will be shown on query results.'''
|
||||
className = classDescr.name
|
||||
fieldName = 'resultColumnsFor%s' % className
|
||||
fieldType = String(multiplicity=(0,None), validator=Selection(
|
||||
fieldType = gen.String(multiplicity=(0,None), validator=gen.Selection(
|
||||
'_appy_getAllFields*%s' % className), page='userInterface',
|
||||
group=classDescr.klass.__name__)
|
||||
self.addField(fieldName, fieldType)
|
||||
|
@ -520,13 +521,13 @@ class ToolClassDescriptor(ClassDescriptor):
|
|||
# Field that defines if advanced search is enabled for class
|
||||
# p_classDescr or not.
|
||||
fieldName = 'enableAdvancedSearchFor%s' % className
|
||||
fieldType = Boolean(default=True, page='userInterface',
|
||||
fieldType = gen.Boolean(default=True, page='userInterface',
|
||||
group=classDescr.klass.__name__)
|
||||
self.addField(fieldName, fieldType)
|
||||
# Field that defines how many columns are shown on the custom search
|
||||
# screen.
|
||||
fieldName = 'numberOfSearchColumnsFor%s' % className
|
||||
fieldType = Integer(default=3, page='userInterface',
|
||||
fieldType = gen.Integer(default=3, page='userInterface',
|
||||
group=classDescr.klass.__name__)
|
||||
self.addField(fieldName, fieldType)
|
||||
# Field that allows to select, among all indexed fields, what fields
|
||||
|
@ -534,7 +535,7 @@ class ToolClassDescriptor(ClassDescriptor):
|
|||
fieldName = 'searchFieldsFor%s' % className
|
||||
defaultValue = [a[0] for a in classDescr.getOrderedAppyAttributes(
|
||||
condition='attrValue.indexed')]
|
||||
fieldType = String(multiplicity=(0,None), validator=Selection(
|
||||
fieldType = gen.String(multiplicity=(0,None), validator=gen.Selection(
|
||||
'_appy_getSearchableFields*%s' % className), default=defaultValue,
|
||||
page='userInterface', group=classDescr.klass.__name__)
|
||||
self.addField(fieldName, fieldType)
|
||||
|
@ -546,8 +547,8 @@ class ToolClassDescriptor(ClassDescriptor):
|
|||
# Field that defines the path of the files to import.
|
||||
fieldName = 'importPathFor%s' % className
|
||||
defValue = classDescr.getCreateMean('Import').path
|
||||
fieldType = String(page='data', multiplicity=(1,1), default=defValue,
|
||||
group=classDescr.klass.__name__)
|
||||
fieldType = gen.String(page='data', multiplicity=(1,1),
|
||||
default=defValue,group=classDescr.klass.__name__)
|
||||
self.addField(fieldName, fieldType)
|
||||
|
||||
def addWorkflowFields(self, classDescr):
|
||||
|
@ -560,12 +561,12 @@ class ToolClassDescriptor(ClassDescriptor):
|
|||
if classDescr.isRoot() or issubclass(classDescr.klass, ModelClass):
|
||||
defaultValue = True
|
||||
fieldName = 'showWorkflowFor%s' % className
|
||||
fieldType = Boolean(default=defaultValue, page='userInterface',
|
||||
fieldType = gen.Boolean(default=defaultValue, page='userInterface',
|
||||
group=groupName)
|
||||
self.addField(fieldName, fieldType)
|
||||
# Adds the boolean field for showing or not the field "enter comments".
|
||||
fieldName = 'showWorkflowCommentFieldFor%s' % className
|
||||
fieldType = Boolean(default=defaultValue, page='userInterface',
|
||||
fieldType = gen.Boolean(default=defaultValue, page='userInterface',
|
||||
group=groupName)
|
||||
self.addField(fieldName, fieldType)
|
||||
# Adds the boolean field for showing all states in current state or not.
|
||||
|
@ -577,13 +578,12 @@ class ToolClassDescriptor(ClassDescriptor):
|
|||
if len(classDescr.getPhases()) > 1:
|
||||
defaultValue = True
|
||||
fieldName = 'showAllStatesInPhaseFor%s' % className
|
||||
fieldType = Boolean(default=defaultValue, page='userInterface',
|
||||
fieldType = gen.Boolean(default=defaultValue, page='userInterface',
|
||||
group=groupName)
|
||||
self.addField(fieldName, fieldType)
|
||||
|
||||
class UserClassDescriptor(ClassDescriptor):
|
||||
'''Represents an Archetypes-compliant class that corresponds to the User
|
||||
for the generated application.'''
|
||||
'''Appy-specific class for representing a user.'''
|
||||
def __init__(self, klass, generator):
|
||||
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
|
||||
self.modelClass = self.klass
|
||||
|
@ -649,7 +649,7 @@ class TranslationClassDescriptor(ClassDescriptor):
|
|||
def addLabelField(self, messageId, page):
|
||||
'''Adds a Computed field that will display, in the source language, the
|
||||
content of the text to translate.'''
|
||||
field = Computed(method=self.modelClass.label, plainText=False,
|
||||
field = gen.Computed(method=self.modelClass.label, plainText=False,
|
||||
page=page, show=self.modelClass.show, layouts='f')
|
||||
self.addField('%s_label' % messageId, field)
|
||||
|
||||
|
@ -683,7 +683,7 @@ class TranslationClassDescriptor(ClassDescriptor):
|
|||
params['width'] = width
|
||||
else:
|
||||
# This is a multi-line field, or a very-long-single-lined field
|
||||
params['format'] = String.TEXT
|
||||
params['format'] = gen.String.TEXT
|
||||
params['height'] = height
|
||||
self.addField(messageId, String(**params))
|
||||
self.addField(messageId, gen.String(**params))
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import os, os.path, re, sys, parser, symbol, token, types
|
||||
import appy.pod, appy.pod.renderer
|
||||
from appy.shared.utils import FolderDeleter
|
||||
#from appy.gen import *
|
||||
import appy.gen as gen
|
||||
from po import PoMessage, PoFile, PoParser
|
||||
from descriptors import *
|
||||
from utils import produceNiceMessage, getClassName
|
||||
|
@ -140,7 +140,7 @@ class Generator:
|
|||
self.user = None
|
||||
self.workflows = []
|
||||
self.initialize()
|
||||
self.config = Config.getDefault()
|
||||
self.config = gen.Config.getDefault()
|
||||
self.modulesWithTests = set()
|
||||
self.totalNumberOfTests = 0
|
||||
|
||||
|
@ -152,9 +152,9 @@ class Generator:
|
|||
workflow.'''
|
||||
res = 'none'
|
||||
for attrValue in klass.__dict__.itervalues():
|
||||
if isinstance(attrValue, Type):
|
||||
if isinstance(attrValue, gen.Type):
|
||||
res = 'class'
|
||||
elif isinstance(attrValue, State):
|
||||
elif isinstance(attrValue, gen.State):
|
||||
res = 'workflow'
|
||||
if not res:
|
||||
for baseClass in klass.__bases__:
|
||||
|
@ -219,13 +219,13 @@ class Generator:
|
|||
attrs = astClasses[moduleElem.__name__].attributes
|
||||
if appyType == 'class':
|
||||
# Determine the class type (standard, tool, user...)
|
||||
if issubclass(moduleElem, Tool):
|
||||
if issubclass(moduleElem, gen.Tool):
|
||||
if not self.tool:
|
||||
klass = self.descriptorClasses['tool']
|
||||
self.tool = klass(moduleElem, attrs, self)
|
||||
else:
|
||||
self.tool.update(moduleElem, attrs)
|
||||
elif issubclass(moduleElem, User):
|
||||
elif issubclass(moduleElem, gen.User):
|
||||
if not self.user:
|
||||
klass = self.descriptorClasses['user']
|
||||
self.user = klass(moduleElem, attrs, self)
|
||||
|
@ -244,7 +244,7 @@ class Generator:
|
|||
self.workflows.append(descriptor)
|
||||
if self.containsTests(moduleElem):
|
||||
self.modulesWithTests.add(moduleObj.__name__)
|
||||
elif isinstance(moduleElem, Config):
|
||||
elif isinstance(moduleElem, gen.Config):
|
||||
self.config = moduleElem
|
||||
|
||||
# Walk potential sub-modules
|
||||
|
@ -461,7 +461,6 @@ class ZopeGenerator(Generator):
|
|||
self.generateTool()
|
||||
self.generateInit()
|
||||
self.generateTests()
|
||||
self.generateConfigureZcml()
|
||||
# Create version.txt
|
||||
f = open(os.path.join(self.outputFolder, 'version.txt'), 'w')
|
||||
f.write(self.version)
|
||||
|
@ -536,13 +535,13 @@ class ZopeGenerator(Generator):
|
|||
self.generateWrappers()
|
||||
self.generateConfig()
|
||||
|
||||
def getAllUsedRoles(self, plone=None, local=None, grantable=None):
|
||||
def getAllUsedRoles(self, zope=None, local=None, grantable=None):
|
||||
'''Produces a list of all the roles used within all workflows and
|
||||
classes defined in this application.
|
||||
|
||||
If p_plone is True, it keeps only Plone-standard roles; if p_plone
|
||||
If p_zope is True, it keeps only Zope-standard roles; if p_zope
|
||||
is False, it keeps only roles which are specific to this application;
|
||||
if p_plone is None it has no effect (so it keeps both roles).
|
||||
if p_zope is None it has no effect (so it keeps both roles).
|
||||
|
||||
If p_local is True, it keeps only local roles (ie, roles that can
|
||||
only be granted locally); if p_local is False, it keeps only "global"
|
||||
|
@ -557,8 +556,8 @@ class ZopeGenerator(Generator):
|
|||
for wfDescr in self.workflows:
|
||||
for attr in dir(wfDescr.klass):
|
||||
attrValue = getattr(wfDescr.klass, attr)
|
||||
if isinstance(attrValue, State) or \
|
||||
isinstance(attrValue, Transition):
|
||||
if isinstance(attrValue, gen.State) or \
|
||||
isinstance(attrValue, gen.Transition):
|
||||
for role in attrValue.getUsedRoles():
|
||||
if role.name not in allRoles:
|
||||
allRoles[role.name] = role
|
||||
|
@ -569,7 +568,7 @@ class ZopeGenerator(Generator):
|
|||
allRoles[role.name] = role
|
||||
res = allRoles.values()
|
||||
# Filter the result according to parameters
|
||||
for p in ('plone', 'local', 'grantable'):
|
||||
for p in ('zope', 'local', 'grantable'):
|
||||
if eval(p) != None:
|
||||
res = [r for r in res if eval('r.%s == %s' % (p, p))]
|
||||
return res
|
||||
|
@ -613,17 +612,6 @@ class ZopeGenerator(Generator):
|
|||
res = configClasses
|
||||
return res
|
||||
|
||||
def generateConfigureZcml(self):
|
||||
'''Generates file configure.zcml.'''
|
||||
repls = self.repls.copy()
|
||||
# Note every class as "deprecated".
|
||||
depr = ''
|
||||
for klass in self.getClasses(include='all'):
|
||||
depr += '<five:deprecatedManageAddDelete class=".%s.%s"/>\n' % \
|
||||
(klass.name, klass.name)
|
||||
repls['deprecated'] = depr
|
||||
self.copyFile('configure.zcml', repls)
|
||||
|
||||
def generateConfig(self):
|
||||
repls = self.repls.copy()
|
||||
# Get some lists of classes
|
||||
|
@ -677,9 +665,9 @@ class ZopeGenerator(Generator):
|
|||
attributes.append('"%s":[%s]' % (classDescr.name, ','.join(qNames)))
|
||||
repls['attributes'] = ',\n '.join(attributes)
|
||||
# Compute list of used roles for registering them if needed
|
||||
specificRoles = self.getAllUsedRoles(plone=False)
|
||||
specificRoles = self.getAllUsedRoles(zope=False)
|
||||
repls['roles'] = ','.join(['"%s"' % r.name for r in specificRoles])
|
||||
globalRoles = self.getAllUsedRoles(plone=False, local=False)
|
||||
globalRoles = self.getAllUsedRoles(zope=False, local=False)
|
||||
repls['gRoles'] = ','.join(['"%s"' % r.name for r in globalRoles])
|
||||
grantableRoles = self.getAllUsedRoles(local=False, grantable=True)
|
||||
repls['grRoles'] = ','.join(['"%s"' % r.name for r in grantableRoles])
|
||||
|
@ -780,7 +768,7 @@ class ZopeGenerator(Generator):
|
|||
self.copyFile('testAll.py', repls, destFolder='tests')
|
||||
|
||||
def generateTool(self):
|
||||
'''Generates the Plone tool that corresponds to this application.'''
|
||||
'''Generates the tool that corresponds to this application.'''
|
||||
Msg = PoMessage
|
||||
# Create Tool-related i18n-related messages
|
||||
msg = Msg(self.tool.name, '', Msg.CONFIG % self.applicationName)
|
||||
|
@ -874,7 +862,7 @@ class ZopeGenerator(Generator):
|
|||
poMsg.produceNiceDefault()
|
||||
if poMsg not in self.labels:
|
||||
self.labels.append(poMsg)
|
||||
# Generate the resulting Archetypes class.
|
||||
# Generate the resulting Zope class.
|
||||
self.copyFile('Class.py', repls, destName=fileName)
|
||||
|
||||
def generateWorkflow(self, wfDescr):
|
||||
|
@ -886,14 +874,14 @@ class ZopeGenerator(Generator):
|
|||
wfName = WorkflowDescriptor.getWorkflowName(wfDescr.klass)
|
||||
# Add i18n messages for states
|
||||
for name in dir(wfDescr.klass):
|
||||
if not isinstance(getattr(wfDescr.klass, name), State): continue
|
||||
if not isinstance(getattr(wfDescr.klass, name), gen.State): continue
|
||||
poMsg = PoMessage('%s_%s' % (wfName, name), '', name)
|
||||
poMsg.produceNiceDefault()
|
||||
self.labels.append(poMsg)
|
||||
# Add i18n messages for transitions
|
||||
for name in dir(wfDescr.klass):
|
||||
transition = getattr(wfDescr.klass, name)
|
||||
if not isinstance(transition, Transition): continue
|
||||
if not isinstance(transition, gen.Transition): continue
|
||||
poMsg = PoMessage('%s_%s' % (wfName, name), '', name)
|
||||
poMsg.produceNiceDefault()
|
||||
self.labels.append(poMsg)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import os, os.path, time
|
||||
import appy
|
||||
import appy.version
|
||||
from appy.gen import Type, Ref, String, File
|
||||
import appy.gen as gen
|
||||
from appy.gen.po import PoParser
|
||||
from appy.gen.utils import updateRolesForPermission, createObject
|
||||
from appy.shared.data import languages
|
||||
|
@ -110,7 +110,7 @@ class ZopeInstaller:
|
|||
for name in files:
|
||||
baseName, ext = os.path.splitext(name)
|
||||
f = file(j(root, name))
|
||||
if ext in File.imageExts:
|
||||
if ext in gen.File.imageExts:
|
||||
zopeFolder.manage_addImage(name, f)
|
||||
elif ext == '.pt':
|
||||
manage_addPageTemplate(zopeFolder, baseName, '', f.read())
|
||||
|
@ -302,13 +302,11 @@ class ZopeInstaller:
|
|||
# "po" file on disk.
|
||||
appFolder = self.config.diskFolder
|
||||
appName = self.config.PROJECTNAME
|
||||
dn = os.path.dirname
|
||||
jn = os.path.join
|
||||
i18nFolder = jn(jn(jn(dn(dn(dn(appFolder))),'Products'),appName),'i18n')
|
||||
i18nFolder = os.path.join(appFolder, 'tr')
|
||||
for translation in appyTool.translations:
|
||||
# Get the "po" file
|
||||
poName = '%s-%s.po' % (appName, translation.id)
|
||||
poFile = PoParser(jn(i18nFolder, poName)).parse()
|
||||
poFile = PoParser(os.path.join(i18nFolder, poName)).parse()
|
||||
for message in poFile.messages:
|
||||
setattr(translation, message.id, message.getMessage())
|
||||
appyTool.log('Translation "%s" updated from "%s".' % \
|
||||
|
@ -359,7 +357,7 @@ class ZopeInstaller:
|
|||
wrapperClass = klass.wrapperClass
|
||||
if not hasattr(wrapperClass, 'title'):
|
||||
# Special field "type" is mandatory for every class.
|
||||
title = String(multiplicity=(1,1), show='edit', indexed=True)
|
||||
title = gen.String(multiplicity=(1,1), show='edit',indexed=True)
|
||||
title.init('title', None, 'appy')
|
||||
setattr(wrapperClass, 'title', title)
|
||||
names = self.config.attributes[wrapperClass.__name__[:-8]]
|
||||
|
@ -368,8 +366,8 @@ class ZopeInstaller:
|
|||
for baseClass in klass.wrapperClass.__bases__:
|
||||
if baseClass.__name__ == 'AbstractWrapper': continue
|
||||
for name, appyType in baseClass.__dict__.iteritems():
|
||||
if not isinstance(appyType, Type) or \
|
||||
(isinstance(appyType, Ref) and appyType.isBack):
|
||||
if not isinstance(appyType, gen.Type) or \
|
||||
(isinstance(appyType, gen.Ref) and appyType.isBack):
|
||||
continue # Back refs are initialised within fw refs
|
||||
appyType.init(name, baseClass, appName)
|
||||
|
||||
|
|
|
@ -9,8 +9,7 @@ class Migrator:
|
|||
self.installer = installer
|
||||
|
||||
def migrateTo_0_7_1(self):
|
||||
'''Appy 0.7.1 has its own management of Ref fields and does not use
|
||||
Archetypes references and the reference catalog anymore. So we must
|
||||
'''Appy 0.7.1 has its own management of Ref fields. So we must
|
||||
update data structures that store Ref info on instances.'''
|
||||
ins = self.installer
|
||||
ins.info('Migrating to Appy 0.7.1...')
|
||||
|
|
|
@ -3,7 +3,7 @@ import os, os.path, sys
|
|||
|
||||
# ------------------------------------------------------------------------------
|
||||
class TestMixin:
|
||||
'''This class is mixed in with any PloneTestCase.'''
|
||||
'''This class is mixed in with any ZopeTestCase.'''
|
||||
def createUser(self, userId, roles):
|
||||
'''Creates a user with id p_userId with some p_roles.'''
|
||||
self.acl_users.addMember(userId, 'password', [], [])
|
||||
|
@ -59,7 +59,7 @@ class TestMixin:
|
|||
def beforeTest(test):
|
||||
'''Is executed before every test.'''
|
||||
g = test.globs
|
||||
g['tool'] = test.app.plone.get('portal_%s' % g['appName'].lower()).appy()
|
||||
g['tool'] = test.app.config.appy()
|
||||
cfg = g['tool'].o.getProductConfig()
|
||||
g['appFolder'] = cfg.diskFolder
|
||||
moduleOrClassName = g['test'].name # Not used yet.
|
||||
|
|
|
@ -6,9 +6,9 @@ from appy.shared.data import languages
|
|||
import appy.gen
|
||||
from appy.gen import Type, Search, Selection
|
||||
from appy.gen.utils import SomeObjects, sequenceTypes, getClassName
|
||||
from appy.gen.plone25.mixins import BaseMixin
|
||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||
from appy.gen.plone25.descriptors import ClassDescriptor
|
||||
from appy.gen.mixins import BaseMixin
|
||||
from appy.gen.wrappers import AbstractWrapper
|
||||
from appy.gen.descriptors import ClassDescriptor
|
||||
try:
|
||||
from AccessControl.ZopeSecurityPolicy import _noroles
|
||||
except ImportError:
|
||||
|
@ -411,7 +411,7 @@ class ToolMixin(BaseMixin):
|
|||
|
||||
def getCreateMeans(self, contentTypeOrAppyClass):
|
||||
'''Gets the different ways objects of p_contentTypeOrAppyClass (which
|
||||
can be a Plone content type or a Appy class) can be created
|
||||
can be a Zope content type or a Appy class) can be created
|
||||
(via a web form, by importing external data, etc). Result is a
|
||||
dict whose keys are strings (ie "form", "import"...) and whose
|
||||
values are additional data bout the particular mean.'''
|
||||
|
@ -810,7 +810,7 @@ class ToolMixin(BaseMixin):
|
|||
9: 'month_sep', 10: 'month_oct', 11: 'month_nov', 12: 'month_dec'}
|
||||
def getMonthName(self, monthNumber):
|
||||
'''Gets the translated month name of month numbered p_monthNumber.'''
|
||||
return self.translate(self.monthsIds[int(monthNumber)], domain='plone')
|
||||
return self.translate(self.monthsIds[int(monthNumber)])
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Authentication-related methods
|
||||
|
@ -824,7 +824,7 @@ class ToolMixin(BaseMixin):
|
|||
|
||||
if jsEnabled and not cookiesEnabled:
|
||||
msg = self.translate(u'You must enable cookies before you can ' \
|
||||
'log in.', domain='plone')
|
||||
'log in.')
|
||||
return self.goto(urlBack, msg.encode('utf-8'))
|
||||
# Perform the Zope-level authentication
|
||||
login = rq.get('__ac_name', '')
|
||||
|
@ -835,11 +835,10 @@ class ToolMixin(BaseMixin):
|
|||
user = self.acl_users.validate(rq)
|
||||
if self.userIsAnon():
|
||||
rq.RESPONSE.expireCookie('__ac', path='/')
|
||||
msg = self.translate(u'Login failed', domain='plone')
|
||||
msg = self.translate(u'Login failed')
|
||||
logMsg = 'Authentication failed (tried with login "%s")' % login
|
||||
else:
|
||||
msg = self.translate(u'Welcome! You are now logged in.',
|
||||
domain='plone')
|
||||
msg = self.translate(u'Welcome! You are now logged in.')
|
||||
logMsg = 'User "%s" has been logged in.' % login
|
||||
msg = msg.encode('utf-8')
|
||||
self.log(logMsg)
|
||||
|
@ -865,7 +864,7 @@ class ToolMixin(BaseMixin):
|
|||
session.invalidate()
|
||||
self.log('User "%s" has been logged out.' % userId)
|
||||
# Remove user from variable "loggedUsers"
|
||||
from appy.gen.plone25.installer import loggedUsers
|
||||
from appy.gen.installer import loggedUsers
|
||||
if loggedUsers.has_key(userId): del loggedUsers[userId]
|
||||
return self.goto(self.getApp().absolute_url())
|
||||
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
'''This package contains mixin classes that are mixed in with generated classes:
|
||||
- mixins/BaseMixin is mixed in with Standard Archetypes classes;
|
||||
- mixins/BaseMixin is mixed in with standard Zope classes;
|
||||
- mixins/ToolMixin is mixed in with the generated application Tool class.'''
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
import os, os.path, sys, types, mimetypes, urllib, cgi
|
||||
from appy import Object
|
||||
import appy.gen
|
||||
from appy.gen import Type, String, Selection, Role, No, WorkflowAnonymous, \
|
||||
Transition, Permission
|
||||
import appy.gen as gen
|
||||
from appy.gen.utils import *
|
||||
from appy.gen.layout import Table, defaultPageLayouts
|
||||
from appy.gen.descriptors import WorkflowDescriptor
|
||||
from appy.gen.plone25.descriptors import ClassDescriptor
|
||||
from appy.gen.descriptors import WorkflowDescriptor, ClassDescriptor
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class BaseMixin:
|
||||
|
@ -187,8 +184,7 @@ class BaseMixin:
|
|||
fields in the database.'''
|
||||
rq = self.REQUEST
|
||||
tool = self.getTool()
|
||||
errorMessage = self.translate(
|
||||
'Please correct the indicated errors.', domain='plone')
|
||||
errorMessage = self.translate('Please correct the indicated errors.')
|
||||
isNew = rq.get('is_new') == 'True'
|
||||
# If this object is created from an initiator, get info about him.
|
||||
initiator = None
|
||||
|
@ -209,7 +205,7 @@ class BaseMixin:
|
|||
urlBack = tool.getSiteUrl()
|
||||
else:
|
||||
urlBack = self.getUrl()
|
||||
self.say(self.translate('Changes canceled.', domain='plone'))
|
||||
self.say(self.translate('Changes canceled.'))
|
||||
return self.goto(urlBack)
|
||||
|
||||
# Object for storing validation errors
|
||||
|
@ -245,7 +241,7 @@ class BaseMixin:
|
|||
obj, msg = self.createOrUpdate(isNew, values, initiator, initiatorField)
|
||||
|
||||
# Redirect the user to the appropriate page
|
||||
if not msg: msg = obj.translate('Changes saved.', domain='plone')
|
||||
if not msg: msg = obj.translate('Changes saved.')
|
||||
# If the object has already been deleted (ie, it is a kind of transient
|
||||
# object like a one-shot form and has already been deleted in method
|
||||
# onEdit), redirect to the main site page.
|
||||
|
@ -711,7 +707,7 @@ class BaseMixin:
|
|||
if not includeFake:
|
||||
includeIt = mayTrigger
|
||||
else:
|
||||
includeIt = mayTrigger or isinstance(mayTrigger, No)
|
||||
includeIt = mayTrigger or isinstance(mayTrigger, gen.No)
|
||||
if not includeNotShowable:
|
||||
includeIt = includeIt and transition.isShowable(wf, self)
|
||||
if not includeIt: continue
|
||||
|
@ -864,7 +860,7 @@ class BaseMixin:
|
|||
# Get the initial workflow state
|
||||
initialState = self.State(name=False)
|
||||
# Create a Transition instance representing the initial transition.
|
||||
initialTransition = Transition((initialState, initialState))
|
||||
initialTransition = gen.Transition((initialState, initialState))
|
||||
initialTransition.trigger('_init_', self, wf, '')
|
||||
|
||||
def getWorkflow(self, name=False, className=None):
|
||||
|
@ -875,7 +871,7 @@ class BaseMixin:
|
|||
else:
|
||||
appyClass = self.getTool().getAppyClass(className)
|
||||
if hasattr(appyClass, 'workflow'): wf = appyClass.workflow
|
||||
else: wf = WorkflowAnonymous
|
||||
else: wf = gen.WorkflowAnonymous
|
||||
if not name: return wf
|
||||
return WorkflowDescriptor.getWorkflowName(wf)
|
||||
|
||||
|
|
81
gen/model.py
81
gen/model.py
|
@ -1,14 +1,10 @@
|
|||
'''This file contains basic classes that will be added into any user
|
||||
application for creating the basic structure of the application "Tool" which
|
||||
is the set of web pages used for configuring the application. The "Tool" is
|
||||
available to administrators under the standard Plone link "site setup". Plone
|
||||
itself is shipped with several tools used for conguring the various parts of
|
||||
Plone (content types, catalogs, workflows, etc.)'''
|
||||
is the set of web pages used for configuring the application.'''
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
import types
|
||||
from appy.gen import *
|
||||
Grp=Group # Avoid name clash between appy.gen.Group and class Group below
|
||||
import appy.gen as gen
|
||||
|
||||
# Prototypical instances of every type -----------------------------------------
|
||||
class Protos:
|
||||
|
@ -73,7 +69,7 @@ class ModelClass:
|
|||
value = appyType.getInputLayouts()
|
||||
elif isinstance(value, basestring):
|
||||
value = '"%s"' % value
|
||||
elif isinstance(value, Ref):
|
||||
elif isinstance(value, gen.Ref):
|
||||
if not value.isBack: continue
|
||||
value = klass._appy_getTypeBody(value, wrapperName)
|
||||
elif type(value) == type(ModelClass):
|
||||
|
@ -82,11 +78,11 @@ class ModelClass:
|
|||
value = value.__name__
|
||||
else:
|
||||
value = '%s.%s' % (moduleName, value.__name__)
|
||||
elif isinstance(value, Selection):
|
||||
elif isinstance(value, gen.Selection):
|
||||
value = 'Selection("%s")' % value.methodName
|
||||
elif isinstance(value, Grp):
|
||||
elif isinstance(value, gen.Group):
|
||||
value = 'Grp("%s")' % value.name
|
||||
elif isinstance(value, Page):
|
||||
elif isinstance(value, gen.Page):
|
||||
value = 'pages["%s"]' % value.name
|
||||
elif callable(value):
|
||||
value = '%s.%s' % (wrapperName, value.__name__)
|
||||
|
@ -135,20 +131,22 @@ class User(ModelClass):
|
|||
_appy_attributes = ['title', 'name', 'firstName', 'login', 'password1',
|
||||
'password2', 'roles']
|
||||
# All methods defined below are fake. Real versions are in the wrapper.
|
||||
title = String(show=False, indexed=True)
|
||||
title = gen.String(show=False, indexed=True)
|
||||
gm = {'group': 'main', 'multiplicity': (1,1), 'width': 25}
|
||||
name = String(**gm)
|
||||
firstName = String(**gm)
|
||||
name = gen.String(**gm)
|
||||
firstName = gen.String(**gm)
|
||||
def showLogin(self): pass
|
||||
def validateLogin(self): pass
|
||||
login = String(show=showLogin, validator=validateLogin, indexed=True, **gm)
|
||||
login = gen.String(show=showLogin, validator=validateLogin,
|
||||
indexed=True, **gm)
|
||||
def showPassword(self): pass
|
||||
def validatePassword(self): pass
|
||||
password1 = String(format=String.PASSWORD, show=showPassword,
|
||||
password1 = gen.String(format=gen.String.PASSWORD, show=showPassword,
|
||||
validator=validatePassword, **gm)
|
||||
password2 = String(format=String.PASSWORD, show=showPassword, **gm)
|
||||
password2 = gen.String(format=gen.String.PASSWORD, show=showPassword, **gm)
|
||||
gm['multiplicity'] = (0, None)
|
||||
roles = String(validator=Selection('getGrantableRoles'), indexed=True, **gm)
|
||||
roles = gen.String(validator=gen.Selection('getGrantableRoles'),
|
||||
indexed=True, **gm)
|
||||
|
||||
# The Group class --------------------------------------------------------------
|
||||
class Group(ModelClass):
|
||||
|
@ -156,15 +154,15 @@ class Group(ModelClass):
|
|||
_appy_attributes = ['title', 'login', 'roles', 'users']
|
||||
# All methods defined below are fake. Real versions are in the wrapper.
|
||||
m = {'group': 'main', 'width': 25, 'indexed': True}
|
||||
title = String(multiplicity=(1,1), **m)
|
||||
title = gen.String(multiplicity=(1,1), **m)
|
||||
def showLogin(self): pass
|
||||
def validateLogin(self): pass
|
||||
login = String(show=showLogin, validator=validateLogin,
|
||||
login = gen.String(show=showLogin, validator=validateLogin,
|
||||
multiplicity=(1,1), **m)
|
||||
roles = String(validator=Selection('getGrantableRoles'),
|
||||
roles = gen.String(validator=gen.Selection('getGrantableRoles'),
|
||||
multiplicity=(0,None), **m)
|
||||
users = Ref(User, multiplicity=(0,None), add=False, link=True,
|
||||
back=Ref(attribute='groups', show=True),
|
||||
users = gen.Ref(User, multiplicity=(0,None), add=False, link=True,
|
||||
back=gen.Ref(attribute='groups', show=True),
|
||||
showHeaders=True, shownInfo=('title', 'login'))
|
||||
|
||||
# The Translation class --------------------------------------------------------
|
||||
|
@ -172,9 +170,9 @@ class Translation(ModelClass):
|
|||
_appy_attributes = ['po', 'title']
|
||||
# All methods defined below are fake. Real versions are in the wrapper.
|
||||
def getPoFile(self): pass
|
||||
po = Action(action=getPoFile, page=Page('actions', show='view'),
|
||||
po = gen.Action(action=getPoFile, page=gen.Page('actions', show='view'),
|
||||
result='filetmp')
|
||||
title = String(show=False, indexed=True)
|
||||
title = gen.String(show=False, indexed=True)
|
||||
def label(self): pass
|
||||
def show(self, name): pass
|
||||
|
||||
|
@ -195,30 +193,31 @@ class Tool(ModelClass):
|
|||
|
||||
# Tool attributes
|
||||
def validPythonWithUno(self, value): pass # Real method in the wrapper
|
||||
unoEnabledPython = String(group="connectionToOpenOffice",
|
||||
unoEnabledPython = gen.String(group="connectionToOpenOffice",
|
||||
validator=validPythonWithUno)
|
||||
openOfficePort = Integer(default=2002, group="connectionToOpenOffice")
|
||||
numberOfResultsPerPage = Integer(default=30, show=False)
|
||||
listBoxesMaximumWidth = Integer(default=100, show=False)
|
||||
appyVersion = String(show=False, layouts='f')
|
||||
openOfficePort = gen.Integer(default=2002, group="connectionToOpenOffice")
|
||||
numberOfResultsPerPage = gen.Integer(default=30, show=False)
|
||||
listBoxesMaximumWidth = gen.Integer(default=100, show=False)
|
||||
appyVersion = gen.String(show=False, layouts='f')
|
||||
def refreshSecurity(self): pass # Real method in the wrapper
|
||||
refreshSecurity = Action(action=refreshSecurity, confirm=True)
|
||||
refreshSecurity = gen.Action(action=refreshSecurity, confirm=True)
|
||||
# Ref(User) will maybe be transformed into Ref(CustomUserClass).
|
||||
users = Ref(User, multiplicity=(0,None), add=True, link=False,
|
||||
back=Ref(attribute='toTool', show=False),
|
||||
page=Page('users', show='view'),
|
||||
users = gen.Ref(User, multiplicity=(0,None), add=True, link=False,
|
||||
back=gen.Ref(attribute='toTool', show=False),
|
||||
page=gen.Page('users', show='view'),
|
||||
queryable=True, queryFields=('title', 'login'),
|
||||
showHeaders=True, shownInfo=('title', 'login', 'roles'))
|
||||
groups = Ref(Group, multiplicity=(0,None), add=True, link=False,
|
||||
back=Ref(attribute='toTool2', show=False),
|
||||
page=Page('groups', show='view'),
|
||||
groups = gen.Ref(Group, multiplicity=(0,None), add=True, link=False,
|
||||
back=gen.Ref(attribute='toTool2', show=False),
|
||||
page=gen.Page('groups', show='view'),
|
||||
queryable=True, queryFields=('title', 'login'),
|
||||
showHeaders=True, shownInfo=('title', 'login', 'roles'))
|
||||
translations = Ref(Translation, multiplicity=(0,None),add=False,link=False,
|
||||
back=Ref(attribute='trToTool', show=False), show='view',
|
||||
page=Page('translations', show='view'))
|
||||
enableNotifications = Boolean(default=True,
|
||||
page=Page('notifications', show=False))
|
||||
translations = gen.Ref(Translation, multiplicity=(0,None), add=False,
|
||||
link=False, show='view',
|
||||
back=gen.Ref(attribute='trToTool', show=False),
|
||||
page=gen.Page('translations', show='view'))
|
||||
enableNotifications = gen.Boolean(default=True,
|
||||
page=gen.Page('notifications', show=False))
|
||||
|
||||
@classmethod
|
||||
def _appy_clean(klass):
|
||||
|
|
|
@ -41,8 +41,8 @@ def sendMail(obj, transition, transitionName, workflow):
|
|||
'''Sends mail about p_transition that has been triggered on p_obj that is
|
||||
controlled by p_workflow.'''
|
||||
wfName = WorkflowDescriptor.getWorkflowName(workflow.__class__)
|
||||
ploneObj = obj.o
|
||||
portal = ploneObj.portal_url.getPortalObject()
|
||||
zopeObj = obj.o
|
||||
tool = zopeObj.getTool()
|
||||
mailInfo = transition.notify(workflow, obj)
|
||||
if not mailInfo[0]: return # Send a mail to nobody.
|
||||
# mailInfo may be one of the following:
|
||||
|
@ -54,15 +54,15 @@ def sendMail(obj, transition, transitionName, workflow):
|
|||
# address or one role) or sequences of strings.
|
||||
# Determine mail subject and body.
|
||||
if len(mailInfo) <= 2:
|
||||
# The user didn't mention mail body and subject. We will use
|
||||
# those defined from i18n labels.
|
||||
wfHistory = ploneObj.getWorkflowHistory()
|
||||
# The user didn't mention mail body and subject. We will use those
|
||||
# defined from i18n labels.
|
||||
wfHistory = zopeObj.getHistory()
|
||||
labelPrefix = '%s_%s' % (wfName, transitionName)
|
||||
tName = obj.translate(labelPrefix)
|
||||
keys = {'siteUrl': portal.absolute_url(),
|
||||
'siteTitle': portal.Title(),
|
||||
'objectUrl': ploneObj.absolute_url(),
|
||||
'objectTitle': ploneObj.Title(),
|
||||
keys = {'siteUrl': tool.getPath('/').absolute_url(),
|
||||
'siteTitle': tool.getAppName(),
|
||||
'objectUrl': zopeObj.absolute_url(),
|
||||
'objectTitle': zopeObj.Title(),
|
||||
'transitionName': tName,
|
||||
'transitionComment': wfHistory[0]['comments']}
|
||||
mailSubject = obj.translate(labelPrefix + '_mail_subject', keys)
|
||||
|
|
|
@ -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.
|
@ -4,8 +4,8 @@ from OFS.Folder import Folder
|
|||
from appy.gen.utils import createObject
|
||||
from AccessControl import ClassSecurityInfo
|
||||
import Products.<!applicationName!>.config as cfg
|
||||
from appy.gen.plone25.mixins import BaseMixin
|
||||
from appy.gen.plone25.mixins.ToolMixin import ToolMixin
|
||||
from appy.gen.mixins import BaseMixin
|
||||
from appy.gen.mixins.ToolMixin import ToolMixin
|
||||
from wrappers import <!genClassName!>_Wrapper as Wrapper
|
||||
|
||||
def manage_add<!genClassName!>(self, id, title='', REQUEST=None):
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<!codeHeader!>
|
||||
# Test coverage-related stuff --------------------------------------------------
|
||||
import sys
|
||||
from appy.gen.plone25.mixins.TestMixin import TestMixin
|
||||
from appy.gen.mixins.TestMixin import TestMixin
|
||||
covFolder = TestMixin.getCovFolder()
|
||||
# The previous method checks in sys.argv whether Zope was lauched for performing
|
||||
# coverage tests or not.
|
||||
|
@ -26,7 +26,7 @@ def countTest():
|
|||
|
||||
# ------------------------------------------------------------------------------
|
||||
import config
|
||||
from appy.gen.plone25.installer import ZopeInstaller
|
||||
from appy.gen.installer import ZopeInstaller
|
||||
|
||||
# Zope-level installation of the generated product. ----------------------------
|
||||
def initialize(context):
|
||||
|
|
|
@ -5,9 +5,7 @@ import wrappers
|
|||
<!imports!>
|
||||
|
||||
# The following imports are here for allowing mixin classes to access those
|
||||
# elements without being statically dependent on Plone/Zope packages. Indeed,
|
||||
# every Archetype instance has a method "getProductConfig" that returns this
|
||||
# module.
|
||||
# elements without being statically dependent on Zope packages.
|
||||
from persistent.list import PersistentList
|
||||
from zExceptions import BadRequest
|
||||
from ZPublisher.HTTPRequest import BaseRequest
|
||||
|
|
|
@ -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>
|
|
@ -3,17 +3,13 @@
|
|||
from unittest import TestSuite
|
||||
from Testing import ZopeTestCase
|
||||
from Testing.ZopeTestCase import ZopeDocTestSuite
|
||||
from Products.PloneTestCase import PloneTestCase
|
||||
from appy.gen.plone25.mixins.TestMixin import TestMixin, beforeTest, afterTest
|
||||
from appy.gen.mixins.TestMixin import TestMixin, beforeTest, afterTest
|
||||
<!imports!>
|
||||
|
||||
# Initialize Zope & Plone test systems -----------------------------------------
|
||||
ZopeTestCase.installProduct('PloneLanguageTool')
|
||||
# Initialize the Zope test system ----------------------------------------------
|
||||
ZopeTestCase.installProduct('<!applicationName!>')
|
||||
PloneTestCase.setupPloneSite(products=['PloneLanguageTool',
|
||||
'<!applicationName!>'])
|
||||
|
||||
class Test(PloneTestCase.PloneTestCase, TestMixin):
|
||||
class Test(ZopeTestCase.ZopeTestCase, TestMixin):
|
||||
'''Base test class for <!applicationName!> test cases.'''
|
||||
|
||||
# Data needed for defining the tests -------------------------------------------
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
from appy.gen import *
|
||||
Grp = Group # Avoid name clashes with the Group class below and appy.gen.Group
|
||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||
from appy.gen.plone25.wrappers.ToolWrapper import ToolWrapper as WTool
|
||||
from appy.gen.plone25.wrappers.UserWrapper import UserWrapper as WUser
|
||||
from appy.gen.plone25.wrappers.GroupWrapper import GroupWrapper as WGroup
|
||||
from appy.gen.plone25.wrappers.TranslationWrapper import TranslationWrapper as WT
|
||||
from appy.gen.wrappers import AbstractWrapper
|
||||
from appy.gen.wrappers.ToolWrapper import ToolWrapper as WTool
|
||||
from appy.gen.wrappers.UserWrapper import UserWrapper as WUser
|
||||
from appy.gen.wrappers.GroupWrapper import GroupWrapper as WGroup
|
||||
from appy.gen.wrappers.TranslationWrapper import TranslationWrapper as WT
|
||||
from Globals import InitializeClass
|
||||
from AccessControl import ClassSecurityInfo
|
||||
tfw = {"edit":"f","cell":"f","view":"f"} # Layout for Translation fields
|
||||
|
|
|
@ -1,21 +1,7 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
|
||||
lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"
|
||||
xmlns:metal="http://xml.zope.org/namespaces/metal"
|
||||
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
|
||||
metal:use-macro="here/main_template/macros/master">
|
||||
|
||||
<tal:comment replace="nothing">Disable standard Plone green tabs</tal:comment>
|
||||
<div metal:fill-slot="top_slot">
|
||||
<metal:block metal:use-macro="here/global_defines/macros/defines" />
|
||||
<div tal:define="dummy python:request.set('disable_border', 1)" />
|
||||
</div>
|
||||
|
||||
<tal:comment replace="nothing">Fill main slot of Plone main_template</tal:comment>
|
||||
<body>
|
||||
<metal:fill fill-slot="main"
|
||||
tal:define="appFolder context/getParentNode;
|
||||
className request/className;
|
||||
tool python: portal.get('portal_%s' % appFolder.id.lower());
|
||||
<tal:main define="tool context/config">
|
||||
<html metal:use-macro="context/ui/template/macros/main">
|
||||
<metal:fill fill-slot="content"
|
||||
tal:define="className request/className;
|
||||
importElems python: tool.getImportElements(className);
|
||||
global allAreImported python:True">
|
||||
|
||||
|
@ -68,7 +54,7 @@
|
|||
</script>
|
||||
<tal:comment replace="nothing">Form for importing several meetings at once.</tal:comment>
|
||||
<form name="importElements"
|
||||
tal:attributes="action python: appFolder.absolute_url()+'/do'" method="post">
|
||||
tal:attributes="action python: tool.absolute_url()+'/do'" method="post">
|
||||
<input type="hidden" name="action" value="ImportObjects"/>
|
||||
<input type="hidden" name="className" tal:attributes="value className"/>
|
||||
<input type="hidden" name="importPath" value=""/>
|
||||
|
@ -120,6 +106,6 @@
|
|||
tal:condition="python: importElems[1] and not allAreImported"
|
||||
tal:attributes="value python:tool.translate('import_many')"/>
|
||||
</p>
|
||||
</metal:fill>
|
||||
</body>
|
||||
</metal:fill>
|
||||
</html>
|
||||
</tal:main>
|
||||
|
|
|
@ -70,11 +70,11 @@
|
|||
<tal:history condition="objs">
|
||||
<metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/>
|
||||
<table width="100%" class="history">
|
||||
<tr i18n:domain="plone">
|
||||
<th align="left" i18n:translate="listingheader_action">Action</th>
|
||||
<th align="left" i18n:translate="listingheader_performed_by">By</th>
|
||||
<th align="left" i18n:translate="listingheader_date_and_time">Date</th>
|
||||
<th align="left" i18n:translate="listingheader_comment">Comment</th>
|
||||
<tr>
|
||||
<th align="left">Action</th>
|
||||
<th align="left">By</th>
|
||||
<th align="left">Date</th>
|
||||
<th align="left">Comment</th>
|
||||
</tr>
|
||||
<tal:event repeat="event objs">
|
||||
<tr tal:define="odd repeat/event/odd;
|
||||
|
@ -202,7 +202,7 @@
|
|||
<img align="left" style="cursor:pointer" onClick="toggleCookie('appyHistory')"
|
||||
tal:attributes="src python:test(historyExpanded, 'ui/collapse.gif', 'ui/expand.gif');"
|
||||
id="appyHistory_img"/>
|
||||
<span i18n:translate="label_history" i18n:domain="plone" class="historyLabel">History</span> ||
|
||||
<span>History</span> ||
|
||||
</tal:accessHistory>
|
||||
|
||||
<tal:comment replace="nothing">Show document creator</tal:comment>
|
||||
|
@ -263,32 +263,28 @@
|
|||
<tal:previous condition="python: previousPage and pageInfo['showPrevious']">
|
||||
<tal:button condition="isEdit">
|
||||
<input type="image" class="imageInput" style="cursor:pointer" name="buttonPrevious"
|
||||
title="label_previous" i18n:attributes="title" i18n:domain="plone"
|
||||
tal:attributes="src string:$appUrl/ui/previous.png"/>
|
||||
title="Previous" tal:attributes="src string:$appUrl/ui/previous.png"/>
|
||||
<input type="hidden" name="previousPage" tal:attributes="value previousPage"/>
|
||||
</tal:button>
|
||||
<tal:link condition="not: isEdit">
|
||||
<a tal:attributes="href python: contextObj.getUrl(page=previousPage)">
|
||||
<img tal:attributes="src string:$appUrl/ui/previous.png"
|
||||
title="label_previous" i18n:attributes="title" i18n:domain="plone"/>
|
||||
<img tal:attributes="src string:$appUrl/ui/previous.png" title="Previous"/>
|
||||
</a>
|
||||
</tal:link>
|
||||
</tal:previous>
|
||||
|
||||
<tal:save condition="python: isEdit and pageInfo['showSave']">
|
||||
<input type="image" class="imageInput" style="cursor:pointer" name="buttonOk"
|
||||
title="label_save" i18n:attributes="title" i18n:domain="plone"
|
||||
tal:attributes="src string:$appUrl/ui/save.png"/>
|
||||
title="Save" tal:attributes="src string:$appUrl/ui/save.png"/>
|
||||
</tal:save>
|
||||
|
||||
<tal:cancel condition="python: isEdit and pageInfo['showCancel']">
|
||||
<input type="image" class="imageInput" style="cursor:pointer" name="buttonCancel"
|
||||
title="label_cancel" i18n:attributes="title" i18n:domain="plone"
|
||||
tal:attributes="src string:$appUrl/ui/cancel.png"/>
|
||||
title="Cancel" tal:attributes="src string:$appUrl/ui/cancel.png"/>
|
||||
</tal:cancel>
|
||||
|
||||
<tal:edit condition="python: not isEdit and pageInfo['showOnEdit']">
|
||||
<img title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
||||
<img title="Edit" style="cursor:pointer"
|
||||
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=page);
|
||||
src string: $appUrl/ui/editBig.png"
|
||||
tal:condition="python: contextObj.allows('Modify portal content')"/>
|
||||
|
@ -303,14 +299,12 @@
|
|||
<tal:next condition="python: nextPage and pageInfo['showNext']">
|
||||
<tal:button condition="isEdit">
|
||||
<input type="image" class="imageInput" style="cursor:pointer" name="buttonNext"
|
||||
title="label_next" i18n:attributes="title" i18n:domain="plone"
|
||||
tal:attributes="src string:$appUrl/ui/next.png"/>
|
||||
title="Next" tal:attributes="src string:$appUrl/ui/next.png"/>
|
||||
<input type="hidden" name="nextPage" tal:attributes="value nextPage"/>
|
||||
</tal:button>
|
||||
<tal:link condition="not: isEdit">
|
||||
<a tal:attributes="href python: contextObj.getUrl(page=nextPage)">
|
||||
<img tal:attributes="src string:$appUrl/ui/next.png"
|
||||
title="label_next" i18n:attributes="title" i18n:domain="plone"/>
|
||||
<img tal:attributes="src string:$appUrl/ui/next.png" title="Next"/>
|
||||
</a>
|
||||
</tal:link>
|
||||
</tal:next>
|
||||
|
|
|
@ -117,7 +117,7 @@
|
|||
tal:content="structure python: _(label)"></a>
|
||||
</td>
|
||||
<td align="right">
|
||||
<img title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
||||
<img title="Edit" style="cursor:pointer"
|
||||
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=pageName);
|
||||
src string: $appUrl/ui/edit.gif"
|
||||
tal:condition="python: contextObj.allows('Modify portal content') and phase['pagesInfo'][pageName]['showOnEdit']"/>
|
||||
|
@ -136,7 +136,7 @@
|
|||
</a>
|
||||
</td>
|
||||
<td align="right">
|
||||
<img title="Edit" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
||||
<img title="Edit" style="cursor:pointer"
|
||||
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=aPage);
|
||||
src string: $appUrl/ui/edit.gif"
|
||||
tal:condition="python: user.has_permission('Modify portal content', contextObj) and phase['pagesInfo'][aPage]['showOnEdit']"/>
|
||||
|
|
|
@ -125,13 +125,12 @@
|
|||
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (className, searchName, repeat['obj'].number()+startNumber, totalNumber);"
|
||||
tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)"
|
||||
tal:condition="python: obj.allows('Modify portal content')">
|
||||
<img title="Edit" i18n:domain="plone" i18n:attributes="title"
|
||||
tal:attributes="src string: $appUrl/ui/edit.gif"/>
|
||||
<img title="Edit" tal:attributes="src string: $appUrl/ui/edit.gif"/>
|
||||
</a></td>
|
||||
<tal:comment replace="nothing">Delete the element</tal:comment>
|
||||
<td>
|
||||
<img tal:condition="python: obj.allows('Delete objects') and obj.mayDelete()"
|
||||
title="Delete" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
||||
title="Delete" style="cursor:pointer"
|
||||
tal:attributes="src string: $appUrl/ui/delete.png;
|
||||
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>
|
||||
</td>
|
||||
|
|
|
@ -14,10 +14,7 @@
|
|||
onClick python: 'askConfirm(\'form\', \'%s\', "%s")' % (formId, labelConfirm)"/>
|
||||
</tal:confirm>
|
||||
<input type="submit" name="do" tal:condition="not: widget/confirm"
|
||||
tal:attributes="value label" onClick="javascript:;"/>
|
||||
<tal:comment replace="nothing">The previous onClick is simply used to prevent Plone
|
||||
from adding a CSS class that displays a popup when the user triggers the form multiple
|
||||
times.</tal:comment>
|
||||
tal:attributes="value label"/>
|
||||
</form>
|
||||
</metal:view>
|
||||
|
||||
|
|
|
@ -29,11 +29,11 @@
|
|||
<label tal:attributes="for widgetName" tal:content="python: tool.translate(widget['labelId'])"></label><br>
|
||||
<tal:yes define="valueId python:'%s_yes' % name">
|
||||
<input type="radio" value="True" tal:attributes="name typedWidget; id valueId"/>
|
||||
<label tal:attributes="for valueId" i18n:translate="yes" i18n:domain="plone"></label>
|
||||
<label tal:attributes="for valueId">Yes</label>
|
||||
</tal:yes>
|
||||
<tal:no define="valueId python:'%s_no' % name">
|
||||
<input type="radio" value="False" tal:attributes="name typedWidget; id valueId"/>
|
||||
<label tal:attributes="for valueId" i18n:translate="no" i18n:domain="plone"></label>
|
||||
<label tal:attributes="for valueId">No</label>
|
||||
</tal:no>
|
||||
<tal:whatever define="valueId python:'%s_whatever' % name">
|
||||
<input type="radio" value="" tal:attributes="name typedWidget; id valueId" checked="checked"/>
|
||||
|
|
|
@ -24,14 +24,13 @@
|
|||
<metal:call use-macro="app/ui/widgets/file/macros/view"/><br/>
|
||||
</tal:showFile>
|
||||
<tal:editButtons condition="not: empty">
|
||||
<tal:comment replace="nothing">Keep the file untouched.</tal:comment>
|
||||
<tal:comment replace="nothing">Keep the file unchanged.</tal:comment>
|
||||
<input type="radio" value="nochange"
|
||||
tal:attributes="checked python:test(info['size']!=0, 'checked', None);
|
||||
name string:${name}_delete;
|
||||
id string:${name}_nochange;
|
||||
onclick string:document.getElementById('${name}_file').disabled=true;"/>
|
||||
<label tal:attributes="for string:${name}_nochange"
|
||||
i18n:translate="nochange_file" i18n:domain="plone">Keep the file unchanged</label>
|
||||
<label tal:attributes="for string:${name}_nochange">Keep the file unchanged</label>
|
||||
<br/>
|
||||
<tal:comment replace="nothing">Delete the file.</tal:comment>
|
||||
<tal:delete condition="not: widget/required">
|
||||
|
@ -39,8 +38,7 @@
|
|||
tal:attributes="name string:${name}_delete;
|
||||
id string:${name}_delete;
|
||||
onclick string:document.getElementById('${name}_file').disabled=true;"/>
|
||||
<label tal:attributes="for string:${name}_delete"
|
||||
i18n:translate="delete_file" i18n:domain="plone">Delete the file</label>
|
||||
<label tal:attributes="for string:${name}_delete">Delete the file</label>
|
||||
<br/>
|
||||
</tal:delete>
|
||||
<tal:comment replace="nothing">Replace with a new file.</tal:comment>
|
||||
|
@ -49,8 +47,7 @@
|
|||
name string:${name}_delete;
|
||||
id string:${name}_upload;
|
||||
onclick string:document.getElementById('${name}_file').disabled=false"/>
|
||||
<label tal:attributes="for string:${name}_upload;"
|
||||
i18n:translate="upload_file" i18n:domain="plone">Replace it with a new file</label>
|
||||
<label tal:attributes="for string:${name}_upload;">Replace it with a new file</label>
|
||||
<br/>
|
||||
</tal:editButtons>
|
||||
<tal:comment replace="nothing">The upload field.</tal:comment>
|
||||
|
|
|
@ -43,14 +43,13 @@
|
|||
<td tal:condition="python: obj.allows('Modify portal content') and not appyType['noForm']">
|
||||
<a tal:define="navInfo python:'ref.%s.%s:%s.%d.%d' % (contextObj.UID(), fieldName, appyType['pageName'], repeat['obj'].number()+startNumber, totalNumber);"
|
||||
tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)">
|
||||
<img title="label_edit" i18n:domain="plone" i18n:attributes="title"
|
||||
tal:attributes="src string: $appUrl/ui/edit.gif"/>
|
||||
<img title="Edit" tal:attributes="src string: $appUrl/ui/edit.gif"/>
|
||||
</a>
|
||||
</td>
|
||||
<tal:comment replace="nothing">Delete the element</tal:comment>
|
||||
<td>
|
||||
<img tal:condition="python: not appyType['isBack'] and obj.allows('Delete objects') and obj.mayDelete()"
|
||||
title="Delete" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
|
||||
title="Delete" style="cursor:pointer"
|
||||
tal:attributes="src string: $appUrl/ui/delete.png;
|
||||
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>
|
||||
</td>
|
||||
|
|
23
gen/utils.py
23
gen/utils.py
|
@ -247,30 +247,30 @@ CONVERSION_ERROR = 'An error occurred while executing command "%s". %s'
|
|||
class FileWrapper:
|
||||
'''When you get, from an appy object, the value of a File attribute, you
|
||||
get an instance of this class.'''
|
||||
def __init__(self, atFile):
|
||||
def __init__(self, zopeFile):
|
||||
'''This constructor is only used by Appy to create a nice File instance
|
||||
from a Plone/Zope corresponding instance (p_atFile). If you need to
|
||||
from a Zope corresponding instance (p_zopeFile). If you need to
|
||||
create a new file and assign it to a File attribute, use the
|
||||
attribute setter, do not create yourself an instance of this
|
||||
class.'''
|
||||
d = self.__dict__
|
||||
d['_atFile'] = atFile # Not for you!
|
||||
d['name'] = atFile.filename
|
||||
d['content'] = atFile.data
|
||||
d['mimeType'] = atFile.content_type
|
||||
d['size'] = atFile.size # In bytes
|
||||
d['_zopeFile'] = zopeFile # Not for you!
|
||||
d['name'] = zopeFile.filename
|
||||
d['content'] = zopeFile.data
|
||||
d['mimeType'] = zopeFile.content_type
|
||||
d['size'] = zopeFile.size # In bytes
|
||||
|
||||
def __setattr__(self, name, v):
|
||||
d = self.__dict__
|
||||
if name == 'name':
|
||||
self._atFile.filename = v
|
||||
self._zopeFile.filename = v
|
||||
d['name'] = v
|
||||
elif name == 'content':
|
||||
self._atFile.update_data(v, self.mimeType, len(v))
|
||||
self._zopeFile.update_data(v, self.mimeType, len(v))
|
||||
d['content'] = v
|
||||
d['size'] = len(v)
|
||||
elif name == 'mimeType':
|
||||
self._atFile.content_type = self.mimeType = v
|
||||
self._zopeFile.content_type = self.mimeType = v
|
||||
else:
|
||||
raise 'Impossible to set attribute %s. "Settable" attributes ' \
|
||||
'are "name", "content" and "mimeType".' % name
|
||||
|
@ -326,8 +326,7 @@ def getClassName(klass, appName=None):
|
|||
Zope class. For some classes, name p_appName is required: it is
|
||||
part of the class name.'''
|
||||
moduleName = klass.__module__
|
||||
if (moduleName == 'appy.gen.plone25.model') or \
|
||||
moduleName.endswith('.wrappers'):
|
||||
if (moduleName == 'appy.gen.model') or moduleName.endswith('.wrappers'):
|
||||
# This is a model (generation time or run time)
|
||||
res = appName + klass.__name__
|
||||
elif klass.__bases__ and (klass.__bases__[-1].__module__ == 'appy.gen'):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||
from appy.gen.wrappers import AbstractWrapper
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class GroupWrapper(AbstractWrapper):
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import os.path
|
||||
import appy
|
||||
from appy.shared.utils import executeCommand
|
||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||
from appy.gen.wrappers import AbstractWrapper
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
_PY = 'Please specify a file corresponding to a Python interpreter ' \
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import os.path
|
||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||
from appy.gen.wrappers import AbstractWrapper
|
||||
from appy.gen.po import PoFile, PoMessage
|
||||
from appy.shared.utils import getOsTempFolder
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||
from appy.gen.wrappers import AbstractWrapper
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class UserWrapper(AbstractWrapper):
|
||||
|
|
Loading…
Reference in a new issue