# ------------------------------------------------------------------------------ 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 # ------------------------------------------------------------------------------