459 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			459 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
 | |
| # ------------------------------------------------------------------------------
 | 
