460 lines
22 KiB
Python
Executable file
460 lines
22 KiB
Python
Executable file
# ------------------------------------------------------------------------------
|
|
import re
|
|
from appy.gen.utils import sequenceTypes, PageDescr
|
|
|
|
# Default Appy permissions -----------------------------------------------------
|
|
r, w, d = ('read', 'write', 'delete')
|
|
|
|
# Descriptor classes used for refining descriptions of elements in types
|
|
# (pages, groups,...) ----------------------------------------------------------
|
|
class Page:
|
|
def __init__(self, name, phase='main', show=True):
|
|
self.name = name
|
|
self.phase = phase
|
|
self.show = show
|
|
|
|
# ------------------------------------------------------------------------------
|
|
class Type:
|
|
'''Basic abstract class for defining any appy type.'''
|
|
def __init__(self, validator, multiplicity, index, default, optional,
|
|
editDefault, show, page, group, move, searchable,
|
|
specificReadPermission, specificWritePermission, width,
|
|
height, master, masterValue):
|
|
# The validator restricts which values may be defined. It can be an
|
|
# interval (1,None), a list of string values ['choice1', 'choice2'],
|
|
# a regular expression, a custom function, a Selection instance, etc.
|
|
self.validator = validator
|
|
# Multiplicity is a tuple indicating the minimum and maximum
|
|
# occurrences of values.
|
|
self.multiplicity = multiplicity
|
|
# Type of the index on the values. If you want to represent a simple
|
|
# (ordered) list of values, specify None. If you want to
|
|
# index your values with unordered integers or with other types like
|
|
# strings (thus creating a dictionary of values instead of a list),
|
|
# specify a type specification for the index, like Integer() or
|
|
# String(). Note that this concept of "index" has nothing to do with
|
|
# the concept of "database index".
|
|
self.index = index
|
|
# Default value
|
|
self.default = default
|
|
# Is the field optional or not ?
|
|
self.optional = optional
|
|
# May the user configure a default value ?
|
|
self.editDefault = editDefault
|
|
# Must the field be visible or not?
|
|
self.show = show
|
|
# When displaying/editing the whole object, on what page and phase must
|
|
# this field value appear? Default is ('main', 'main'). pageShow
|
|
# indicates if the page must be shown or not.
|
|
self.page, self.phase, self.pageShow = PageDescr.getPageInfo(page, Page)
|
|
# Within self.page, in what group of fields must this field value
|
|
# appear?
|
|
self.group = group
|
|
# The following attribute allows to move a field back to a previous
|
|
# position (useful for content types that inherit from others).
|
|
self.move = move
|
|
# If specified "searchable", the field will be referenced in low-level
|
|
# indexing mechanisms for fast access and search functionalities.
|
|
self.searchable = searchable
|
|
# Normally, permissions to read or write every attribute in a type are
|
|
# granted if the user has the global permission to read or
|
|
# create/edit instances of the whole type. If you want a given attribute
|
|
# to be protected by specific permissions, set one or the 2 next boolean
|
|
# values to "True".
|
|
self.specificReadPermission = specificReadPermission
|
|
self.specificWritePermission = specificWritePermission
|
|
# Widget width and height
|
|
self.width = width
|
|
self.height = height
|
|
# The behaviour of this field may depend on another, "master" field
|
|
self.master = master
|
|
if master:
|
|
self.master.slaves.append(self)
|
|
# When master has some value(s), there is impact on this field.
|
|
self.masterValue = masterValue
|
|
self.id = id(self)
|
|
self.type = self.__class__.__name__
|
|
self.pythonType = None # The True corresponding Python type
|
|
self.slaves = [] # The list of slaves of this field
|
|
self.selfClass = None # The Python class to which this Type definition
|
|
# is linked. This will be computed at runtime.
|
|
|
|
def isMultiValued(self):
|
|
'''Does this type definition allow to define multiple values?'''
|
|
res = False
|
|
maxOccurs = self.multiplicity[1]
|
|
if (maxOccurs == None) or (maxOccurs > 1):
|
|
res = True
|
|
return res
|
|
|
|
class Integer(Type):
|
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
|
default=None, optional=False, editDefault=False, show=True,
|
|
page='main', group=None, move=0, searchable=False,
|
|
specificReadPermission=False, specificWritePermission=False,
|
|
width=None, height=None, master=None, masterValue=None):
|
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
|
editDefault, show, page, group, move, False,
|
|
specificReadPermission, specificWritePermission, width,
|
|
height, master, masterValue)
|
|
self.pythonType = long
|
|
|
|
class Float(Type):
|
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
|
default=None, optional=False, editDefault=False, show=True,
|
|
page='main', group=None, move=0, searchable=False,
|
|
specificReadPermission=False, specificWritePermission=False,
|
|
width=None, height=None, master=None, masterValue=None):
|
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
|
editDefault, show, page, group, move, False,
|
|
specificReadPermission, specificWritePermission, width,
|
|
height, master, masterValue)
|
|
self.pythonType = float
|
|
|
|
class String(Type):
|
|
# Some predefined regular expressions that may be used as validators
|
|
c = re.compile
|
|
EMAIL = c('[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.' \
|
|
'[a-zA-Z][a-zA-Z\.]*[a-zA-Z]')
|
|
ALPHANUMERIC = c('[\w-]+')
|
|
URL = c('(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*(\.[a-z]{2,5})?' \
|
|
'(([0-9]{1,5})?\/.*)?')
|
|
# Possible values for "format"
|
|
LINE = 0
|
|
TEXT = 1
|
|
XHTML = 2
|
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
|
default=None, optional=False, editDefault=False, format=LINE,
|
|
show=True, page='main', group=None, move=0, searchable=False,
|
|
specificReadPermission=False, specificWritePermission=False,
|
|
width=None, height=None, master=None, masterValue=None):
|
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
|
editDefault, show, page, group, move, searchable,
|
|
specificReadPermission, specificWritePermission, width,
|
|
height, master, masterValue)
|
|
self.format = format
|
|
def isSelection(self):
|
|
'''Does the validator of this type definition define a list of values
|
|
into which the user must select one or more values?'''
|
|
res = True
|
|
if type(self.validator) in (list, tuple):
|
|
for elem in self.validator:
|
|
if not isinstance(elem, basestring):
|
|
res = False
|
|
break
|
|
else:
|
|
if not isinstance(self.validator, Selection):
|
|
res = False
|
|
return res
|
|
|
|
class Boolean(Type):
|
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
|
default=None, optional=False, editDefault=False, show=True,
|
|
page='main', group=None, move=0, searchable=False,
|
|
specificReadPermission=False, specificWritePermission=False,
|
|
width=None, height=None, master=None, masterValue=None):
|
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
|
editDefault, show, page, group, move, searchable,
|
|
specificReadPermission, specificWritePermission, width,
|
|
height, master, masterValue)
|
|
self.pythonType = bool
|
|
|
|
class Date(Type):
|
|
# Possible values for "format"
|
|
WITH_HOUR = 0
|
|
WITHOUT_HOUR = 1
|
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
|
default=None, optional=False, editDefault=False,
|
|
format=WITH_HOUR, show=True, page='main', group=None, move=0,
|
|
searchable=False,
|
|
specificReadPermission=False, specificWritePermission=False,
|
|
width=None, height=None, master=None, masterValue=None):
|
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
|
editDefault, show, page, group, move, searchable,
|
|
specificReadPermission, specificWritePermission, width,
|
|
height, master, masterValue)
|
|
self.format = format
|
|
|
|
class File(Type):
|
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
|
default=None, optional=False, editDefault=False, show=True,
|
|
page='main', group=None, move=0, searchable=False,
|
|
specificReadPermission=False, specificWritePermission=False,
|
|
width=None, height=None, master=None, masterValue=None,
|
|
isImage=False):
|
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
|
editDefault, show, page, group, move, False,
|
|
specificReadPermission, specificWritePermission, width,
|
|
height, master, masterValue)
|
|
self.isImage = isImage
|
|
|
|
class Ref(Type):
|
|
def __init__(self, klass=None, attribute=None, validator=None,
|
|
multiplicity=(0,1), index=None, default=None, optional=False,
|
|
editDefault=False, add=False, link=True, unlink=False,
|
|
back=None, isBack=False, show=True, page='main', group=None,
|
|
showHeaders=False, shownInfo=(), wide=False, select=None,
|
|
move=0, searchable=False,
|
|
specificReadPermission=False, specificWritePermission=False,
|
|
width=None, height=None, master=None, masterValue=None):
|
|
Type.__init__(self, validator, multiplicity, index, default, optional,
|
|
editDefault, show, page, group, move, False,
|
|
specificReadPermission, specificWritePermission, width,
|
|
height, master, masterValue)
|
|
self.klass = klass
|
|
self.attribute = attribute
|
|
self.add = add # May the user add new objects through this ref ?
|
|
self.link = link # May the user link existing objects through this ref?
|
|
self.unlink = unlink # May the user unlink existing objects?
|
|
self.back = back
|
|
self.isBack = isBack # Should always be False
|
|
self.showHeaders = showHeaders # When displaying a tabular list of
|
|
# referenced objects, must we show the table headers?
|
|
self.shownInfo = shownInfo # When displaying referenced object(s),
|
|
# we will display its title + all other fields whose names are listed
|
|
# in this attribute.
|
|
self.wide = wide # If True, the table of references will be as wide
|
|
# as possible
|
|
self.select = select # If a method is defined here, it will be used to
|
|
# filter the list of available tied objects.
|
|
|
|
class Computed(Type):
|
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
|
default=None, optional=False, editDefault=False, show=True,
|
|
page='main', group=None, move=0, searchable=False,
|
|
specificReadPermission=False, specificWritePermission=False,
|
|
width=None, height=None, method=None, plainText=True,
|
|
master=None, masterValue=None):
|
|
Type.__init__(self, None, multiplicity, index, default, optional,
|
|
False, show, page, group, move, False,
|
|
specificReadPermission, specificWritePermission, width,
|
|
height, master, masterValue)
|
|
self.method = method # The method used for computing the field value
|
|
self.plainText = plainText # Does field computation produce pain text
|
|
# or XHTML?
|
|
|
|
class Action(Type):
|
|
'''An action is a workflow-independent Python method that can be triggered
|
|
by the user on a given gen-class. For example, the custom installation
|
|
procedure of a gen-application is implemented by an action on the custom
|
|
tool class. An action is rendered as a button.'''
|
|
def __init__(self, validator=None, multiplicity=(1,1), index=None,
|
|
default=None, optional=False, editDefault=False, show=True,
|
|
page='main', group=None, move=0, searchable=False,
|
|
specificReadPermission=False, specificWritePermission=False,
|
|
width=None, height=None, action=None, master=None,
|
|
masterValue=None):
|
|
Type.__init__(self, None, (0,1), index, default, optional,
|
|
False, show, page, group, move, False,
|
|
specificReadPermission, specificWritePermission, width,
|
|
height, master, masterValue)
|
|
self.action = action # Can be a single method or a list/tuple of methods
|
|
|
|
def __call__(self, obj):
|
|
'''Calls the action on p_obj.'''
|
|
try:
|
|
if type(self.action) in sequenceTypes:
|
|
# There are multiple Python methods
|
|
res = [True, '']
|
|
for act in self.action:
|
|
actRes = act(obj)
|
|
if type(actRes) in sequenceTypes:
|
|
res[0] = res[0] and actRes[0]
|
|
res[1] = res[1] + '\n' + actRes[1]
|
|
else:
|
|
res[0] = res[0] and actRes
|
|
else:
|
|
# There is only one Python method
|
|
actRes = self.action(obj)
|
|
if type(actRes) in sequenceTypes:
|
|
res = list(actRes)
|
|
else:
|
|
res = [actRes, '']
|
|
# If res is None (ie the user-defined action did not return anything)
|
|
# we consider the action as successfull.
|
|
if res[0] == None: res[0] = True
|
|
except Exception, e:
|
|
res = (False, str(e))
|
|
return res
|
|
|
|
class Info(Type):
|
|
'''An info is a field whose purpose is to present information
|
|
(text, html...) to the user.'''
|
|
def __init__(self, validator=None, multiplicity=(1,1), index=None,
|
|
default=None, optional=False, editDefault=False, show=True,
|
|
page='main', group=None, move=0, searchable=False,
|
|
specificReadPermission=False, specificWritePermission=False,
|
|
width=None, height=None, master=None, masterValue=None):
|
|
Type.__init__(self, None, (0,1), index, default, optional,
|
|
False, show, page, group, move, False,
|
|
specificReadPermission, specificWritePermission, width,
|
|
height, master, masterValue)
|
|
|
|
# Workflow-specific types ------------------------------------------------------
|
|
class State:
|
|
def __init__(self, permissions, initial=False, phase='main', show=True):
|
|
self.permissions = permissions #~{s_permissionName:[s_roleName]}~ This
|
|
# dict gives, for every permission managed by a workflow, the list of
|
|
# roles for which the permission is granted in this state.
|
|
# Standard permissions are 'read', 'write' and 'delete'.
|
|
self.initial = initial
|
|
self.phase = phase
|
|
self.show = show
|
|
def getUsedRoles(self):
|
|
res = set()
|
|
for roleValue in self.permissions.itervalues():
|
|
if isinstance(roleValue, basestring):
|
|
res.add(roleValue)
|
|
elif roleValue:
|
|
for role in roleValue:
|
|
res.add(role)
|
|
return list(res)
|
|
def getTransitions(self, transitions, selfIsFromState=True):
|
|
'''Among p_transitions, returns those whose fromState is p_self (if
|
|
p_selfIsFromState is True) or those whose toState is p_self (if
|
|
p_selfIsFromState is False).'''
|
|
res = []
|
|
for t in transitions:
|
|
if self in t.getStates(selfIsFromState):
|
|
res.append(t)
|
|
return res
|
|
def getPermissions(self):
|
|
'''If you get the permissions mapping through self.permissions, dict
|
|
values may be of different types (a list of roles, a single role or
|
|
None). Iy you call this method, you will always get a list which
|
|
may be empty.'''
|
|
res = {}
|
|
for permission, roleValue in self.permissions.iteritems():
|
|
if roleValue == None:
|
|
res[permission] = []
|
|
elif isinstance(roleValue, basestring):
|
|
res[permission] = [roleValue]
|
|
else:
|
|
res[permission] = roleValue
|
|
return res
|
|
|
|
class Transition:
|
|
def __init__(self, states, condition=True, action=None, notify=None):
|
|
self.states = states # In its simpler form, it is a tuple with 2
|
|
# states: (fromState, toState). But it can also be a tuple of several
|
|
# (fromState, toState) sub-tuples. This way, you may define only 1
|
|
# transition at several places in the state-transition diagram. It may
|
|
# be useful for "undo" transitions, for example.
|
|
self.condition = condition
|
|
self.action = action
|
|
self.notify = notify # If not None, it is a method telling who must be
|
|
# notified by email after the transition has been executed.
|
|
|
|
def getUsedRoles(self):
|
|
'''If self.condition is specifies a role.'''
|
|
res = []
|
|
if isinstance(self.condition, basestring):
|
|
res = [self.condition]
|
|
return res
|
|
|
|
def isSingle(self):
|
|
'''If this transitions is only define between 2 states, returns True.
|
|
Else, returns False.'''
|
|
return isinstance(self.states[0], State)
|
|
|
|
def getStates(self, fromStates=True):
|
|
'''Returns the fromState(s) if p_fromStates is True, the toState(s)
|
|
else. If you want to get the states grouped in tuples
|
|
(fromState, toState), simply use self.states.'''
|
|
res = []
|
|
stateIndex = 1
|
|
if fromStates:
|
|
stateIndex = 0
|
|
if self.isSingle():
|
|
res.append(self.states[stateIndex])
|
|
else:
|
|
for states in self.states:
|
|
theState = states[stateIndex]
|
|
if theState not in res:
|
|
res.append(theState)
|
|
return res
|
|
|
|
def hasState(self, state, isFrom):
|
|
'''If p_isFrom is True, this method returns True if p_state is a
|
|
starting state for p_self. If p_isFrom is False, this method returns
|
|
True if p_state is an ending state for p_self.'''
|
|
stateIndex = 1
|
|
if isFrom:
|
|
stateIndex = 0
|
|
if self.isSingle():
|
|
res = state == self.states[stateIndex]
|
|
else:
|
|
res = False
|
|
for states in self.states:
|
|
if states[stateIndex] == state:
|
|
res = True
|
|
break
|
|
return res
|
|
|
|
class Permission:
|
|
'''If you need to define a specific read or write permission of a given
|
|
attribute of an Appy type, you use the specific boolean parameters
|
|
"specificReadPermission" or "specificWritePermission" for this attribute.
|
|
When you want to refer to those specific read or write permissions when
|
|
defining a workflow, for example, you need to use instances of
|
|
"ReadPermission" and "WritePermission", the 2 children classes of this
|
|
class. For example, if you need to refer to write permission of
|
|
attribute "t1" of class A, write: "WritePermission("A.t1") or
|
|
WritePermission("x.y.A.t1") if class A is not in the same module as
|
|
where you instantiate the class.'''
|
|
def __init__(self, fieldDescriptor):
|
|
self.fieldDescriptor = fieldDescriptor
|
|
|
|
class ReadPermission(Permission): pass
|
|
class WritePermission(Permission): pass
|
|
|
|
# ------------------------------------------------------------------------------
|
|
class Selection:
|
|
'''Instances of this class may be given as validator of a String, in order
|
|
to tell Appy that the validator is a selection that will be computed
|
|
dynamically.'''
|
|
pass
|
|
|
|
# ------------------------------------------------------------------------------
|
|
class Tool:
|
|
'''If you want so define a custom tool class, she must inherit from this
|
|
class.'''
|
|
class Flavour:
|
|
'''A flavour represents a given group of configuration options. If you want
|
|
to define a custom flavour class, she must inherit from this class.'''
|
|
def __init__(self, name): self.name = name
|
|
|
|
# ------------------------------------------------------------------------------
|
|
class Config:
|
|
'''If you want to specify some configuration parameters for appy.gen and
|
|
your application, please create an instance of this class and modify its
|
|
attributes. You may put your instance anywhere in your application
|
|
(main package, sub-package, etc).'''
|
|
|
|
# The default Config instance, used if the application does not give one.
|
|
defaultConfig = None
|
|
def getDefault():
|
|
if not Config.defaultConfig:
|
|
Config.defaultConfig = Config()
|
|
return Config.defaultConfig
|
|
getDefault = staticmethod(getDefault)
|
|
|
|
def __init__(self):
|
|
# For every language code that you specify in this list, appy.gen will
|
|
# produce and maintain translation files.
|
|
self.languages = ['en']
|
|
# People having one of these roles will be able to create instances
|
|
# of classes defined in your application.
|
|
self.defaultCreators = ['Manager', 'Owner']
|
|
# If True, the following flag will produce a minimalist Plone, where
|
|
# some actions, portlets or other stuff less relevant for building
|
|
# web applications, are removed or hidden. Using this produces
|
|
# effects on your whole Plone site!
|
|
self.minimalistPlone = False
|
|
# If you want to replace the Plone front page with a page coming from
|
|
# your application, use the following parameter. Setting
|
|
# frontPage = True will replace the Plone front page with a page
|
|
# whose content will come fron i18n label "front_page_text".
|
|
self.frontPage = False
|
|
# ------------------------------------------------------------------------------
|