Eradicated Flavour and PodTemplate classes (for the latter, use Pod fields instead); Added a code analyser; Groups can now be slaves in master/slaves relationships; Refs have more params (show a confirmation popup before adding an object, add an object without creation form); Code for Refs has been refactored to comply with the new way to organize Types; Added a WebDAV client library.
This commit is contained in:
parent
9f4db88bdf
commit
990e16c6e7
|
@ -4,6 +4,7 @@
|
||||||
import sys, os.path
|
import sys, os.path
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
from appy.gen.generator import GeneratorError
|
from appy.gen.generator import GeneratorError
|
||||||
|
from appy.shared.utils import LinesCounter
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
ERROR_CODE = 1
|
ERROR_CODE = 1
|
||||||
|
@ -104,6 +105,8 @@ class GeneratorScript:
|
||||||
self.manageArgs(optParser, options, args)
|
self.manageArgs(optParser, options, args)
|
||||||
print 'Generating %s product in %s...' % (args[1], args[2])
|
print 'Generating %s product in %s...' % (args[1], args[2])
|
||||||
self.generateProduct(options, *args)
|
self.generateProduct(options, *args)
|
||||||
|
# Give the user some statistics about its code
|
||||||
|
LinesCounter(args[0]).run()
|
||||||
except GeneratorError, ge:
|
except GeneratorError, ge:
|
||||||
sys.stderr.write(str(ge))
|
sys.stderr.write(str(ge))
|
||||||
sys.stderr.write('\n')
|
sys.stderr.write('\n')
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
#!/usr/bin/python2.4.4
|
#!/usr/bin/python2.4.4
|
||||||
# Imports ----------------------------------------------------------------------
|
# Imports ----------------------------------------------------------------------
|
||||||
import os, os.path, shutil, re, zipfile, sys, ftplib, time
|
import os, os.path, shutil, re, zipfile, sys, ftplib, time
|
||||||
|
import appy
|
||||||
from appy.shared import appyPath
|
from appy.shared import appyPath
|
||||||
from appy.shared.utils import FolderDeleter
|
from appy.shared.utils import FolderDeleter, LinesCounter
|
||||||
from appy.bin.clean import Cleaner
|
from appy.bin.clean import Cleaner
|
||||||
from appy.gen.utils import produceNiceMessage
|
from appy.gen.utils import produceNiceMessage
|
||||||
|
|
||||||
|
@ -432,6 +433,8 @@ class Publisher:
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
Cleaner().run(verbose=False)
|
Cleaner().run(verbose=False)
|
||||||
|
# Perform a small analysis on the Appy code
|
||||||
|
LinesCounter(appy).run()
|
||||||
print 'Generating site in %s...' % self.genFolder
|
print 'Generating site in %s...' % self.genFolder
|
||||||
self.prepareGenFolder()
|
self.prepareGenFolder()
|
||||||
self.createDocToc()
|
self.createDocToc()
|
||||||
|
|
158
gen/__init__.py
158
gen/__init__.py
|
@ -5,7 +5,7 @@ from appy.gen.layout import Table
|
||||||
from appy.gen.layout import defaultFieldLayouts
|
from appy.gen.layout import defaultFieldLayouts
|
||||||
from appy.gen.po import PoMessage
|
from appy.gen.po import PoMessage
|
||||||
from appy.gen.utils import sequenceTypes, PageDescr, GroupDescr, Keywords, \
|
from appy.gen.utils import sequenceTypes, PageDescr, GroupDescr, Keywords, \
|
||||||
FileWrapper, getClassName
|
FileWrapper, getClassName, SomeObjects
|
||||||
from appy.shared.data import languages
|
from appy.shared.data import languages
|
||||||
|
|
||||||
# Default Appy permissions -----------------------------------------------------
|
# Default Appy permissions -----------------------------------------------------
|
||||||
|
@ -29,10 +29,10 @@ class Page:
|
||||||
|
|
||||||
class Group:
|
class Group:
|
||||||
'''Used for describing a group of widgets within a page.'''
|
'''Used for describing a group of widgets within a page.'''
|
||||||
def __init__(self, name, columns=['100%'], wide=True, style='fieldset',
|
def __init__(self, name, columns=['100%'], wide=True, style='section2',
|
||||||
hasLabel=True, hasDescr=False, hasHelp=False,
|
hasLabel=True, hasDescr=False, hasHelp=False,
|
||||||
hasHeaders=False, group=None, colspan=1, align='center',
|
hasHeaders=False, group=None, colspan=1, align='center',
|
||||||
valign='top'):
|
valign='top', css_class='', master=None, masterValue=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
# In its simpler form, field "columns" below can hold a list or tuple
|
# In its simpler form, field "columns" below can hold a list or tuple
|
||||||
# of column widths expressed as strings, that will be given as is in
|
# of column widths expressed as strings, that will be given as is in
|
||||||
|
@ -77,6 +77,26 @@ class Group:
|
||||||
self.columns = self.columns[:1]
|
self.columns = self.columns[:1]
|
||||||
# Header labels will be used as labels for the tabs.
|
# Header labels will be used as labels for the tabs.
|
||||||
self.hasHeaders = True
|
self.hasHeaders = True
|
||||||
|
self.css_class = css_class
|
||||||
|
self.master = None
|
||||||
|
self.masterValue = None
|
||||||
|
if self.master:
|
||||||
|
self._addMaster(self, master, masterValue)
|
||||||
|
|
||||||
|
def _addMaster(self, master, masterValue):
|
||||||
|
'''Specifies this group being a slave of another field: we will add css
|
||||||
|
classes allowing to show/hide, in Javascript, its widget according
|
||||||
|
to master value.'''
|
||||||
|
self.master = master
|
||||||
|
self.masterValue = masterValue
|
||||||
|
classes = 'slave_%s' % self.master.id
|
||||||
|
if type(self.masterValue) not in sequenceTypes:
|
||||||
|
masterValues = [self.masterValue]
|
||||||
|
else:
|
||||||
|
masterValues = self.masterValue
|
||||||
|
for masterValue in masterValues:
|
||||||
|
classes += ' slaveValue_%s_%s' % (self.master.id, masterValue)
|
||||||
|
self.css_class += ' ' + classes
|
||||||
|
|
||||||
def _setColumns(self):
|
def _setColumns(self):
|
||||||
'''Standardizes field "columns" as a list of Column instances. Indeed,
|
'''Standardizes field "columns" as a list of Column instances. Indeed,
|
||||||
|
@ -416,13 +436,12 @@ class Type:
|
||||||
'''When displaying p_obj on a given p_layoutType, must we show this
|
'''When displaying p_obj on a given p_layoutType, must we show this
|
||||||
field?'''
|
field?'''
|
||||||
isEdit = layoutType == 'edit'
|
isEdit = layoutType == 'edit'
|
||||||
# Do not show field if it is optional and not selected in flavour
|
# Do not show field if it is optional and not selected in tool
|
||||||
if self.optional:
|
if self.optional:
|
||||||
tool = obj.getTool()
|
tool = obj.getTool().appy()
|
||||||
flavour = tool.getFlavour(obj, appy=True)
|
fieldName = 'optionalFieldsFor%s' % obj.meta_type
|
||||||
flavourAttrName = 'optionalFieldsFor%s' % obj.meta_type
|
fieldValue = getattr(tool, fieldName, ())
|
||||||
flavourAttrValue = getattr(flavour, flavourAttrName, ())
|
if self.name not in fieldValue:
|
||||||
if self.name not in flavourAttrValue:
|
|
||||||
return False
|
return False
|
||||||
# Check if the user has the permission to view or edit the field
|
# Check if the user has the permission to view or edit the field
|
||||||
user = obj.portal_membership.getAuthenticatedMember()
|
user = obj.portal_membership.getAuthenticatedMember()
|
||||||
|
@ -568,11 +587,10 @@ class Type:
|
||||||
return self.default(obj.appy())
|
return self.default(obj.appy())
|
||||||
else:
|
else:
|
||||||
return self.default
|
return self.default
|
||||||
# If value is editable, get the default value from the flavour
|
# If value is editable, get the default value from the tool
|
||||||
portalTypeName = obj._appy_getPortalType(obj.REQUEST)
|
portalTypeName = obj._appy_getPortalType(obj.REQUEST)
|
||||||
tool = obj.getTool()
|
tool = obj.getTool().appy()
|
||||||
flavour = tool.getFlavour(portalTypeName, appy=True)
|
return getattr(tool, 'defaultValueFor%s' % self.labelId)
|
||||||
return getattr(flavour, 'defaultValueFor%s' % self.labelId)
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def getFormattedValue(self, obj, value):
|
def getFormattedValue(self, obj, value):
|
||||||
|
@ -1188,17 +1206,24 @@ class File(Type):
|
||||||
class Ref(Type):
|
class Ref(Type):
|
||||||
def __init__(self, klass=None, attribute=None, validator=None,
|
def __init__(self, klass=None, attribute=None, validator=None,
|
||||||
multiplicity=(0,1), index=None, default=None, optional=False,
|
multiplicity=(0,1), index=None, default=None, optional=False,
|
||||||
editDefault=False, add=False, link=True, unlink=False,
|
editDefault=False, add=False, addConfirm=False, noForm=False,
|
||||||
back=None, show=True, page='main', group=None, layouts=None,
|
link=True, unlink=False, back=None, show=True, page='main',
|
||||||
showHeaders=False, shownInfo=(), select=None, maxPerPage=30,
|
group=None, layouts=None, showHeaders=False, shownInfo=(),
|
||||||
move=0, indexed=False, searchable=False,
|
select=None, maxPerPage=30, move=0, indexed=False,
|
||||||
specificReadPermission=False, specificWritePermission=False,
|
searchable=False, specificReadPermission=False,
|
||||||
width=None, height=None, colspan=1, master=None,
|
specificWritePermission=False, width=None, height=None,
|
||||||
masterValue=None, focus=False, historized=False):
|
colspan=1, master=None, masterValue=None, focus=False,
|
||||||
|
historized=False):
|
||||||
self.klass = klass
|
self.klass = klass
|
||||||
self.attribute = attribute
|
self.attribute = attribute
|
||||||
# May the user add new objects through this ref ?
|
# May the user add new objects through this ref ?
|
||||||
self.add = add
|
self.add = add
|
||||||
|
# When the user adds a new object, must a confirmation popup be shown?
|
||||||
|
self.addConfirm = addConfirm
|
||||||
|
# If noForm is True, when clicking to create an object through this ref,
|
||||||
|
# the object will be created automatically, and no creation form will
|
||||||
|
# be presented to the user.
|
||||||
|
self.noForm = noForm
|
||||||
# May the user link existing objects through this ref?
|
# May the user link existing objects through this ref?
|
||||||
self.link = link
|
self.link = link
|
||||||
# May the user unlink existing objects?
|
# May the user unlink existing objects?
|
||||||
|
@ -1246,12 +1271,70 @@ class Ref(Type):
|
||||||
return obj.getBRefs(self.relationship)
|
return obj.getBRefs(self.relationship)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getValue(self, obj):
|
def getValue(self, obj, type='objects', noListIfSingleObj=False,
|
||||||
|
startNumber=None, someObjects=False):
|
||||||
|
'''Returns the objects linked to p_obj through Ref field "self".
|
||||||
|
- If p_type is "objects", it returns the Appy wrappers;
|
||||||
|
- If p_type is "zobjects", it returns the Zope objects;
|
||||||
|
- If p_type is "uids", it returns UIDs of objects (= strings).
|
||||||
|
|
||||||
|
|
||||||
|
* If p_startNumber is None, it returns all referred objects.
|
||||||
|
* If p_startNumber is a number, it returns self.maxPerPage objects,
|
||||||
|
starting at p_startNumber.
|
||||||
|
|
||||||
|
If p_noListIfSingleObj is True, it returns the single reference as
|
||||||
|
an object and not as a list.
|
||||||
|
|
||||||
|
If p_someObjects is True, it returns an instance of SomeObjects
|
||||||
|
instead of returning a list of references.'''
|
||||||
if self.isBack:
|
if self.isBack:
|
||||||
return obj._appy_getRefsBack(self.name, self.relationship,
|
getRefs = obj.reference_catalog.getBackReferences
|
||||||
noListIfSingleObj=True)
|
uids = [r.sourceUID for r in getRefs(obj, self.relationship)]
|
||||||
else:
|
else:
|
||||||
return obj._appy_getRefs(self.name, noListIfSingleObj=True).objects
|
uids = obj._appy_getSortedField(self.name)
|
||||||
|
batchNeeded = startNumber != None
|
||||||
|
exec 'refUids = obj.getRaw%s%s()' % (self.name[0].upper(),
|
||||||
|
self.name[1:])
|
||||||
|
# There may be too much UIDs in sortedField because these fields
|
||||||
|
# are not updated when objects are deleted. So we do it now.
|
||||||
|
# TODO: do such cleaning on object deletion ?
|
||||||
|
toDelete = []
|
||||||
|
for uid in uids:
|
||||||
|
if uid not in refUids:
|
||||||
|
toDelete.append(uid)
|
||||||
|
for uid in toDelete:
|
||||||
|
uids.remove(uid)
|
||||||
|
# Prepare the result: an instance of SomeObjects, that, in this case,
|
||||||
|
# represent a subset of all referred objects
|
||||||
|
res = SomeObjects()
|
||||||
|
res.totalNumber = res.batchSize = len(uids)
|
||||||
|
batchNeeded = startNumber != None
|
||||||
|
if batchNeeded:
|
||||||
|
res.batchSize = self.maxPerPage
|
||||||
|
if startNumber != None:
|
||||||
|
res.startNumber = startNumber
|
||||||
|
# Get the needed referred objects
|
||||||
|
i = res.startNumber
|
||||||
|
# Is it possible and more efficient to perform a single query in
|
||||||
|
# uid_catalog and get the result in the order of specified uids?
|
||||||
|
while i < (res.startNumber + res.batchSize):
|
||||||
|
if i >= res.totalNumber: break
|
||||||
|
# Retrieve every reference in the correct format according to p_type
|
||||||
|
if type == 'uids':
|
||||||
|
ref = uids[i]
|
||||||
|
else:
|
||||||
|
ref = obj.uid_catalog(UID=uids[i])[0].getObject()
|
||||||
|
if type == 'objects':
|
||||||
|
ref = ref.appy()
|
||||||
|
res.objects.append(ref)
|
||||||
|
i += 1
|
||||||
|
# Manage parameter p_noListIfSingleObj
|
||||||
|
if res.objects and noListIfSingleObj:
|
||||||
|
if self.multiplicity[1] == 1:
|
||||||
|
res.objects = res.objects[0]
|
||||||
|
if someObjects: return res
|
||||||
|
return res.objects
|
||||||
|
|
||||||
def getFormattedValue(self, obj, value):
|
def getFormattedValue(self, obj, value):
|
||||||
return value
|
return value
|
||||||
|
@ -1281,6 +1364,24 @@ class Ref(Type):
|
||||||
elif nbOfRefs > maxRef:
|
elif nbOfRefs > maxRef:
|
||||||
return obj.translate('max_ref_violated')
|
return obj.translate('max_ref_violated')
|
||||||
|
|
||||||
|
def store(self, obj, value):
|
||||||
|
'''Stores on p_obj, the p_value, which can be None, an object UID or a
|
||||||
|
list of UIDs coming from the request. This method is only called for
|
||||||
|
Ref fields with link=True.'''
|
||||||
|
# Security check
|
||||||
|
if not self.isShowable(obj, 'edit'): return
|
||||||
|
# Standardize the way p_value is expressed
|
||||||
|
uids = value
|
||||||
|
if not value: uids = []
|
||||||
|
if isinstance(value, basestring): uids = [value]
|
||||||
|
# Update the field storing on p_obj the ordered list of UIDs
|
||||||
|
sortedRefs = obj._appy_getSortedField(self.name)
|
||||||
|
del sortedRefs[:]
|
||||||
|
for uid in uids: sortedRefs.append(uid)
|
||||||
|
# Update the refs
|
||||||
|
refs = [obj.uid_catalog(UID=uid)[0].getObject() for uid in uids]
|
||||||
|
exec 'obj.set%s%s(refs)' % (self.name[0].upper(), self.name[1:])
|
||||||
|
|
||||||
class Computed(Type):
|
class Computed(Type):
|
||||||
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
def __init__(self, validator=None, multiplicity=(0,1), index=None,
|
||||||
default=None, optional=False, editDefault=False, show='view',
|
default=None, optional=False, editDefault=False, show='view',
|
||||||
|
@ -1645,10 +1746,6 @@ class Model: pass
|
||||||
class Tool(Model):
|
class Tool(Model):
|
||||||
'''If you want so define a custom tool class, she must inherit from this
|
'''If you want so define a custom tool class, she must inherit from this
|
||||||
class.'''
|
class.'''
|
||||||
class Flavour(Model):
|
|
||||||
'''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 User(Model):
|
class User(Model):
|
||||||
'''If you want to extend or modify the User class, subclass me.'''
|
'''If you want to extend or modify the User class, subclass me.'''
|
||||||
|
|
||||||
|
@ -1692,11 +1789,6 @@ class Config:
|
||||||
# If you don't need the portlet that appy.gen has generated for your
|
# If you don't need the portlet that appy.gen has generated for your
|
||||||
# application, set the following parameter to False.
|
# application, set the following parameter to False.
|
||||||
self.showPortlet = True
|
self.showPortlet = True
|
||||||
# Default number of flavours. It will be used for generating i18n labels
|
|
||||||
# for classes in every flavour. Indeed, every flavour can name its
|
|
||||||
# concepts differently. For example, class Thing in flavour 2 may have
|
|
||||||
# i18n label "MyProject_Thing_2".
|
|
||||||
self.numberOfFlavours = 2
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Special field "type" is mandatory for every class. If one class does not
|
# Special field "type" is mandatory for every class. If one class does not
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import os, os.path, sys, parser, symbol, token, types
|
import os, os.path, sys, parser, symbol, token, types
|
||||||
from appy.gen import Type, State, Config, Tool, Flavour, User
|
from appy.gen import Type, State, Config, Tool, User
|
||||||
from appy.gen.descriptors import *
|
from appy.gen.descriptors import *
|
||||||
from appy.gen.utils import produceNiceMessage
|
from appy.gen.utils import produceNiceMessage
|
||||||
import appy.pod, appy.pod.renderer
|
import appy.pod, appy.pod.renderer
|
||||||
|
@ -133,8 +133,7 @@ class Generator:
|
||||||
# Default descriptor classes
|
# Default descriptor classes
|
||||||
self.descriptorClasses = {
|
self.descriptorClasses = {
|
||||||
'class': ClassDescriptor, 'tool': ClassDescriptor,
|
'class': ClassDescriptor, 'tool': ClassDescriptor,
|
||||||
'flavour': ClassDescriptor, 'user': ClassDescriptor,
|
'user': ClassDescriptor, 'workflow': WorkflowDescriptor}
|
||||||
'workflow': WorkflowDescriptor}
|
|
||||||
# The following dict contains a series of replacements that need to be
|
# The following dict contains a series of replacements that need to be
|
||||||
# applied to file templates to generate files.
|
# applied to file templates to generate files.
|
||||||
self.repls = {'applicationName': self.applicationName,
|
self.repls = {'applicationName': self.applicationName,
|
||||||
|
@ -143,7 +142,6 @@ class Generator:
|
||||||
# List of Appy classes and workflows found in the application
|
# List of Appy classes and workflows found in the application
|
||||||
self.classes = []
|
self.classes = []
|
||||||
self.tool = None
|
self.tool = None
|
||||||
self.flavour = None
|
|
||||||
self.user = None
|
self.user = None
|
||||||
self.workflows = []
|
self.workflows = []
|
||||||
self.initialize()
|
self.initialize()
|
||||||
|
@ -224,19 +222,13 @@ class Generator:
|
||||||
# of their definition).
|
# of their definition).
|
||||||
attrs = astClasses[moduleElem.__name__].attributes
|
attrs = astClasses[moduleElem.__name__].attributes
|
||||||
if appyType == 'class':
|
if appyType == 'class':
|
||||||
# Determine the class type (standard, tool, flavour...)
|
# Determine the class type (standard, tool, user...)
|
||||||
if issubclass(moduleElem, Tool):
|
if issubclass(moduleElem, Tool):
|
||||||
if not self.tool:
|
if not self.tool:
|
||||||
klass = self.descriptorClasses['tool']
|
klass = self.descriptorClasses['tool']
|
||||||
self.tool = klass(moduleElem, attrs, self)
|
self.tool = klass(moduleElem, attrs, self)
|
||||||
else:
|
else:
|
||||||
self.tool.update(moduleElem, attrs)
|
self.tool.update(moduleElem, attrs)
|
||||||
elif issubclass(moduleElem, Flavour):
|
|
||||||
if not self.flavour:
|
|
||||||
klass = self.descriptorClasses['flavour']
|
|
||||||
self.flavour = klass(moduleElem, attrs, self)
|
|
||||||
else:
|
|
||||||
self.flavour.update(moduleElem, attrs)
|
|
||||||
elif issubclass(moduleElem, User):
|
elif issubclass(moduleElem, User):
|
||||||
if not self.user:
|
if not self.user:
|
||||||
klass = self.descriptorClasses['user']
|
klass = self.descriptorClasses['user']
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import types, copy
|
import types, copy
|
||||||
from model import ModelClass, Flavour, flavourAttributePrefixes
|
from model import ModelClass, Tool, toolFieldPrefixes
|
||||||
from utils import stringify
|
from utils import stringify
|
||||||
import appy.gen
|
import appy.gen
|
||||||
import appy.gen.descriptors
|
import appy.gen.descriptors
|
||||||
|
@ -43,12 +43,13 @@ class FieldDescriptor:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Field %s, %s>' % (self.fieldName, self.classDescr)
|
return '<Field %s, %s>' % (self.fieldName, self.classDescr)
|
||||||
|
|
||||||
def getFlavourAttributeMessage(self, fieldName):
|
def getToolFieldMessage(self, fieldName):
|
||||||
'''Some attributes generated on the Flavour class need a specific
|
'''Some attributes generated on the Tool class need a specific
|
||||||
default message, returned by this method.'''
|
default message, returned by this method.'''
|
||||||
res = fieldName
|
res = fieldName
|
||||||
for prefix in flavourAttributePrefixes:
|
for prefix in toolFieldPrefixes:
|
||||||
if fieldName.startswith(prefix):
|
fullPrefix = prefix + 'For'
|
||||||
|
if fieldName.startswith(fullPrefix):
|
||||||
messageId = 'MSG_%s' % prefix
|
messageId = 'MSG_%s' % prefix
|
||||||
res = getattr(PoMessage, messageId)
|
res = getattr(PoMessage, messageId)
|
||||||
if res.find('%s') != -1:
|
if res.find('%s') != -1:
|
||||||
|
@ -66,8 +67,8 @@ class FieldDescriptor:
|
||||||
produceNice = True
|
produceNice = True
|
||||||
default = self.fieldName
|
default = self.fieldName
|
||||||
# Some attributes need a specific predefined message
|
# Some attributes need a specific predefined message
|
||||||
if isinstance(self.classDescr, FlavourClassDescriptor):
|
if isinstance(self.classDescr, ToolClassDescriptor):
|
||||||
default = self.getFlavourAttributeMessage(self.fieldName)
|
default = self.getToolFieldMessage(self.fieldName)
|
||||||
if default != self.fieldName: produceNice = False
|
if default != self.fieldName: produceNice = False
|
||||||
msg = PoMessage(msgId, '', default)
|
msg = PoMessage(msgId, '', default)
|
||||||
if produceNice:
|
if produceNice:
|
||||||
|
@ -88,9 +89,7 @@ class FieldDescriptor:
|
||||||
self.generator.labels.append(poMsg)
|
self.generator.labels.append(poMsg)
|
||||||
|
|
||||||
def walkAction(self):
|
def walkAction(self):
|
||||||
'''How to generate an action field ? We generate an Archetypes String
|
'''Generates the i18n-related labels.'''
|
||||||
field.'''
|
|
||||||
# Add action-specific i18n messages
|
|
||||||
for suffix in ('ok', 'ko'):
|
for suffix in ('ok', 'ko'):
|
||||||
label = '%s_%s_action_%s' % (self.classDescr.name, self.fieldName,
|
label = '%s_%s_action_%s' % (self.classDescr.name, self.fieldName,
|
||||||
suffix)
|
suffix)
|
||||||
|
@ -98,6 +97,10 @@ class FieldDescriptor:
|
||||||
getattr(PoMessage, 'ACTION_%s' % suffix.upper()))
|
getattr(PoMessage, 'ACTION_%s' % suffix.upper()))
|
||||||
self.generator.labels.append(msg)
|
self.generator.labels.append(msg)
|
||||||
self.classDescr.labelsToPropagate.append(msg)
|
self.classDescr.labelsToPropagate.append(msg)
|
||||||
|
if self.appyType.confirm:
|
||||||
|
label = '%s_%s_confirm' % (self.classDescr.name, self.fieldName)
|
||||||
|
msg = PoMessage(label, '', PoMessage.CONFIRM)
|
||||||
|
self.generator.labels.append(msg)
|
||||||
|
|
||||||
def walkRef(self):
|
def walkRef(self):
|
||||||
'''How to generate a Ref?'''
|
'''How to generate a Ref?'''
|
||||||
|
@ -115,6 +118,11 @@ class FieldDescriptor:
|
||||||
poMsg = PoMessage(backLabel, '', self.appyType.back.attribute)
|
poMsg = PoMessage(backLabel, '', self.appyType.back.attribute)
|
||||||
poMsg.produceNiceDefault()
|
poMsg.produceNiceDefault()
|
||||||
self.generator.labels.append(poMsg)
|
self.generator.labels.append(poMsg)
|
||||||
|
# Add the label for the confirm message if relevant
|
||||||
|
if self.appyType.addConfirm:
|
||||||
|
label = '%s_%s_addConfirm' % (self.classDescr.name, self.fieldName)
|
||||||
|
msg = PoMessage(label, '', PoMessage.CONFIRM)
|
||||||
|
self.generator.labels.append(msg)
|
||||||
|
|
||||||
def walkPod(self):
|
def walkPod(self):
|
||||||
# Add i18n-specific messages
|
# Add i18n-specific messages
|
||||||
|
@ -123,8 +131,8 @@ class FieldDescriptor:
|
||||||
msg = PoMessage(label, '', PoMessage.POD_ASKACTION)
|
msg = PoMessage(label, '', PoMessage.POD_ASKACTION)
|
||||||
self.generator.labels.append(msg)
|
self.generator.labels.append(msg)
|
||||||
self.classDescr.labelsToPropagate.append(msg)
|
self.classDescr.labelsToPropagate.append(msg)
|
||||||
# Add the POD-related fields on the Flavour
|
# Add the POD-related fields on the Tool
|
||||||
Flavour._appy_addPodRelatedFields(self)
|
Tool._appy_addPodRelatedFields(self)
|
||||||
|
|
||||||
notToValidateFields = ('Info', 'Computed', 'Action', 'Pod')
|
notToValidateFields = ('Info', 'Computed', 'Action', 'Pod')
|
||||||
def walkAppyType(self):
|
def walkAppyType(self):
|
||||||
|
@ -133,10 +141,10 @@ class FieldDescriptor:
|
||||||
# Manage things common to all Appy types
|
# Manage things common to all Appy types
|
||||||
# - optional ?
|
# - optional ?
|
||||||
if self.appyType.optional:
|
if self.appyType.optional:
|
||||||
Flavour._appy_addOptionalField(self)
|
Tool._appy_addOptionalField(self)
|
||||||
# - edit default value ?
|
# - edit default value ?
|
||||||
if self.appyType.editDefault:
|
if self.appyType.editDefault:
|
||||||
Flavour._appy_addDefaultField(self)
|
Tool._appy_addDefaultField(self)
|
||||||
# - put an index on this field?
|
# - put an index on this field?
|
||||||
if self.appyType.indexed and \
|
if self.appyType.indexed and \
|
||||||
(self.fieldName not in ('title', 'description')):
|
(self.fieldName not in ('title', 'description')):
|
||||||
|
@ -229,8 +237,8 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
|
||||||
# (because they contain the class name). But at this time we don't know
|
# (because they contain the class name). But at this time we don't know
|
||||||
# yet every sub-class. So we store those labels here; the Generator
|
# yet every sub-class. So we store those labels here; the Generator
|
||||||
# will propagate them later.
|
# will propagate them later.
|
||||||
self.flavourFieldsToPropagate = [] # For this class, some fields have
|
self.toolFieldsToPropagate = [] # For this class, some fields have
|
||||||
# been defined on the Flavour class. Those fields need to be defined
|
# been defined on the Tool class. Those fields need to be defined
|
||||||
# for child classes of this class as well, but at this time we don't
|
# for child classes of this class as well, but at this time we don't
|
||||||
# know yet every sub-class. So we store field definitions here; the
|
# know yet every sub-class. So we store field definitions here; the
|
||||||
# Generator will propagate them later.
|
# Generator will propagate them later.
|
||||||
|
@ -251,7 +259,7 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
|
||||||
def generateSchema(self, configClass=False):
|
def generateSchema(self, configClass=False):
|
||||||
'''Generates the corresponding Archetypes schema in self.schema. If we
|
'''Generates the corresponding Archetypes schema in self.schema. If we
|
||||||
are generating a schema for a class that is in the configuration
|
are generating a schema for a class that is in the configuration
|
||||||
(tool, flavour, etc) we must avoid having attributes that rely on
|
(tool, user, etc) we must avoid having attributes that rely on
|
||||||
the configuration (ie attributes that are optional, with
|
the configuration (ie attributes that are optional, with
|
||||||
editDefault=True, etc).'''
|
editDefault=True, etc).'''
|
||||||
for attrName in self.orderedAttributes:
|
for attrName in self.orderedAttributes:
|
||||||
|
@ -286,13 +294,6 @@ class ClassDescriptor(appy.gen.descriptors.ClassDescriptor):
|
||||||
res = self.klass.__dict__['root']
|
res = self.klass.__dict__['root']
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def isPod(self):
|
|
||||||
'''May this class be associated with POD templates?.'''
|
|
||||||
res = False
|
|
||||||
if self.klass.__dict__.has_key('pod') and self.klass.__dict__['pod']:
|
|
||||||
res = True
|
|
||||||
return res
|
|
||||||
|
|
||||||
def isFolder(self, klass=None):
|
def isFolder(self, klass=None):
|
||||||
'''Must self.klass be a folder? If klass is not None, this method tests
|
'''Must self.klass be a folder? If klass is not None, this method tests
|
||||||
it on p_klass instead of self.klass.'''
|
it on p_klass instead of self.klass.'''
|
||||||
|
@ -375,6 +376,7 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
'''Represents the POD-specific fields that must be added to the tool.'''
|
'''Represents the POD-specific fields that must be added to the tool.'''
|
||||||
def __init__(self, klass, generator):
|
def __init__(self, klass, generator):
|
||||||
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
|
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
|
||||||
|
self.attributesByClass = klass._appy_classes
|
||||||
self.modelClass = self.klass
|
self.modelClass = self.klass
|
||||||
self.predefined = True
|
self.predefined = True
|
||||||
self.customized = False
|
self.customized = False
|
||||||
|
@ -395,42 +397,6 @@ class ToolClassDescriptor(ClassDescriptor):
|
||||||
def generateSchema(self):
|
def generateSchema(self):
|
||||||
ClassDescriptor.generateSchema(self, configClass=True)
|
ClassDescriptor.generateSchema(self, configClass=True)
|
||||||
|
|
||||||
class FlavourClassDescriptor(ClassDescriptor):
|
|
||||||
'''Represents an Archetypes-compliant class that corresponds to the Flavour
|
|
||||||
for the generated application.'''
|
|
||||||
def __init__(self, klass, generator):
|
|
||||||
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
|
|
||||||
self.attributesByClass = klass._appy_classes
|
|
||||||
self.modelClass = self.klass
|
|
||||||
self.predefined = True
|
|
||||||
self.customized = False
|
|
||||||
def getParents(self, allClasses=()):
|
|
||||||
res = ['Flavour']
|
|
||||||
if self.customized:
|
|
||||||
res.append('%s.%s' % (self.klass.__module__, self.klass.__name__))
|
|
||||||
return res
|
|
||||||
def update(self, klass, attributes):
|
|
||||||
'''This method is called by the generator when he finds a custom flavour
|
|
||||||
definition. We must then add the custom flavour elements in this
|
|
||||||
default Flavour descriptor.'''
|
|
||||||
self.orderedAttributes += attributes
|
|
||||||
self.klass = klass
|
|
||||||
self.customized = True
|
|
||||||
def isFolder(self, klass=None): return True
|
|
||||||
def isRoot(self): return False
|
|
||||||
def generateSchema(self):
|
|
||||||
ClassDescriptor.generateSchema(self, configClass=True)
|
|
||||||
|
|
||||||
class PodTemplateClassDescriptor(ClassDescriptor):
|
|
||||||
'''Represents a POD template.'''
|
|
||||||
def __init__(self, klass, generator):
|
|
||||||
ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator)
|
|
||||||
self.modelClass = self.klass
|
|
||||||
self.predefined = True
|
|
||||||
self.customized = False
|
|
||||||
def getParents(self, allClasses=()): return ['PodTemplate']
|
|
||||||
def isRoot(self): return False
|
|
||||||
|
|
||||||
class UserClassDescriptor(ClassDescriptor):
|
class UserClassDescriptor(ClassDescriptor):
|
||||||
'''Represents an Archetypes-compliant class that corresponds to the User
|
'''Represents an Archetypes-compliant class that corresponds to the User
|
||||||
for the generated application.'''
|
for the generated application.'''
|
||||||
|
|
|
@ -8,11 +8,8 @@ from appy.gen import *
|
||||||
from appy.gen.po import PoMessage, PoFile, PoParser
|
from appy.gen.po import PoMessage, PoFile, PoParser
|
||||||
from appy.gen.generator import Generator as AbstractGenerator
|
from appy.gen.generator import Generator as AbstractGenerator
|
||||||
from appy.gen.utils import getClassName
|
from appy.gen.utils import getClassName
|
||||||
from model import ModelClass, PodTemplate, User, Flavour, Tool
|
from model import ModelClass, User, Tool
|
||||||
from descriptors import FieldDescriptor, ClassDescriptor, \
|
from descriptors import *
|
||||||
WorkflowDescriptor, ToolClassDescriptor, \
|
|
||||||
FlavourClassDescriptor, PodTemplateClassDescriptor, \
|
|
||||||
UserClassDescriptor
|
|
||||||
|
|
||||||
# Common methods that need to be defined on every Archetype class --------------
|
# Common methods that need to be defined on every Archetype class --------------
|
||||||
COMMON_METHODS = '''
|
COMMON_METHODS = '''
|
||||||
|
@ -29,22 +26,18 @@ class Generator(AbstractGenerator):
|
||||||
poExtensions = ('.po', '.pot')
|
poExtensions = ('.po', '.pot')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
Flavour._appy_clean()
|
Tool._appy_clean()
|
||||||
AbstractGenerator.__init__(self, *args, **kwargs)
|
AbstractGenerator.__init__(self, *args, **kwargs)
|
||||||
# Set our own Descriptor classes
|
# Set our own Descriptor classes
|
||||||
self.descriptorClasses['class'] = ClassDescriptor
|
self.descriptorClasses['class'] = ClassDescriptor
|
||||||
self.descriptorClasses['workflow'] = WorkflowDescriptor
|
self.descriptorClasses['workflow'] = WorkflowDescriptor
|
||||||
# Create our own Tool, Flavour and PodTemplate instances
|
# Create our own Tool and User instances
|
||||||
self.tool = ToolClassDescriptor(Tool, self)
|
self.tool = ToolClassDescriptor(Tool, self)
|
||||||
self.flavour = FlavourClassDescriptor(Flavour, self)
|
|
||||||
self.podTemplate = PodTemplateClassDescriptor(PodTemplate, self)
|
|
||||||
self.user = UserClassDescriptor(User, self)
|
self.user = UserClassDescriptor(User, self)
|
||||||
# i18n labels to generate
|
# i18n labels to generate
|
||||||
self.labels = [] # i18n labels
|
self.labels = [] # i18n labels
|
||||||
self.toolName = '%sTool' % self.applicationName
|
self.toolName = '%sTool' % self.applicationName
|
||||||
self.flavourName = '%sFlavour' % self.applicationName
|
|
||||||
self.toolInstanceName = 'portal_%s' % self.applicationName.lower()
|
self.toolInstanceName = 'portal_%s' % self.applicationName.lower()
|
||||||
self.podTemplateName = '%sPodTemplate' % self.applicationName
|
|
||||||
self.userName = '%sUser' % self.applicationName
|
self.userName = '%sUser' % self.applicationName
|
||||||
self.portletName = '%s_portlet' % self.applicationName.lower()
|
self.portletName = '%s_portlet' % self.applicationName.lower()
|
||||||
self.queryName = '%s_query' % self.applicationName.lower()
|
self.queryName = '%s_query' % self.applicationName.lower()
|
||||||
|
@ -55,10 +48,9 @@ class Generator(AbstractGenerator):
|
||||||
commonMethods = COMMON_METHODS % \
|
commonMethods = COMMON_METHODS % \
|
||||||
(self.toolInstanceName, self.applicationName)
|
(self.toolInstanceName, self.applicationName)
|
||||||
self.repls.update(
|
self.repls.update(
|
||||||
{'toolName': self.toolName, 'flavourName': self.flavourName,
|
{'toolName': self.toolName, 'portletName': self.portletName,
|
||||||
'portletName': self.portletName, 'queryName': self.queryName,
|
'queryName': self.queryName, 'userName': self.userName,
|
||||||
'toolInstanceName': self.toolInstanceName,
|
'toolInstanceName': self.toolInstanceName,
|
||||||
'podTemplateName': self.podTemplateName, 'userName': self.userName,
|
|
||||||
'commonMethods': commonMethods})
|
'commonMethods': commonMethods})
|
||||||
self.referers = {}
|
self.referers = {}
|
||||||
|
|
||||||
|
@ -145,7 +137,6 @@ class Generator(AbstractGenerator):
|
||||||
msg('goto_last', '', msg.GOTO_LAST),
|
msg('goto_last', '', msg.GOTO_LAST),
|
||||||
msg('goto_source', '', msg.GOTO_SOURCE),
|
msg('goto_source', '', msg.GOTO_SOURCE),
|
||||||
msg('whatever', '', msg.WHATEVER),
|
msg('whatever', '', msg.WHATEVER),
|
||||||
msg('confirm', '', msg.CONFIRM),
|
|
||||||
msg('yes', '', msg.YES),
|
msg('yes', '', msg.YES),
|
||||||
msg('no', '', msg.NO),
|
msg('no', '', msg.NO),
|
||||||
msg('field_required', '', msg.FIELD_REQUIRED),
|
msg('field_required', '', msg.FIELD_REQUIRED),
|
||||||
|
@ -321,15 +312,14 @@ class Generator(AbstractGenerator):
|
||||||
appClasses.append('%s.%s' % (k.__module__, k.__name__))
|
appClasses.append('%s.%s' % (k.__module__, k.__name__))
|
||||||
repls['appClasses'] = "[%s]" % ','.join(appClasses)
|
repls['appClasses'] = "[%s]" % ','.join(appClasses)
|
||||||
# Compute lists of class names
|
# Compute lists of class names
|
||||||
allClassNames = '"%s",' % self.flavourName
|
allClassNames = '"%s",' % self.userName
|
||||||
allClassNames += '"%s",' % self.podTemplateName
|
|
||||||
appClassNames = ','.join(['"%s"' % c.name for c in self.classes])
|
appClassNames = ','.join(['"%s"' % c.name for c in self.classes])
|
||||||
allClassNames += appClassNames
|
allClassNames += appClassNames
|
||||||
repls['allClassNames'] = allClassNames
|
repls['allClassNames'] = allClassNames
|
||||||
repls['appClassNames'] = appClassNames
|
repls['appClassNames'] = appClassNames
|
||||||
# Compute classes whose instances must not be catalogued.
|
# Compute classes whose instances must not be catalogued.
|
||||||
catalogMap = ''
|
catalogMap = ''
|
||||||
blackClasses = [self.toolName, self.flavourName, self.podTemplateName]
|
blackClasses = [self.toolName]
|
||||||
for blackClass in blackClasses:
|
for blackClass in blackClasses:
|
||||||
catalogMap += "catalogMap['%s'] = {}\n" % blackClass
|
catalogMap += "catalogMap['%s'] = {}\n" % blackClass
|
||||||
catalogMap += "catalogMap['%s']['black'] = " \
|
catalogMap += "catalogMap['%s']['black'] = " \
|
||||||
|
@ -464,28 +454,30 @@ class Generator(AbstractGenerator):
|
||||||
repls['workflows'] = workflows
|
repls['workflows'] = workflows
|
||||||
self.copyFile('workflows.py', repls, destFolder='Extensions')
|
self.copyFile('workflows.py', repls, destFolder='Extensions')
|
||||||
|
|
||||||
def generateWrapperProperty(self, name):
|
def generateWrapperProperty(self, name, type):
|
||||||
'''Generates the getter for attribute p_name.'''
|
'''Generates the getter for attribute p_name.'''
|
||||||
res = ' def get_%s(self):\n ' % name
|
res = ' def get_%s(self):\n ' % name
|
||||||
if name == 'title':
|
if name == 'title':
|
||||||
res += 'return self.o.Title()\n'
|
res += 'return self.o.Title()\n'
|
||||||
else:
|
else:
|
||||||
res += 'return self.o.getAppyType("%s").getValue(self.o)\n' % name
|
suffix = ''
|
||||||
|
if type == 'Ref': suffix = ', noListIfSingleObj=True'
|
||||||
|
res += 'return self.o.getAppyType("%s").getValue(self.o%s)\n' % \
|
||||||
|
(name, suffix)
|
||||||
res += ' %s = property(get_%s)\n\n' % (name, name)
|
res += ' %s = property(get_%s)\n\n' % (name, name)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getClasses(self, include=None):
|
def getClasses(self, include=None):
|
||||||
'''Returns the descriptors for all the classes in the generated
|
'''Returns the descriptors for all the classes in the generated
|
||||||
gen-application. If p_include is "all", it includes the descriptors
|
gen-application. If p_include is "all", it includes the descriptors
|
||||||
for the config-related classes (tool, flavour, etc); if
|
for the config-related classes (tool, user, etc); if p_include is
|
||||||
p_include is "allButTool", it includes the same descriptors, the
|
"allButTool", it includes the same descriptors, the tool excepted;
|
||||||
tool excepted; if p_include is "custom", it includes descriptors
|
if p_include is "custom", it includes descriptors for the
|
||||||
for the config-related classes for which the user has created a
|
config-related classes for which the user has created a sub-class.'''
|
||||||
sub-class.'''
|
|
||||||
if not include: return self.classes
|
if not include: return self.classes
|
||||||
else:
|
else:
|
||||||
res = self.classes[:]
|
res = self.classes[:]
|
||||||
configClasses = [self.tool,self.flavour,self.podTemplate,self.user]
|
configClasses = [self.tool, self.user]
|
||||||
if include == 'all':
|
if include == 'all':
|
||||||
res += configClasses
|
res += configClasses
|
||||||
elif include == 'allButTool':
|
elif include == 'allButTool':
|
||||||
|
@ -547,24 +539,25 @@ class Generator(AbstractGenerator):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
attrValue = getattr(c.modelClass, attrName)
|
attrValue = getattr(c.modelClass, attrName)
|
||||||
if isinstance(attrValue, Type):
|
if isinstance(attrValue, Type):
|
||||||
wrapperDef += self.generateWrapperProperty(attrName)
|
wrapperDef += self.generateWrapperProperty(attrName,
|
||||||
|
attrValue.type)
|
||||||
# Generate properties for back references
|
# Generate properties for back references
|
||||||
if self.referers.has_key(c.name):
|
if self.referers.has_key(c.name):
|
||||||
for refDescr, rel in self.referers[c.name]:
|
for refDescr, rel in self.referers[c.name]:
|
||||||
attrName = refDescr.appyType.back.attribute
|
attrName = refDescr.appyType.back.attribute
|
||||||
wrapperDef += self.generateWrapperProperty(attrName)
|
wrapperDef += self.generateWrapperProperty(attrName, 'Ref')
|
||||||
if not titleFound:
|
if not titleFound:
|
||||||
# Implicitly, the title will be added by Archetypes. So I need
|
# Implicitly, the title will be added by Archetypes. So I need
|
||||||
# to define a property for it.
|
# to define a property for it.
|
||||||
wrapperDef += self.generateWrapperProperty('title')
|
wrapperDef += self.generateWrapperProperty('title', 'String')
|
||||||
if c.customized:
|
if c.customized:
|
||||||
# For custom tool and flavour, add a call to a method that
|
# For custom tool, add a call to a method that allows to
|
||||||
# allows to customize elements from the base class.
|
# customize elements from the base class.
|
||||||
wrapperDef += " if hasattr(%s, 'update'):\n " \
|
wrapperDef += " if hasattr(%s, 'update'):\n " \
|
||||||
"%s.update(%s)\n" % (parentClasses[1], parentClasses[1],
|
"%s.update(%s)\n" % (parentClasses[1], parentClasses[1],
|
||||||
parentClasses[0])
|
parentClasses[0])
|
||||||
# For custom tool and flavour, add security declaration that
|
# For custom tool, add security declaration that will allow to
|
||||||
# will allow to call their methods from ZPTs.
|
# call their methods from ZPTs.
|
||||||
for parentClass in parentClasses:
|
for parentClass in parentClasses:
|
||||||
wrapperDef += " for elem in dir(%s):\n " \
|
wrapperDef += " for elem in dir(%s):\n " \
|
||||||
"if not elem.startswith('_'): security.declarePublic" \
|
"if not elem.startswith('_'): security.declarePublic" \
|
||||||
|
@ -576,8 +569,6 @@ class Generator(AbstractGenerator):
|
||||||
repls['imports'] = '\n'.join(imports)
|
repls['imports'] = '\n'.join(imports)
|
||||||
repls['wrappers'] = '\n'.join(wrappers)
|
repls['wrappers'] = '\n'.join(wrappers)
|
||||||
repls['toolBody'] = Tool._appy_getBody()
|
repls['toolBody'] = Tool._appy_getBody()
|
||||||
repls['flavourBody'] = Flavour._appy_getBody()
|
|
||||||
repls['podTemplateBody'] = PodTemplate._appy_getBody()
|
|
||||||
repls['userBody'] = User._appy_getBody()
|
repls['userBody'] = User._appy_getBody()
|
||||||
self.copyFile('appyWrappers.py', repls, destFolder='Extensions')
|
self.copyFile('appyWrappers.py', repls, destFolder='Extensions')
|
||||||
|
|
||||||
|
@ -613,72 +604,49 @@ class Generator(AbstractGenerator):
|
||||||
|
|
||||||
def generateTool(self):
|
def generateTool(self):
|
||||||
'''Generates the Plone tool that corresponds to this application.'''
|
'''Generates the Plone tool that corresponds to this application.'''
|
||||||
# Generate the tool class in itself and related i18n messages
|
|
||||||
t = self.toolName
|
|
||||||
Msg = PoMessage
|
Msg = PoMessage
|
||||||
repls = self.repls.copy()
|
# Create Tool-related i18n-related messages
|
||||||
# Manage predefined fields
|
self.labels += [
|
||||||
Tool.flavours.klass = Flavour
|
Msg(self.toolName, '', Msg.CONFIG % self.applicationName),
|
||||||
if self.flavour.customized:
|
Msg('%s_edit_descr' % self.toolName, '', ' ')]
|
||||||
Tool.flavours.klass = self.flavour.klass
|
|
||||||
|
# Tune the Ref field between Tool and User
|
||||||
Tool.users.klass = User
|
Tool.users.klass = User
|
||||||
if self.user.customized:
|
if self.user.customized:
|
||||||
Tool.users.klass = self.user.klass
|
Tool.users.klass = self.user.klass
|
||||||
|
|
||||||
|
# Before generating the Tool class, finalize it with query result
|
||||||
|
# columns, with fields to propagate, workflow-related fields.
|
||||||
|
for classDescr in self.classes:
|
||||||
|
for fieldName, fieldType in classDescr.toolFieldsToPropagate:
|
||||||
|
for childDescr in classDescr.getChildren():
|
||||||
|
childFieldName = fieldName % childDescr.name
|
||||||
|
fieldType.group = childDescr.klass.__name__
|
||||||
|
Tool._appy_addField(childFieldName, fieldType, childDescr)
|
||||||
|
if classDescr.isRoot():
|
||||||
|
# We must be able to configure query results from the tool.
|
||||||
|
Tool._appy_addQueryResultColumns(classDescr)
|
||||||
|
# Add the search-related fields.
|
||||||
|
Tool._appy_addSearchRelatedFields(classDescr)
|
||||||
|
importMean = classDescr.getCreateMean('Import')
|
||||||
|
if importMean:
|
||||||
|
Tool._appy_addImportRelatedFields(classDescr)
|
||||||
|
Tool._appy_addWorkflowFields(self.user)
|
||||||
|
# Complete self.tool.orderedAttributes from the attributes that we
|
||||||
|
# just added to the Tool model class.
|
||||||
|
for fieldName in Tool._appy_attributes:
|
||||||
|
if fieldName not in self.tool.orderedAttributes:
|
||||||
|
self.tool.orderedAttributes.append(fieldName)
|
||||||
self.tool.generateSchema()
|
self.tool.generateSchema()
|
||||||
|
|
||||||
|
# Generate the Tool class
|
||||||
|
repls = self.repls.copy()
|
||||||
|
repls['metaTypes'] = [c.name for c in self.classes]
|
||||||
repls['fields'] = self.tool.schema
|
repls['fields'] = self.tool.schema
|
||||||
repls['methods'] = self.tool.methods
|
repls['methods'] = self.tool.methods
|
||||||
repls['wrapperClass'] = '%s_Wrapper' % self.tool.name
|
repls['wrapperClass'] = '%s_Wrapper' % self.tool.name
|
||||||
self.copyFile('ToolTemplate.py', repls, destName='%s.py'% self.toolName)
|
self.copyFile('ToolTemplate.py', repls, destName='%s.py'% self.toolName)
|
||||||
repls = self.repls.copy()
|
|
||||||
# Create i18n-related messages
|
|
||||||
self.labels += [
|
|
||||||
Msg(self.toolName, '', Msg.CONFIG % self.applicationName),
|
|
||||||
Msg('%s_edit_descr' % self.toolName, '', ' ')]
|
|
||||||
# Before generating the Flavour class, finalize it with query result
|
|
||||||
# columns, with fields to propagate, workflow-related fields.
|
|
||||||
for classDescr in self.classes:
|
|
||||||
for fieldName, fieldType in classDescr.flavourFieldsToPropagate:
|
|
||||||
for childDescr in classDescr.getChildren():
|
|
||||||
childFieldName = fieldName % childDescr.name
|
|
||||||
fieldType.group = childDescr.klass.__name__
|
|
||||||
Flavour._appy_addField(childFieldName,fieldType,childDescr)
|
|
||||||
if classDescr.isRoot():
|
|
||||||
# We must be able to configure query results from the flavour.
|
|
||||||
Flavour._appy_addQueryResultColumns(classDescr)
|
|
||||||
# Add the search-related fields.
|
|
||||||
Flavour._appy_addSearchRelatedFields(classDescr)
|
|
||||||
importMean = classDescr.getCreateMean('Import')
|
|
||||||
if importMean:
|
|
||||||
Flavour._appy_addImportRelatedFields(classDescr)
|
|
||||||
Flavour._appy_addWorkflowFields(self.flavour)
|
|
||||||
Flavour._appy_addWorkflowFields(self.podTemplate)
|
|
||||||
Flavour._appy_addWorkflowFields(self.user)
|
|
||||||
# Complete self.flavour.orderedAttributes from the attributes that we
|
|
||||||
# just added to the Flavour model class.
|
|
||||||
for fieldName in Flavour._appy_attributes:
|
|
||||||
if fieldName not in self.flavour.orderedAttributes:
|
|
||||||
self.flavour.orderedAttributes.append(fieldName)
|
|
||||||
# Generate the flavour class and related i18n messages
|
|
||||||
self.flavour.generateSchema()
|
|
||||||
self.labels += [ Msg(self.flavourName, '', Msg.FLAVOUR),
|
|
||||||
Msg('%s_edit_descr' % self.flavourName, '', ' ')]
|
|
||||||
repls = self.repls.copy()
|
|
||||||
repls['fields'] = self.flavour.schema
|
|
||||||
repls['methods'] = self.flavour.methods
|
|
||||||
repls['wrapperClass'] = '%s_Wrapper' % self.flavour.name
|
|
||||||
repls['metaTypes'] = [c.name for c in self.classes]
|
|
||||||
self.copyFile('FlavourTemplate.py', repls,
|
|
||||||
destName='%s.py'% self.flavourName)
|
|
||||||
# Generate the PodTemplate class
|
|
||||||
self.podTemplate.generateSchema()
|
|
||||||
self.labels += [ Msg(self.podTemplateName, '', Msg.POD_TEMPLATE),
|
|
||||||
Msg('%s_edit_descr' % self.podTemplateName, '', ' ')]
|
|
||||||
repls = self.repls.copy()
|
|
||||||
repls['fields'] = self.podTemplate.schema
|
|
||||||
repls['methods'] = self.podTemplate.methods
|
|
||||||
repls['wrapperClass'] = '%s_Wrapper' % self.podTemplate.name
|
|
||||||
self.copyFile('PodTemplate.py', repls,
|
|
||||||
destName='%s.py' % self.podTemplateName)
|
|
||||||
# Generate the User class
|
# Generate the User class
|
||||||
self.user.generateSchema()
|
self.user.generateSchema()
|
||||||
self.labels += [ Msg(self.userName, '', Msg.USER),
|
self.labels += [ Msg(self.userName, '', Msg.USER),
|
||||||
|
@ -687,32 +655,28 @@ class Generator(AbstractGenerator):
|
||||||
repls['fields'] = self.user.schema
|
repls['fields'] = self.user.schema
|
||||||
repls['methods'] = self.user.methods
|
repls['methods'] = self.user.methods
|
||||||
repls['wrapperClass'] = '%s_Wrapper' % self.user.name
|
repls['wrapperClass'] = '%s_Wrapper' % self.user.name
|
||||||
self.copyFile('UserTemplate.py', repls,
|
self.copyFile('UserTemplate.py', repls,destName='%s.py' % self.userName)
|
||||||
destName='%s.py' % self.userName)
|
|
||||||
|
|
||||||
def generateClass(self, classDescr):
|
def generateClass(self, classDescr):
|
||||||
'''Is called each time an Appy class is found in the application, for
|
'''Is called each time an Appy class is found in the application, for
|
||||||
generating the corresponding Archetype class and schema.'''
|
generating the corresponding Archetype class and schema.'''
|
||||||
k = classDescr.klass
|
k = classDescr.klass
|
||||||
print 'Generating %s.%s (gen-class)...' % (k.__module__, k.__name__)
|
print 'Generating %s.%s (gen-class)...' % (k.__module__, k.__name__)
|
||||||
# Add, for this class, the needed configuration attributes on Flavour
|
|
||||||
if classDescr.isPod():
|
|
||||||
Flavour._appy_addPodField(classDescr)
|
|
||||||
if not classDescr.isAbstract():
|
if not classDescr.isAbstract():
|
||||||
Flavour._appy_addWorkflowFields(classDescr)
|
Tool._appy_addWorkflowFields(classDescr)
|
||||||
# Determine base archetypes schema and class
|
# Determine base archetypes schema and class
|
||||||
baseClass = 'BaseContent'
|
baseClass = 'BaseContent'
|
||||||
baseSchema = 'BaseSchema'
|
baseSchema = 'BaseSchema'
|
||||||
if classDescr.isFolder():
|
if classDescr.isFolder():
|
||||||
baseClass = 'OrderedBaseFolder'
|
baseClass = 'OrderedBaseFolder'
|
||||||
baseSchema = 'OrderedBaseFolderSchema'
|
baseSchema = 'OrderedBaseFolderSchema'
|
||||||
parents = [baseClass, 'ClassMixin']
|
parents = [baseClass, 'BaseMixin']
|
||||||
imports = []
|
imports = []
|
||||||
implements = [baseClass]
|
implements = [baseClass]
|
||||||
for baseClass in classDescr.klass.__bases__:
|
for baseClass in classDescr.klass.__bases__:
|
||||||
if self.determineAppyType(baseClass) == 'class':
|
if self.determineAppyType(baseClass) == 'class':
|
||||||
bcName = getClassName(baseClass)
|
bcName = getClassName(baseClass)
|
||||||
parents.remove('ClassMixin')
|
parents.remove('BaseMixin')
|
||||||
parents.append(bcName)
|
parents.append(bcName)
|
||||||
implements.append(bcName)
|
implements.append(bcName)
|
||||||
imports.append('from %s import %s' % (bcName, bcName))
|
imports.append('from %s import %s' % (bcName, bcName))
|
||||||
|
@ -750,19 +714,6 @@ class Generator(AbstractGenerator):
|
||||||
classDescr.klass.__name__+'s')
|
classDescr.klass.__name__+'s')
|
||||||
poMsgPl.produceNiceDefault()
|
poMsgPl.produceNiceDefault()
|
||||||
self.labels.append(poMsgPl)
|
self.labels.append(poMsgPl)
|
||||||
# Create i18n labels for flavoured variants
|
|
||||||
for i in range(2, self.config.numberOfFlavours+1):
|
|
||||||
poMsg = PoMessage('%s_%d' % (classDescr.name, i), '',
|
|
||||||
classDescr.klass.__name__)
|
|
||||||
poMsg.produceNiceDefault()
|
|
||||||
self.labels.append(poMsg)
|
|
||||||
poMsgDescr = PoMessage('%s_%d_edit_descr' % (classDescr.name, i),
|
|
||||||
'', ' ')
|
|
||||||
self.labels.append(poMsgDescr)
|
|
||||||
poMsgPl = PoMessage('%s_%d_plural' % (classDescr.name, i), '',
|
|
||||||
classDescr.klass.__name__+'s')
|
|
||||||
poMsgPl.produceNiceDefault()
|
|
||||||
self.labels.append(poMsgPl)
|
|
||||||
# Create i18n labels for searches
|
# Create i18n labels for searches
|
||||||
for search in classDescr.getSearches(classDescr.klass):
|
for search in classDescr.getSearches(classDescr.klass):
|
||||||
searchLabel = '%s_search_%s' % (classDescr.name, search.name)
|
searchLabel = '%s_search_%s' % (classDescr.name, search.name)
|
||||||
|
|
|
@ -144,7 +144,6 @@ class PloneInstaller:
|
||||||
appFolder = getattr(site, self.productName)
|
appFolder = getattr(site, self.productName)
|
||||||
for className in self.config.rootClasses:
|
for className in self.config.rootClasses:
|
||||||
permission = self.getAddPermission(className)
|
permission = self.getAddPermission(className)
|
||||||
print 'Permission is', permission
|
|
||||||
appFolder.manage_permission(permission, (), acquire=0)
|
appFolder.manage_permission(permission, (), acquire=0)
|
||||||
else:
|
else:
|
||||||
appFolder = getattr(site, self.productName)
|
appFolder = getattr(site, self.productName)
|
||||||
|
@ -221,78 +220,33 @@ class PloneInstaller:
|
||||||
current_catalogs.remove(catalog)
|
current_catalogs.remove(catalog)
|
||||||
atTool.setCatalogsByType(meta_type, list(current_catalogs))
|
atTool.setCatalogsByType(meta_type, list(current_catalogs))
|
||||||
|
|
||||||
def findPodFile(self, klass, podTemplateName):
|
|
||||||
'''Finds the file that corresponds to p_podTemplateName for p_klass.'''
|
|
||||||
res = None
|
|
||||||
exec 'import %s' % klass.__module__
|
|
||||||
exec 'moduleFile = %s.__file__' % klass.__module__
|
|
||||||
folderName = os.path.dirname(moduleFile)
|
|
||||||
fileName = os.path.join(folderName, '%s.odt' % podTemplateName)
|
|
||||||
if os.path.isfile(fileName):
|
|
||||||
res = fileName
|
|
||||||
return res
|
|
||||||
|
|
||||||
def updatePodTemplates(self):
|
def updatePodTemplates(self):
|
||||||
'''Creates or updates the POD templates in flavours according to pod
|
'''Creates or updates the POD templates in the tool according to pod
|
||||||
declarations in the application classes.'''
|
declarations in the application classes.'''
|
||||||
# Creates or updates the old-way class-related templates
|
# Creates the templates for Pod fields if they do not exist.
|
||||||
i = -1
|
|
||||||
for klass in self.appClasses:
|
|
||||||
i += 1
|
|
||||||
if klass.__dict__.has_key('pod'):
|
|
||||||
pod = getattr(klass, 'pod')
|
|
||||||
if isinstance(pod, bool):
|
|
||||||
podTemplates = [klass.__name__]
|
|
||||||
else:
|
|
||||||
podTemplates = pod
|
|
||||||
for templateName in podTemplates:
|
|
||||||
fileName = self.findPodFile(klass, templateName)
|
|
||||||
if fileName:
|
|
||||||
# Create the corresponding PodTemplate in all flavours
|
|
||||||
for flavour in self.appyTool.flavours:
|
|
||||||
podId='%s_%s' % (self.appClassNames[i],templateName)
|
|
||||||
podAttr = 'podTemplatesFor%s'% self.appClassNames[i]
|
|
||||||
allPodTemplates = getattr(flavour, podAttr)
|
|
||||||
if allPodTemplates:
|
|
||||||
if isinstance(allPodTemplates, list):
|
|
||||||
allIds = [p.id for p in allPodTemplates]
|
|
||||||
else:
|
|
||||||
allIds = [allPodTemplates.id]
|
|
||||||
else:
|
|
||||||
allIds = []
|
|
||||||
if podId not in allIds:
|
|
||||||
# Create a PodTemplate instance
|
|
||||||
f = file(fileName)
|
|
||||||
flavour.create(podAttr, id=podId, podTemplate=f,
|
|
||||||
title=produceNiceMessage(templateName))
|
|
||||||
f.close()
|
|
||||||
# Creates the new-way templates for Pod fields if they do not exist.
|
|
||||||
for contentType, appyTypes in self.attributes.iteritems():
|
for contentType, appyTypes in self.attributes.iteritems():
|
||||||
appyClass = self.tool.getAppyClass(contentType)
|
appyClass = self.tool.getAppyClass(contentType)
|
||||||
if not appyClass: continue # May be an abstract class
|
if not appyClass: continue # May be an abstract class
|
||||||
for appyType in appyTypes:
|
for appyType in appyTypes:
|
||||||
if appyType.type == 'Pod':
|
if appyType.type != 'Pod': continue
|
||||||
# For every flavour, find the attribute that stores the
|
# Find the attribute that stores the template, and store on
|
||||||
# template, and store on it the default one specified in
|
# it the default one specified in the appyType if no
|
||||||
# the appyType if no template is stored yet.
|
# template is stored yet.
|
||||||
for flavour in self.appyTool.flavours:
|
attrName = self.appyTool.getAttributeName(
|
||||||
attrName = flavour.getAttributeName(
|
'podTemplate', appyClass, appyType.name)
|
||||||
'podTemplate', appyClass, appyType.name)
|
fileObject = getattr(self.appyTool, attrName)
|
||||||
fileObject = getattr(flavour, attrName)
|
if not fileObject or (fileObject.size == 0):
|
||||||
if not fileObject or (fileObject.size == 0):
|
# There is no file. Put the one specified in the appyType.
|
||||||
# There is no file. Put the one specified in the
|
fileName = os.path.join(self.appyTool.getDiskFolder(),
|
||||||
# appyType.
|
appyType.template)
|
||||||
fileName=os.path.join(self.appyTool.getDiskFolder(),
|
if os.path.exists(fileName):
|
||||||
appyType.template)
|
setattr(self.appyTool, attrName, fileName)
|
||||||
if os.path.exists(fileName):
|
else:
|
||||||
setattr(flavour, attrName, fileName)
|
self.appyTool.log('Template "%s" was not found!' % \
|
||||||
else:
|
fileName, type='error')
|
||||||
self.appyTool.log(
|
|
||||||
'Template "%s" was not found!' % \
|
|
||||||
fileName, type='error')
|
|
||||||
|
|
||||||
def installTool(self):
|
def installTool(self):
|
||||||
'''Configures the application tool and flavours.'''
|
'''Configures the application tool.'''
|
||||||
# Register the tool in Plone
|
# Register the tool in Plone
|
||||||
try:
|
try:
|
||||||
self.ploneSite.manage_addProduct[
|
self.ploneSite.manage_addProduct[
|
||||||
|
@ -333,9 +287,6 @@ class PloneInstaller:
|
||||||
else:
|
else:
|
||||||
self.tool.createOrUpdate(True, None)
|
self.tool.createOrUpdate(True, None)
|
||||||
|
|
||||||
if not self.appyTool.flavours:
|
|
||||||
# Create the default flavour
|
|
||||||
self.appyTool.create('flavours', title=self.productName, number=1)
|
|
||||||
self.updatePodTemplates()
|
self.updatePodTemplates()
|
||||||
|
|
||||||
# Uncatalog tool
|
# Uncatalog tool
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
from appy.gen.plone25.mixins import AbstractMixin
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
class ClassMixin(AbstractMixin):
|
|
||||||
_appy_meta_type = 'Class'
|
|
||||||
# ------------------------------------------------------------------------------
|
|
|
@ -1,263 +0,0 @@
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
import os, os.path, time, types
|
|
||||||
from StringIO import StringIO
|
|
||||||
from appy.shared import mimeTypes
|
|
||||||
from appy.shared.utils import getOsTempFolder
|
|
||||||
import appy.pod
|
|
||||||
from appy.pod.renderer import Renderer
|
|
||||||
import appy.gen
|
|
||||||
from appy.gen import Type
|
|
||||||
from appy.gen.plone25.mixins import AbstractMixin
|
|
||||||
from appy.gen.plone25.descriptors import ClassDescriptor
|
|
||||||
|
|
||||||
# Errors -----------------------------------------------------------------------
|
|
||||||
DELETE_TEMP_DOC_ERROR = 'A temporary document could not be removed. %s.'
|
|
||||||
POD_ERROR = 'An error occurred while generating the document. Please ' \
|
|
||||||
'contact the system administrator.'
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
class FlavourMixin(AbstractMixin):
|
|
||||||
_appy_meta_type = 'Flavour'
|
|
||||||
def getPortalType(self, metaTypeOrAppyClass):
|
|
||||||
'''Returns the name of the portal_type that is based on
|
|
||||||
p_metaTypeOrAppyType in this flavour.'''
|
|
||||||
return self.getParentNode().getPortalType(metaTypeOrAppyClass)
|
|
||||||
|
|
||||||
def registerPortalTypes(self):
|
|
||||||
'''Registers, into portal_types, the portal types which are specific
|
|
||||||
to this flavour.'''
|
|
||||||
i = -1
|
|
||||||
registeredFactoryTypes = self.portal_factory.getFactoryTypes().keys()
|
|
||||||
factoryTypesToRegister = []
|
|
||||||
appName = self.getProductConfig().PROJECTNAME
|
|
||||||
for metaTypeName in self.allMetaTypes:
|
|
||||||
i += 1
|
|
||||||
portalTypeName = '%s_%d' % (metaTypeName, self.number)
|
|
||||||
# If the portal type corresponding to the meta type is
|
|
||||||
# registered in portal_factory (in the model:
|
|
||||||
# use_portal_factory=True), we must also register the new
|
|
||||||
# portal_type we are currently creating.
|
|
||||||
if metaTypeName in registeredFactoryTypes:
|
|
||||||
factoryTypesToRegister.append(portalTypeName)
|
|
||||||
if not hasattr(self.portal_types, portalTypeName) and \
|
|
||||||
hasattr(self.portal_types, metaTypeName):
|
|
||||||
# Indeed abstract meta_types have no associated portal_type
|
|
||||||
typeInfoName = "%s: %s (%s)" % (appName, metaTypeName,
|
|
||||||
metaTypeName)
|
|
||||||
self.portal_types.manage_addTypeInformation(
|
|
||||||
getattr(self.portal_types, metaTypeName).meta_type,
|
|
||||||
id=portalTypeName, typeinfo_name=typeInfoName)
|
|
||||||
# Set the human readable title explicitly
|
|
||||||
portalType = getattr(self.portal_types, portalTypeName)
|
|
||||||
portalType.title = portalTypeName
|
|
||||||
# Associate a workflow for this new portal type.
|
|
||||||
pf = self.portal_workflow
|
|
||||||
workflowChain = pf.getChainForPortalType(metaTypeName)
|
|
||||||
pf.setChainForPortalTypes([portalTypeName],workflowChain)
|
|
||||||
# Copy actions from the base portal type
|
|
||||||
basePortalType = getattr(self.portal_types, metaTypeName)
|
|
||||||
portalType._actions = tuple(basePortalType._cloneActions())
|
|
||||||
# Copy aliases from the base portal type
|
|
||||||
portalType.setMethodAliases(basePortalType.getMethodAliases())
|
|
||||||
# Update the factory tool with the list of types to register
|
|
||||||
self.portal_factory.manage_setPortalFactoryTypes(
|
|
||||||
listOfTypeIds=factoryTypesToRegister+registeredFactoryTypes)
|
|
||||||
|
|
||||||
def getClassFolder(self, className):
|
|
||||||
'''Return the folder related to p_className.'''
|
|
||||||
return getattr(self, className)
|
|
||||||
|
|
||||||
def getAvailablePodTemplates(self, obj, phase='main'):
|
|
||||||
'''Returns the POD templates which are available for generating a
|
|
||||||
document from p_obj.'''
|
|
||||||
appySelf = self.appy()
|
|
||||||
fieldName = 'podTemplatesFor%s' % obj.meta_type
|
|
||||||
res = []
|
|
||||||
podTemplates = getattr(appySelf, fieldName, [])
|
|
||||||
if not isinstance(podTemplates, list):
|
|
||||||
podTemplates = [podTemplates]
|
|
||||||
res = [r.o for r in podTemplates if r.podPhase == phase]
|
|
||||||
hasParents = True
|
|
||||||
klass = obj.__class__
|
|
||||||
while hasParents:
|
|
||||||
parent = klass.__bases__[-1]
|
|
||||||
if hasattr(parent, 'wrapperClass'):
|
|
||||||
fieldName = 'podTemplatesFor%s' % parent.meta_type
|
|
||||||
podTemplates = getattr(appySelf, fieldName, [])
|
|
||||||
if not isinstance(podTemplates, list):
|
|
||||||
podTemplates = [podTemplates]
|
|
||||||
res += [r.o for r in podTemplates if r.podPhase == phase]
|
|
||||||
klass = parent
|
|
||||||
else:
|
|
||||||
hasParents = False
|
|
||||||
return res
|
|
||||||
|
|
||||||
def getMaxShownTemplates(self, obj):
|
|
||||||
attrName = 'getPodMaxShownTemplatesFor%s' % obj.meta_type
|
|
||||||
return getattr(self, attrName)()
|
|
||||||
|
|
||||||
def getPodInfo(self, ploneObj, fieldName):
|
|
||||||
'''Returns POD-related information about Pod field p_fieldName defined
|
|
||||||
on class whose p_ploneObj is an instance of.'''
|
|
||||||
res = {}
|
|
||||||
appyClass = self.getParentNode().getAppyClass(ploneObj.meta_type)
|
|
||||||
appyFlavour = self.appy()
|
|
||||||
n = appyFlavour.getAttributeName('formats', appyClass, fieldName)
|
|
||||||
res['formats'] = getattr(appyFlavour, n)
|
|
||||||
n = appyFlavour.getAttributeName('podTemplate', appyClass, fieldName)
|
|
||||||
res['template'] = getattr(appyFlavour, n)
|
|
||||||
appyType = ploneObj.getAppyType(fieldName)
|
|
||||||
res['title'] = self.translate(appyType.labelId)
|
|
||||||
res['context'] = appyType.context
|
|
||||||
res['action'] = appyType.action
|
|
||||||
return res
|
|
||||||
|
|
||||||
def generateDocument(self):
|
|
||||||
'''Generates the document:
|
|
||||||
- from a PodTemplate instance if it is a class-wide pod template;
|
|
||||||
- from field-related info on the flavour if it is a Pod field.
|
|
||||||
UID of object that is the template target is given in the request.'''
|
|
||||||
rq = self.REQUEST
|
|
||||||
appyTool = self.getParentNode().appy()
|
|
||||||
# Get the object
|
|
||||||
objectUid = rq.get('objectUid')
|
|
||||||
obj = self.uid_catalog(UID=objectUid)[0].getObject()
|
|
||||||
appyObj = obj.appy()
|
|
||||||
# Get information about the document to render. Information comes from
|
|
||||||
# a PodTemplate instance or from the flavour itself, depending on
|
|
||||||
# whether we generate a doc from a class-wide template or from a pod
|
|
||||||
# field.
|
|
||||||
templateUid = rq.get('templateUid', None)
|
|
||||||
specificPodContext = None
|
|
||||||
if templateUid:
|
|
||||||
podTemplate = self.uid_catalog(UID=templateUid)[0].getObject()
|
|
||||||
appyPt = podTemplate.appy()
|
|
||||||
format = podTemplate.getPodFormat()
|
|
||||||
template = appyPt.podTemplate.content
|
|
||||||
podTitle = podTemplate.Title()
|
|
||||||
doAction = False
|
|
||||||
else:
|
|
||||||
fieldName = rq.get('fieldName')
|
|
||||||
format = rq.get('podFormat')
|
|
||||||
podInfo = self.getPodInfo(obj, fieldName)
|
|
||||||
template = podInfo['template'].content
|
|
||||||
podTitle = podInfo['title']
|
|
||||||
if podInfo['context']:
|
|
||||||
if type(podInfo['context']) == types.FunctionType:
|
|
||||||
specificPodContext = podInfo['context'](appyObj)
|
|
||||||
else:
|
|
||||||
specificPodContext = podInfo['context']
|
|
||||||
doAction = rq.get('askAction') == 'True'
|
|
||||||
# Temporary file where to generate the result
|
|
||||||
tempFileName = '%s/%s_%f.%s' % (
|
|
||||||
getOsTempFolder(), obj.UID(), time.time(), format)
|
|
||||||
# Define parameters to pass to the appy.pod renderer
|
|
||||||
currentUser = self.portal_membership.getAuthenticatedMember()
|
|
||||||
podContext = {'tool': appyTool, 'flavour': self.appy(),
|
|
||||||
'user': currentUser, 'self': appyObj,
|
|
||||||
'now': self.getProductConfig().DateTime(),
|
|
||||||
'projectFolder': appyTool.getDiskFolder(),
|
|
||||||
}
|
|
||||||
if specificPodContext:
|
|
||||||
podContext.update(specificPodContext)
|
|
||||||
if templateUid:
|
|
||||||
podContext['podTemplate'] = appyPt
|
|
||||||
rendererParams = {'template': StringIO(template),
|
|
||||||
'context': podContext,
|
|
||||||
'result': tempFileName}
|
|
||||||
if appyTool.unoEnabledPython:
|
|
||||||
rendererParams['pythonWithUnoPath'] = appyTool.unoEnabledPython
|
|
||||||
if appyTool.openOfficePort:
|
|
||||||
rendererParams['ooPort'] = appyTool.openOfficePort
|
|
||||||
# Launch the renderer
|
|
||||||
try:
|
|
||||||
renderer = Renderer(**rendererParams)
|
|
||||||
renderer.run()
|
|
||||||
except appy.pod.PodError, pe:
|
|
||||||
if not os.path.exists(tempFileName):
|
|
||||||
# In some (most?) cases, when OO returns an error, the result is
|
|
||||||
# nevertheless generated.
|
|
||||||
appyTool.log(str(pe), type='error')
|
|
||||||
appyTool.say(POD_ERROR)
|
|
||||||
return self.goto(rq.get('HTTP_REFERER'))
|
|
||||||
# Open the temp file on the filesystem
|
|
||||||
f = file(tempFileName, 'rb')
|
|
||||||
res = f.read()
|
|
||||||
# Identify the filename to return
|
|
||||||
fileName = u'%s-%s' % (obj.Title().decode('utf-8'), podTitle)
|
|
||||||
fileName = appyTool.normalize(fileName)
|
|
||||||
response = obj.REQUEST.RESPONSE
|
|
||||||
response.setHeader('Content-Type', mimeTypes[format])
|
|
||||||
response.setHeader('Content-Disposition', 'inline;filename="%s.%s"'\
|
|
||||||
% (fileName, format))
|
|
||||||
f.close()
|
|
||||||
# Execute the related action if relevant
|
|
||||||
if doAction and podInfo['action']:
|
|
||||||
podInfo['action'](appyObj, podContext)
|
|
||||||
# Returns the doc and removes the temp file
|
|
||||||
try:
|
|
||||||
os.remove(tempFileName)
|
|
||||||
except OSError, oe:
|
|
||||||
appyTool.log(DELETE_TEMP_DOC_ERROR % str(oe), type='warning')
|
|
||||||
except IOError, ie:
|
|
||||||
appyTool.log(DELETE_TEMP_DOC_ERROR % str(ie), type='warning')
|
|
||||||
return res
|
|
||||||
|
|
||||||
def getAttr(self, name):
|
|
||||||
'''Gets on this flavour attribute named p_attrName. Useful because we
|
|
||||||
can't use getattr directly in Zope Page Templates.'''
|
|
||||||
return getattr(self.appy(), name, None)
|
|
||||||
|
|
||||||
def _appy_getAllFields(self, contentType):
|
|
||||||
'''Returns the (translated) names of fields of p_contentType.'''
|
|
||||||
res = []
|
|
||||||
for appyType in self.getProductConfig().attributes[contentType]:
|
|
||||||
if appyType.name != 'title': # Will be included by default.
|
|
||||||
label = '%s_%s' % (contentType, appyType.name)
|
|
||||||
res.append((appyType.name, self.translate(label)))
|
|
||||||
# Add object state
|
|
||||||
res.append(('workflowState', self.translate('workflow_state')))
|
|
||||||
return res
|
|
||||||
|
|
||||||
def _appy_getSearchableFields(self, contentType):
|
|
||||||
'''Returns the (translated) names of fields that may be searched on
|
|
||||||
objects of type p_contentType (=indexed fields).'''
|
|
||||||
res = []
|
|
||||||
for appyType in self.getProductConfig().attributes[contentType]:
|
|
||||||
if appyType.indexed:
|
|
||||||
res.append((appyType.name, self.translate(appyType.labelId)))
|
|
||||||
return res
|
|
||||||
|
|
||||||
def getSearchableFields(self, contentType):
|
|
||||||
'''Returns, among the list of all searchable fields (see method above),
|
|
||||||
the list of fields that the user has configured in the flavour as
|
|
||||||
being effectively used in the search screen.'''
|
|
||||||
res = []
|
|
||||||
fieldNames = getattr(self.appy(), 'searchFieldsFor%s' % contentType, ())
|
|
||||||
for name in fieldNames:
|
|
||||||
appyType = self.getAppyType(name, asDict=True,className=contentType)
|
|
||||||
res.append(appyType)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def getImportElements(self, contentType):
|
|
||||||
'''Returns the list of elements that can be imported from p_path for
|
|
||||||
p_contentType.'''
|
|
||||||
tool = self.getParentNode()
|
|
||||||
appyClass = tool.getAppyClass(contentType)
|
|
||||||
importParams = tool.getCreateMeans(appyClass)['import']
|
|
||||||
onElement = importParams['onElement'].__get__('')
|
|
||||||
sortMethod = importParams['sort']
|
|
||||||
if sortMethod: sortMethod = sortMethod.__get__('')
|
|
||||||
elems = []
|
|
||||||
importPath = getattr(self, 'importPathFor%s' % contentType)
|
|
||||||
for elem in os.listdir(importPath):
|
|
||||||
elemFullPath = os.path.join(importPath, elem)
|
|
||||||
elemInfo = onElement(elemFullPath)
|
|
||||||
if elemInfo:
|
|
||||||
elemInfo.insert(0, elemFullPath) # To the result, I add the full
|
|
||||||
# path of the elem, which will not be shown.
|
|
||||||
elems.append(elemInfo)
|
|
||||||
if sortMethod:
|
|
||||||
elems = sortMethod(elems)
|
|
||||||
return [importParams['headers'], elems]
|
|
||||||
# ------------------------------------------------------------------------------
|
|
|
@ -1,7 +0,0 @@
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
from appy.gen.plone25.mixins import AbstractMixin
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
class PodTemplateMixin(AbstractMixin):
|
|
||||||
_appy_meta_type = 'PodTemplate'
|
|
||||||
# ------------------------------------------------------------------------------
|
|
|
@ -1,22 +1,28 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import re, os, os.path, Cookie
|
import re, os, os.path, time, Cookie, StringIO, types
|
||||||
|
from appy.shared import mimeTypes
|
||||||
from appy.shared.utils import getOsTempFolder
|
from appy.shared.utils import getOsTempFolder
|
||||||
|
import appy.pod
|
||||||
|
from appy.pod.renderer import Renderer
|
||||||
import appy.gen
|
import appy.gen
|
||||||
from appy.gen import Type, Search, Selection
|
from appy.gen import Type, Search, Selection
|
||||||
from appy.gen.utils import SomeObjects, sequenceTypes, getClassName
|
from appy.gen.utils import SomeObjects, sequenceTypes, getClassName
|
||||||
from appy.gen.plone25.mixins import AbstractMixin
|
from appy.gen.plone25.mixins import BaseMixin
|
||||||
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
|
|
||||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||||
from appy.gen.plone25.descriptors import ClassDescriptor
|
from appy.gen.plone25.descriptors import ClassDescriptor
|
||||||
|
|
||||||
|
# Errors -----------------------------------------------------------------------
|
||||||
|
DELETE_TEMP_DOC_ERROR = 'A temporary document could not be removed. %s.'
|
||||||
|
POD_ERROR = 'An error occurred while generating the document. Please ' \
|
||||||
|
'contact the system administrator.'
|
||||||
jsMessages = ('no_elem_selected', 'delete_confirm')
|
jsMessages = ('no_elem_selected', 'delete_confirm')
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class ToolMixin(AbstractMixin):
|
class ToolMixin(BaseMixin):
|
||||||
_appy_meta_type = 'Tool'
|
_appy_meta_type = 'Tool'
|
||||||
def getPortalType(self, metaTypeOrAppyClass):
|
def getPortalType(self, metaTypeOrAppyClass):
|
||||||
'''Returns the name of the portal_type that is based on
|
'''Returns the name of the portal_type that is based on
|
||||||
p_metaTypeOrAppyType in this flavour.'''
|
p_metaTypeOrAppyType.'''
|
||||||
appName = self.getProductConfig().PROJECTNAME
|
appName = self.getProductConfig().PROJECTNAME
|
||||||
if not isinstance(metaTypeOrAppyClass, basestring):
|
if not isinstance(metaTypeOrAppyClass, basestring):
|
||||||
res = getClassName(metaTypeOrAppyClass, appName)
|
res = getClassName(metaTypeOrAppyClass, appName)
|
||||||
|
@ -25,52 +31,100 @@ class ToolMixin(AbstractMixin):
|
||||||
res = '%s%s' % (elems[1], elems[4])
|
res = '%s%s' % (elems[1], elems[4])
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getFlavour(self, contextObjOrPortalType, appy=False):
|
def getPodInfo(self, ploneObj, fieldName):
|
||||||
'''Gets the flavour that corresponds to p_contextObjOrPortalType.'''
|
'''Returns POD-related information about Pod field p_fieldName defined
|
||||||
if isinstance(contextObjOrPortalType, basestring):
|
on class whose p_ploneObj is an instance of.'''
|
||||||
portalTypeName = contextObjOrPortalType
|
res = {}
|
||||||
else:
|
appyClass = self.getAppyClass(ploneObj.meta_type)
|
||||||
# It is the contextObj, not a portal type name
|
|
||||||
portalTypeName = contextObjOrPortalType.portal_type
|
|
||||||
res = None
|
|
||||||
appyTool = self.appy()
|
appyTool = self.appy()
|
||||||
flavourNumber = None
|
n = appyTool.getAttributeName('formats', appyClass, fieldName)
|
||||||
nameElems = portalTypeName.split('_')
|
res['formats'] = getattr(appyTool, n)
|
||||||
if len(nameElems) > 1:
|
n = appyTool.getAttributeName('podTemplate', appyClass, fieldName)
|
||||||
try:
|
res['template'] = getattr(appyTool, n)
|
||||||
flavourNumber = int(nameElems[-1])
|
appyType = ploneObj.getAppyType(fieldName)
|
||||||
except ValueError:
|
res['title'] = self.translate(appyType.labelId)
|
||||||
pass
|
res['context'] = appyType.context
|
||||||
appName = self.getProductConfig().PROJECTNAME
|
res['action'] = appyType.action
|
||||||
if flavourNumber != None:
|
|
||||||
for flavour in appyTool.flavours:
|
|
||||||
if flavourNumber == flavour.number:
|
|
||||||
res = flavour
|
|
||||||
elif portalTypeName == ('%sFlavour' % appName):
|
|
||||||
# Current object is the Flavour itself. In this cas we simply
|
|
||||||
# return the wrapped contextObj. Here we are sure that
|
|
||||||
# contextObjOrPortalType is an object, not a portal type.
|
|
||||||
res = contextObjOrPortalType.appy()
|
|
||||||
if not res and appyTool.flavours:
|
|
||||||
res = appyTool.flavours[0]
|
|
||||||
# If appy=False, return the Plone object and not the Appy wrapper
|
|
||||||
# (this way, we avoid Zope security/access-related problems while
|
|
||||||
# using this object in Zope Page Templates)
|
|
||||||
if res and not appy:
|
|
||||||
res = res.o
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getFlavoursInfo(self):
|
def generateDocument(self):
|
||||||
'''Returns information about flavours.'''
|
'''Generates the document from field-related info. UID of object that
|
||||||
res = []
|
is the template target is given in the request.'''
|
||||||
|
rq = self.REQUEST
|
||||||
appyTool = self.appy()
|
appyTool = self.appy()
|
||||||
for flavour in appyTool.flavours:
|
# Get the object
|
||||||
if isinstance(flavour.o, FlavourMixin):
|
objectUid = rq.get('objectUid')
|
||||||
# This is a bug: sometimes other objects are associated as
|
obj = self.uid_catalog(UID=objectUid)[0].getObject()
|
||||||
# flavours.
|
appyObj = obj.appy()
|
||||||
res.append({'title': flavour.title, 'number':flavour.number})
|
# Get information about the document to render.
|
||||||
|
specificPodContext = None
|
||||||
|
fieldName = rq.get('fieldName')
|
||||||
|
format = rq.get('podFormat')
|
||||||
|
podInfo = self.getPodInfo(obj, fieldName)
|
||||||
|
template = podInfo['template'].content
|
||||||
|
podTitle = podInfo['title']
|
||||||
|
if podInfo['context']:
|
||||||
|
if type(podInfo['context']) == types.FunctionType:
|
||||||
|
specificPodContext = podInfo['context'](appyObj)
|
||||||
|
else:
|
||||||
|
specificPodContext = podInfo['context']
|
||||||
|
doAction = rq.get('askAction') == 'True'
|
||||||
|
# Temporary file where to generate the result
|
||||||
|
tempFileName = '%s/%s_%f.%s' % (
|
||||||
|
getOsTempFolder(), obj.UID(), time.time(), format)
|
||||||
|
# Define parameters to pass to the appy.pod renderer
|
||||||
|
currentUser = self.portal_membership.getAuthenticatedMember()
|
||||||
|
podContext = {'tool': appyTool, 'user': currentUser, 'self': appyObj,
|
||||||
|
'now': self.getProductConfig().DateTime(),
|
||||||
|
'projectFolder': appyTool.getDiskFolder(),
|
||||||
|
}
|
||||||
|
if specificPodContext:
|
||||||
|
podContext.update(specificPodContext)
|
||||||
|
rendererParams = {'template': StringIO.StringIO(template),
|
||||||
|
'context': podContext, 'result': tempFileName}
|
||||||
|
if appyTool.unoEnabledPython:
|
||||||
|
rendererParams['pythonWithUnoPath'] = appyTool.unoEnabledPython
|
||||||
|
if appyTool.openOfficePort:
|
||||||
|
rendererParams['ooPort'] = appyTool.openOfficePort
|
||||||
|
# Launch the renderer
|
||||||
|
try:
|
||||||
|
renderer = Renderer(**rendererParams)
|
||||||
|
renderer.run()
|
||||||
|
except appy.pod.PodError, pe:
|
||||||
|
if not os.path.exists(tempFileName):
|
||||||
|
# In some (most?) cases, when OO returns an error, the result is
|
||||||
|
# nevertheless generated.
|
||||||
|
appyTool.log(str(pe), type='error')
|
||||||
|
appyTool.say(POD_ERROR)
|
||||||
|
return self.goto(rq.get('HTTP_REFERER'))
|
||||||
|
# Open the temp file on the filesystem
|
||||||
|
f = file(tempFileName, 'rb')
|
||||||
|
res = f.read()
|
||||||
|
# Identify the filename to return
|
||||||
|
fileName = u'%s-%s' % (obj.Title().decode('utf-8'), podTitle)
|
||||||
|
fileName = appyTool.normalize(fileName)
|
||||||
|
response = obj.REQUEST.RESPONSE
|
||||||
|
response.setHeader('Content-Type', mimeTypes[format])
|
||||||
|
response.setHeader('Content-Disposition', 'inline;filename="%s.%s"'\
|
||||||
|
% (fileName, format))
|
||||||
|
f.close()
|
||||||
|
# Execute the related action if relevant
|
||||||
|
if doAction and podInfo['action']:
|
||||||
|
podInfo['action'](appyObj, podContext)
|
||||||
|
# Returns the doc and removes the temp file
|
||||||
|
try:
|
||||||
|
os.remove(tempFileName)
|
||||||
|
except OSError, oe:
|
||||||
|
appyTool.log(DELETE_TEMP_DOC_ERROR % str(oe), type='warning')
|
||||||
|
except IOError, ie:
|
||||||
|
appyTool.log(DELETE_TEMP_DOC_ERROR % str(ie), type='warning')
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def getAttr(self, name):
|
||||||
|
'''Gets attribute named p_attrName. Useful because we can't use getattr
|
||||||
|
directly in Zope Page Templates.'''
|
||||||
|
return getattr(self.appy(), name, None)
|
||||||
|
|
||||||
def getAppName(self):
|
def getAppName(self):
|
||||||
'''Returns the name of this application.'''
|
'''Returns the name of this application.'''
|
||||||
return self.getProductConfig().PROJECTNAME
|
return self.getProductConfig().PROJECTNAME
|
||||||
|
@ -86,6 +140,58 @@ class ToolMixin(AbstractMixin):
|
||||||
'''Returns the list of root classes for this application.'''
|
'''Returns the list of root classes for this application.'''
|
||||||
return self.getProductConfig().rootClasses
|
return self.getProductConfig().rootClasses
|
||||||
|
|
||||||
|
def _appy_getAllFields(self, contentType):
|
||||||
|
'''Returns the (translated) names of fields of p_contentType.'''
|
||||||
|
res = []
|
||||||
|
for appyType in self.getProductConfig().attributes[contentType]:
|
||||||
|
if appyType.name != 'title': # Will be included by default.
|
||||||
|
label = '%s_%s' % (contentType, appyType.name)
|
||||||
|
res.append((appyType.name, self.translate(label)))
|
||||||
|
# Add object state
|
||||||
|
res.append(('workflowState', self.translate('workflow_state')))
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _appy_getSearchableFields(self, contentType):
|
||||||
|
'''Returns the (translated) names of fields that may be searched on
|
||||||
|
objects of type p_contentType (=indexed fields).'''
|
||||||
|
res = []
|
||||||
|
for appyType in self.getProductConfig().attributes[contentType]:
|
||||||
|
if appyType.indexed:
|
||||||
|
res.append((appyType.name, self.translate(appyType.labelId)))
|
||||||
|
return res
|
||||||
|
|
||||||
|
def getSearchableFields(self, contentType):
|
||||||
|
'''Returns, among the list of all searchable fields (see method above),
|
||||||
|
the list of fields that the user has configured as being effectively
|
||||||
|
used in the search screen.'''
|
||||||
|
res = []
|
||||||
|
fieldNames = getattr(self.appy(), 'searchFieldsFor%s' % contentType, ())
|
||||||
|
for name in fieldNames:
|
||||||
|
appyType = self.getAppyType(name, asDict=True,className=contentType)
|
||||||
|
res.append(appyType)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def getImportElements(self, contentType):
|
||||||
|
'''Returns the list of elements that can be imported from p_path for
|
||||||
|
p_contentType.'''
|
||||||
|
appyClass = self.getAppyClass(contentType)
|
||||||
|
importParams = self.getCreateMeans(appyClass)['import']
|
||||||
|
onElement = importParams['onElement'].__get__('')
|
||||||
|
sortMethod = importParams['sort']
|
||||||
|
if sortMethod: sortMethod = sortMethod.__get__('')
|
||||||
|
elems = []
|
||||||
|
importPath = getattr(self, 'importPathFor%s' % contentType)
|
||||||
|
for elem in os.listdir(importPath):
|
||||||
|
elemFullPath = os.path.join(importPath, elem)
|
||||||
|
elemInfo = onElement(elemFullPath)
|
||||||
|
if elemInfo:
|
||||||
|
elemInfo.insert(0, elemFullPath) # To the result, I add the full
|
||||||
|
# path of the elem, which will not be shown.
|
||||||
|
elems.append(elemInfo)
|
||||||
|
if sortMethod:
|
||||||
|
elems = sortMethod(elems)
|
||||||
|
return [importParams['headers'], elems]
|
||||||
|
|
||||||
def showPortlet(self, context):
|
def showPortlet(self, context):
|
||||||
if self.portal_membership.isAnonymousUser(): return False
|
if self.portal_membership.isAnonymousUser(): return False
|
||||||
if context.id == 'skyn': context = context.getParentNode()
|
if context.id == 'skyn': context = context.getParentNode()
|
||||||
|
@ -106,15 +212,13 @@ class ToolMixin(AbstractMixin):
|
||||||
res = res.appy()
|
res = res.appy()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def executeQuery(self, contentType, flavourNumber=1, searchName=None,
|
def executeQuery(self, contentType, searchName=None, startNumber=0,
|
||||||
startNumber=0, search=None, remember=False,
|
search=None, remember=False, brainsOnly=False,
|
||||||
brainsOnly=False, maxResults=None, noSecurity=False,
|
maxResults=None, noSecurity=False, sortBy=None,
|
||||||
sortBy=None, sortOrder='asc',
|
sortOrder='asc', filterKey=None, filterValue=None):
|
||||||
filterKey=None, filterValue=None):
|
|
||||||
'''Executes a query on a given p_contentType (or several, separated
|
'''Executes a query on a given p_contentType (or several, separated
|
||||||
with commas) in Plone's portal_catalog. Portal types are from the
|
with commas) in Plone's portal_catalog. If p_searchName is specified,
|
||||||
flavour numbered p_flavourNumber. If p_searchName is specified, it
|
it corresponds to:
|
||||||
corresponds to:
|
|
||||||
1) a search defined on p_contentType: additional search criteria
|
1) a search defined on p_contentType: additional search criteria
|
||||||
will be added to the query, or;
|
will be added to the query, or;
|
||||||
2) "_advanced": in this case, additional search criteria will also
|
2) "_advanced": in this case, additional search criteria will also
|
||||||
|
@ -150,11 +254,7 @@ class ToolMixin(AbstractMixin):
|
||||||
p_filterValue.'''
|
p_filterValue.'''
|
||||||
# Is there one or several content types ?
|
# Is there one or several content types ?
|
||||||
if contentType.find(',') != -1:
|
if contentType.find(',') != -1:
|
||||||
# Several content types are specified
|
|
||||||
portalTypes = contentType.split(',')
|
portalTypes = contentType.split(',')
|
||||||
if flavourNumber != 1:
|
|
||||||
portalTypes = ['%s_%d' % (pt, flavourNumber) \
|
|
||||||
for pt in portalTypes]
|
|
||||||
else:
|
else:
|
||||||
portalTypes = contentType
|
portalTypes = contentType
|
||||||
params = {'portal_type': portalTypes}
|
params = {'portal_type': portalTypes}
|
||||||
|
@ -164,8 +264,7 @@ class ToolMixin(AbstractMixin):
|
||||||
# In this case, contentType must contain a single content type.
|
# In this case, contentType must contain a single content type.
|
||||||
appyClass = self.getAppyClass(contentType)
|
appyClass = self.getAppyClass(contentType)
|
||||||
if searchName != '_advanced':
|
if searchName != '_advanced':
|
||||||
search = ClassDescriptor.getSearch(
|
search = ClassDescriptor.getSearch(appyClass, searchName)
|
||||||
appyClass, searchName)
|
|
||||||
else:
|
else:
|
||||||
fields = self.REQUEST.SESSION['searchCriteria']
|
fields = self.REQUEST.SESSION['searchCriteria']
|
||||||
search = Search('customSearch', **fields)
|
search = Search('customSearch', **fields)
|
||||||
|
@ -220,22 +319,17 @@ class ToolMixin(AbstractMixin):
|
||||||
for obj in res.objects:
|
for obj in res.objects:
|
||||||
i += 1
|
i += 1
|
||||||
uids[startNumber+i] = obj.UID()
|
uids[startNumber+i] = obj.UID()
|
||||||
s['search_%s_%s' % (flavourNumber, searchName)] = uids
|
s['search_%s' % searchName] = uids
|
||||||
return res.__dict__
|
return res.__dict__
|
||||||
|
|
||||||
def getResultColumnsNames(self, contentType):
|
def getResultColumnsNames(self, contentType):
|
||||||
contentTypes = contentType.strip(',').split(',')
|
contentTypes = contentType.strip(',').split(',')
|
||||||
resSet = None # Temporary set for computing intersections.
|
resSet = None # Temporary set for computing intersections.
|
||||||
res = [] # Final, sorted result.
|
res = [] # Final, sorted result.
|
||||||
flavour = None
|
|
||||||
fieldNames = None
|
fieldNames = None
|
||||||
|
appyTool = self.appy()
|
||||||
for cType in contentTypes:
|
for cType in contentTypes:
|
||||||
# Get the flavour tied to those content types
|
fieldNames = getattr(appyTool, 'resultColumnsFor%s' % cType)
|
||||||
if not flavour:
|
|
||||||
flavour = self.getFlavour(cType, appy=True)
|
|
||||||
if flavour.number != 1:
|
|
||||||
cType = cType.rsplit('_', 1)[0]
|
|
||||||
fieldNames = getattr(flavour, 'resultColumnsFor%s' % cType)
|
|
||||||
if not resSet:
|
if not resSet:
|
||||||
resSet = set(fieldNames)
|
resSet = set(fieldNames)
|
||||||
else:
|
else:
|
||||||
|
@ -483,9 +577,9 @@ class ToolMixin(AbstractMixin):
|
||||||
attrValue = oper.join(attrValue)
|
attrValue = oper.join(attrValue)
|
||||||
criteria[attrName[2:]] = attrValue
|
criteria[attrName[2:]] = attrValue
|
||||||
rq.SESSION['searchCriteria'] = criteria
|
rq.SESSION['searchCriteria'] = criteria
|
||||||
# Goto the screen that displays search results
|
# Go to the screen that displays search results
|
||||||
backUrl = '%s/query?type_name=%s&flavourNumber=%d&search=_advanced' % \
|
backUrl = '%s/query?type_name=%s&&search=_advanced' % \
|
||||||
(os.path.dirname(rq['URL']), rq['type_name'], rq['flavourNumber'])
|
(os.path.dirname(rq['URL']), rq['type_name'])
|
||||||
return self.goto(backUrl)
|
return self.goto(backUrl)
|
||||||
|
|
||||||
def getJavascriptMessages(self):
|
def getJavascriptMessages(self):
|
||||||
|
@ -535,13 +629,12 @@ class ToolMixin(AbstractMixin):
|
||||||
if cookieValue: return cookieValue.value
|
if cookieValue: return cookieValue.value
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def getQueryUrl(self, contentType, flavourNumber, searchName,
|
def getQueryUrl(self, contentType, searchName, startNumber=None):
|
||||||
startNumber=None):
|
|
||||||
'''This method creates the URL that allows to perform a (non-Ajax)
|
'''This method creates the URL that allows to perform a (non-Ajax)
|
||||||
request for getting queried objects from a search named p_searchName
|
request for getting queried objects from a search named p_searchName
|
||||||
on p_contentType from flavour numbered p_flavourNumber.'''
|
on p_contentType.'''
|
||||||
baseUrl = self.getAppFolder().absolute_url() + '/skyn'
|
baseUrl = self.getAppFolder().absolute_url() + '/skyn'
|
||||||
baseParams= 'type_name=%s&flavourNumber=%s' %(contentType,flavourNumber)
|
baseParams = 'type_name=%s' % contentType
|
||||||
# Manage start number
|
# Manage start number
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
if startNumber != None:
|
if startNumber != None:
|
||||||
|
@ -609,11 +702,10 @@ class ToolMixin(AbstractMixin):
|
||||||
if (nextIndex < lastIndex): lastNeeded = True
|
if (nextIndex < lastIndex): lastNeeded = True
|
||||||
# Get the list of available UIDs surrounding the current object
|
# Get the list of available UIDs surrounding the current object
|
||||||
if t == 'ref': # Manage navigation from a reference
|
if t == 'ref': # Manage navigation from a reference
|
||||||
|
# In the case of a reference, we retrieve ALL surrounding objects.
|
||||||
masterObj = self.getObject(d1)
|
masterObj = self.getObject(d1)
|
||||||
batchSize = masterObj.getAppyType(fieldName).maxPerPage
|
batchSize = masterObj.getAppyType(fieldName).maxPerPage
|
||||||
uids = getattr(masterObj, '_appy_%s' % fieldName)
|
uids = getattr(masterObj, '_appy_%s' % fieldName)
|
||||||
# In the case of a reference, we retrieve ALL surrounding objects.
|
|
||||||
|
|
||||||
# Display the reference widget at the page where the current object
|
# Display the reference widget at the page where the current object
|
||||||
# lies.
|
# lies.
|
||||||
startNumberKey = '%s%s_startNumber' % (masterObj.UID(), fieldName)
|
startNumberKey = '%s%s_startNumber' % (masterObj.UID(), fieldName)
|
||||||
|
@ -622,13 +714,12 @@ class ToolMixin(AbstractMixin):
|
||||||
res['sourceUrl'] = masterObj.getUrl(**{startNumberKey:startNumber,
|
res['sourceUrl'] = masterObj.getUrl(**{startNumberKey:startNumber,
|
||||||
'page':pageName, 'nav':''})
|
'page':pageName, 'nav':''})
|
||||||
else: # Manage navigation from a search
|
else: # Manage navigation from a search
|
||||||
contentType, flavourNumber = d1.split(':')
|
contentType = d1
|
||||||
flavourNumber = int(flavourNumber)
|
|
||||||
searchName = keySuffix = d2
|
searchName = keySuffix = d2
|
||||||
batchSize = self.appy().numberOfResultsPerPage
|
batchSize = self.appy().numberOfResultsPerPage
|
||||||
if not searchName: keySuffix = contentType
|
if not searchName: keySuffix = contentType
|
||||||
s = self.REQUEST.SESSION
|
s = self.REQUEST.SESSION
|
||||||
searchKey = 'search_%s_%s' % (flavourNumber, keySuffix)
|
searchKey = 'search_%s' % keySuffix
|
||||||
if s.has_key(searchKey): uids = s[searchKey]
|
if s.has_key(searchKey): uids = s[searchKey]
|
||||||
else: uids = {}
|
else: uids = {}
|
||||||
# In the case of a search, we retrieve only a part of all
|
# In the case of a search, we retrieve only a part of all
|
||||||
|
@ -640,9 +731,8 @@ class ToolMixin(AbstractMixin):
|
||||||
# this one.
|
# this one.
|
||||||
newStartNumber = (res['currentNumber']-1) - (batchSize / 2)
|
newStartNumber = (res['currentNumber']-1) - (batchSize / 2)
|
||||||
if newStartNumber < 0: newStartNumber = 0
|
if newStartNumber < 0: newStartNumber = 0
|
||||||
self.executeQuery(contentType, flavourNumber,
|
self.executeQuery(contentType, searchName=searchName,
|
||||||
searchName=searchName, startNumber=newStartNumber,
|
startNumber=newStartNumber, remember=True)
|
||||||
remember=True)
|
|
||||||
uids = s[searchKey]
|
uids = s[searchKey]
|
||||||
# For the moment, for first and last, we get them only if we have
|
# For the moment, for first and last, we get them only if we have
|
||||||
# them in session.
|
# them in session.
|
||||||
|
@ -650,9 +740,9 @@ class ToolMixin(AbstractMixin):
|
||||||
if not uids.has_key(lastIndex): lastNeeded = False
|
if not uids.has_key(lastIndex): lastNeeded = False
|
||||||
# Compute URL of source object
|
# Compute URL of source object
|
||||||
startNumber = self.computeStartNumberFrom(res['currentNumber']-1,
|
startNumber = self.computeStartNumberFrom(res['currentNumber']-1,
|
||||||
res['totalNumber'], batchSize)
|
res['totalNumber'], batchSize)
|
||||||
res['sourceUrl'] = self.getQueryUrl(contentType, flavourNumber,
|
res['sourceUrl'] = self.getQueryUrl(contentType, searchName,
|
||||||
searchName, startNumber=startNumber)
|
startNumber=startNumber)
|
||||||
# Compute URLs
|
# Compute URLs
|
||||||
for urlType in ('previous', 'next', 'first', 'last'):
|
for urlType in ('previous', 'next', 'first', 'last'):
|
||||||
exec 'needIt = %sNeeded' % urlType
|
exec 'needIt = %sNeeded' % urlType
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
from appy.gen.plone25.mixins import AbstractMixin
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
class UserMixin(AbstractMixin):
|
|
||||||
_appy_meta_type = 'UserMixin'
|
|
||||||
# ------------------------------------------------------------------------------
|
|
|
@ -1,9 +1,6 @@
|
||||||
'''This package contains mixin classes that are mixed in with generated classes:
|
'''This package contains mixin classes that are mixed in with generated classes:
|
||||||
- mixins/ClassMixin is mixed in with Standard Archetypes classes;
|
- mixins/BaseMixin is mixed in with Standard Archetypes classes;
|
||||||
- mixins/ToolMixin is mixed in with the generated application Tool class;
|
- mixins/ToolMixin is mixed in with the generated application Tool class.'''
|
||||||
- mixins/FlavourMixin is mixed in with the generated application Flavour
|
|
||||||
class.
|
|
||||||
The AbstractMixin defined hereafter is the base class of any mixin.'''
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import os, os.path, types, mimetypes
|
import os, os.path, types, mimetypes
|
||||||
|
@ -15,10 +12,10 @@ from appy.gen.plone25.descriptors import ClassDescriptor
|
||||||
from appy.gen.plone25.utils import updateRolesForPermission
|
from appy.gen.plone25.utils import updateRolesForPermission
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class AbstractMixin:
|
class BaseMixin:
|
||||||
'''Every Archetype class generated by appy.gen inherits from a mixin that
|
'''Every Archetype class generated by appy.gen inherits from this class or
|
||||||
inherits from this class. It contains basic functions allowing to
|
a subclass of it.'''
|
||||||
minimize the amount of generated code.'''
|
_appy_meta_type = 'Class'
|
||||||
|
|
||||||
def createOrUpdate(self, created, values):
|
def createOrUpdate(self, created, values):
|
||||||
'''This method creates (if p_created is True) or updates an object.
|
'''This method creates (if p_created is True) or updates an object.
|
||||||
|
@ -52,17 +49,21 @@ class AbstractMixin:
|
||||||
# Keep in history potential changes on historized fields
|
# Keep in history potential changes on historized fields
|
||||||
self.historizeData(previousData)
|
self.historizeData(previousData)
|
||||||
|
|
||||||
# Manage references
|
# Manage potential link with an initiator object
|
||||||
obj._appy_manageRefs(created)
|
if created and rq.get('nav', None):
|
||||||
|
# Get the initiator
|
||||||
|
splitted = rq['nav'].split('.')
|
||||||
|
if splitted[0] == 'search': return # Not an initiator but a search.
|
||||||
|
initiator = self.uid_catalog(UID=splitted[1])[0].getObject()
|
||||||
|
fieldName = splitted[2].split(':')[0]
|
||||||
|
initiator.appy().link(fieldName, obj)
|
||||||
|
|
||||||
|
# Call the custom "onEdit" if available
|
||||||
if obj.wrapperClass:
|
if obj.wrapperClass:
|
||||||
# Get the wrapper first
|
|
||||||
appyObject = obj.appy()
|
appyObject = obj.appy()
|
||||||
# Call the custom "onEdit" if available
|
if hasattr(appyObject, 'onEdit'): appyObject.onEdit(created)
|
||||||
if hasattr(appyObject, 'onEdit'):
|
# Manage "add" permissions and reindex the object
|
||||||
appyObject.onEdit(created)
|
|
||||||
# Manage "add" permissions
|
|
||||||
obj._appy_managePermissions()
|
obj._appy_managePermissions()
|
||||||
# Reindex object
|
|
||||||
obj.reindexObject()
|
obj.reindexObject()
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
@ -95,6 +96,12 @@ class AbstractMixin:
|
||||||
(baseUrl, typeName, objId)
|
(baseUrl, typeName, objId)
|
||||||
return self.goto(self.getUrl(editUrl, **urlParams))
|
return self.goto(self.getUrl(editUrl, **urlParams))
|
||||||
|
|
||||||
|
def onCreateWithoutForm(self):
|
||||||
|
'''This method is called when a user wants to create a object from a
|
||||||
|
reference field, automatically (without displaying a form).'''
|
||||||
|
rq = self.REQUEST
|
||||||
|
self.appy().create(rq['fieldName'])
|
||||||
|
|
||||||
def intraFieldValidation(self, errors, values):
|
def intraFieldValidation(self, errors, values):
|
||||||
'''This method performs field-specific validation for every field from
|
'''This method performs field-specific validation for every field from
|
||||||
the page that is being created or edited. For every field whose
|
the page that is being created or edited. For every field whose
|
||||||
|
@ -120,7 +127,7 @@ class AbstractMixin:
|
||||||
obj = self.appy()
|
obj = self.appy()
|
||||||
if not hasattr(obj, 'validate'): return
|
if not hasattr(obj, 'validate'): return
|
||||||
obj.validate(values, errors)
|
obj.validate(values, errors)
|
||||||
# This custom "validate" method may have added fields in the given
|
# Those custom validation methods may have added fields in the given
|
||||||
# p_errors object. Within this object, for every error message that is
|
# p_errors object. Within this object, for every error message that is
|
||||||
# not a string, we replace it with the standard validation error for the
|
# not a string, we replace it with the standard validation error for the
|
||||||
# corresponding field.
|
# corresponding field.
|
||||||
|
@ -241,21 +248,19 @@ class AbstractMixin:
|
||||||
res = {}
|
res = {}
|
||||||
for appyType in self.getAllAppyTypes():
|
for appyType in self.getAllAppyTypes():
|
||||||
if appyType.historized:
|
if appyType.historized:
|
||||||
res[appyType.name] = (getattr(self, appyType.name),
|
res[appyType.name] = appyType.getValue(self)
|
||||||
appyType.labelId)
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def addDataChange(self, changes, labels=False):
|
def addDataChange(self, changes):
|
||||||
'''This method allows to add "manually" a data change into the objet's
|
'''This method allows to add "manually" a data change into the objet's
|
||||||
history. Indeed, data changes are "automatically" recorded only when
|
history. Indeed, data changes are "automatically" recorded only when
|
||||||
a HTTP form is uploaded, not if, in the code, a setter is called on
|
a HTTP form is uploaded, not if, in the code, a setter is called on
|
||||||
a field. The method is also called by the method historizeData below,
|
a field. The method is also called by the method historizeData below,
|
||||||
that performs "automatic" recording when a HTTP form is uploaded.'''
|
that performs "automatic" recording when a HTTP form is uploaded.'''
|
||||||
# Add to the p_changes dict the field labels if they are not present
|
# Add to the p_changes dict the field labels
|
||||||
if not labels:
|
for fieldName in changes.iterkeys():
|
||||||
for fieldName in changes.iterkeys():
|
appyType = self.getAppyType(fieldName)
|
||||||
appyType = self.getAppyType(fieldName)
|
changes[fieldName] = (changes[fieldName], appyType.labelId)
|
||||||
changes[fieldName] = (changes[fieldName], appyType.labelId)
|
|
||||||
# Create the event to record in the history
|
# Create the event to record in the history
|
||||||
DateTime = self.getProductConfig().DateTime
|
DateTime = self.getProductConfig().DateTime
|
||||||
state = self.portal_workflow.getInfoFor(self, 'review_state')
|
state = self.portal_workflow.getInfoFor(self, 'review_state')
|
||||||
|
@ -273,14 +278,18 @@ class AbstractMixin:
|
||||||
historized fields, while p_self already contains the (potentially)
|
historized fields, while p_self already contains the (potentially)
|
||||||
modified values.'''
|
modified values.'''
|
||||||
# Remove from previousData all values that were not changed
|
# Remove from previousData all values that were not changed
|
||||||
for fieldName in previousData.keys():
|
for field in previousData.keys():
|
||||||
prev = previousData[fieldName][0]
|
prev = previousData[field]
|
||||||
curr = getattr(self, fieldName)
|
appyType = self.getAppyType(field)
|
||||||
|
curr = appyType.getValue(self)
|
||||||
if (prev == curr) or ((prev == None) and (curr == '')) or \
|
if (prev == curr) or ((prev == None) and (curr == '')) or \
|
||||||
((prev == '') and (curr == None)):
|
((prev == '') and (curr == None)):
|
||||||
del previousData[fieldName]
|
del previousData[field]
|
||||||
|
if (appyType.type == 'Ref') and (field in previousData):
|
||||||
|
titles = [r.title for r in previousData[field]]
|
||||||
|
previousData[field] = ','.join(titles)
|
||||||
if previousData:
|
if previousData:
|
||||||
self.addDataChange(previousData, labels=True)
|
self.addDataChange(previousData)
|
||||||
|
|
||||||
def goto(self, url, addParams=False):
|
def goto(self, url, addParams=False):
|
||||||
'''Brings the user to some p_url after an action has been executed.'''
|
'''Brings the user to some p_url after an action has been executed.'''
|
||||||
|
@ -308,80 +317,14 @@ class AbstractMixin:
|
||||||
field named p_name.'''
|
field named p_name.'''
|
||||||
return self.getAppyType(name).getFormattedValue(self, value)
|
return self.getAppyType(name).getFormattedValue(self, value)
|
||||||
|
|
||||||
def _appy_getRefs(self, fieldName, ploneObjects=False,
|
|
||||||
noListIfSingleObj=False, startNumber=None):
|
|
||||||
'''p_fieldName is the name of a Ref field. This method returns an
|
|
||||||
ordered list containing the objects linked to p_self through this
|
|
||||||
field. If p_ploneObjects is True, the method returns the "true"
|
|
||||||
Plone objects instead of the Appy wrappers.
|
|
||||||
If p_startNumber is None, this method returns all referred objects.
|
|
||||||
If p_startNumber is a number, this method will return x objects,
|
|
||||||
starting at p_startNumber, x being appyType.maxPerPage.'''
|
|
||||||
appyType = self.getAppyType(fieldName)
|
|
||||||
sortedUids = self._appy_getSortedField(fieldName)
|
|
||||||
batchNeeded = startNumber != None
|
|
||||||
exec 'refUids= self.getRaw%s%s()' % (fieldName[0].upper(),fieldName[1:])
|
|
||||||
# There may be too much UIDs in sortedUids because these fields
|
|
||||||
# are not updated when objects are deleted. So we do it now. TODO: do
|
|
||||||
# such cleaning on object deletion?
|
|
||||||
toDelete = []
|
|
||||||
for uid in sortedUids:
|
|
||||||
if uid not in refUids:
|
|
||||||
toDelete.append(uid)
|
|
||||||
for uid in toDelete:
|
|
||||||
sortedUids.remove(uid)
|
|
||||||
# Prepare the result
|
|
||||||
res = SomeObjects()
|
|
||||||
res.totalNumber = res.batchSize = len(sortedUids)
|
|
||||||
if batchNeeded:
|
|
||||||
res.batchSize = appyType.maxPerPage
|
|
||||||
if startNumber != None:
|
|
||||||
res.startNumber = startNumber
|
|
||||||
# Get the needed referred objects
|
|
||||||
i = res.startNumber
|
|
||||||
# Is it possible and more efficient to perform a single query in
|
|
||||||
# uid_catalog and get the result in the order of specified uids?
|
|
||||||
toUnlink = []
|
|
||||||
while i < (res.startNumber + res.batchSize):
|
|
||||||
if i >= res.totalNumber: break
|
|
||||||
refUid = sortedUids[i]
|
|
||||||
refObject = self.uid_catalog(UID=refUid)[0].getObject()
|
|
||||||
i += 1
|
|
||||||
tool = self.getTool()
|
|
||||||
if refObject.meta_type != tool.getPortalType(appyType.klass):
|
|
||||||
toUnlink.append(refObject)
|
|
||||||
continue
|
|
||||||
if not ploneObjects:
|
|
||||||
refObject = refObject.appy()
|
|
||||||
res.objects.append(refObject)
|
|
||||||
# Unlink dummy linked objects
|
|
||||||
if toUnlink:
|
|
||||||
suffix = '%s%s' % (fieldName[0].upper(), fieldName[1:])
|
|
||||||
exec 'linkedObjects = self.get%s()' % suffix
|
|
||||||
for dummyObject in toUnlink:
|
|
||||||
linkedObjects.remove(dummyObject)
|
|
||||||
self.getProductConfig().logger.warn('DB error: Ref %s.%s ' \
|
|
||||||
'contains a %s instance "%s". It was removed.' % \
|
|
||||||
(self.meta_type, fieldName, dummyObject.meta_type,
|
|
||||||
dummyObject.getId()))
|
|
||||||
exec 'self.set%s(linkedObjects)' % suffix
|
|
||||||
if res.objects and noListIfSingleObj:
|
|
||||||
if appyType.multiplicity[1] == 1:
|
|
||||||
res.objects = res.objects[0]
|
|
||||||
return res
|
|
||||||
|
|
||||||
def getAppyRefs(self, name, startNumber=None):
|
def getAppyRefs(self, name, startNumber=None):
|
||||||
'''Gets the objects linked to me through Ref field named p_name.
|
'''Gets the objects linked to me through Ref field named p_name.
|
||||||
If p_startNumber is None, this method returns all referred objects.
|
If p_startNumber is None, this method returns all referred objects.
|
||||||
If p_startNumber is a number, this method will return x objects,
|
If p_startNumber is a number, this method will return
|
||||||
starting at p_startNumber, x being appyType.maxPerPage.'''
|
appyType.maxPerPage objects, starting at p_startNumber.'''
|
||||||
appyType = self.getAppyType(name)
|
appyType = self.getAppyType(name)
|
||||||
if not appyType.isBack:
|
return appyType.getValue(self, type='zobjects', someObjects=True,
|
||||||
return self._appy_getRefs(name, ploneObjects=True,
|
startNumber=startNumber).__dict__
|
||||||
startNumber=startNumber).__dict__
|
|
||||||
else:
|
|
||||||
# Note that pagination is not yet implemented for backward refs.
|
|
||||||
return SomeObjects(self.getBRefs(appyType.relationship)).__dict__
|
|
||||||
|
|
||||||
def getSelectableAppyRefs(self, name):
|
def getSelectableAppyRefs(self, name):
|
||||||
'''p_name is the name of a Ref field. This method returns the list of
|
'''p_name is the name of a Ref field. This method returns the list of
|
||||||
|
@ -780,10 +723,6 @@ class AbstractMixin:
|
||||||
self.reindexObject()
|
self.reindexObject()
|
||||||
return self.goto(urlBack)
|
return self.goto(urlBack)
|
||||||
|
|
||||||
def getFlavour(self):
|
|
||||||
'''Returns the flavour corresponding to this object.'''
|
|
||||||
return self.getTool().getFlavour(self.portal_type)
|
|
||||||
|
|
||||||
def fieldValueSelected(self, fieldName, vocabValue, dbValue):
|
def fieldValueSelected(self, fieldName, vocabValue, dbValue):
|
||||||
'''When displaying a selection box (ie a String with a validator being a
|
'''When displaying a selection box (ie a String with a validator being a
|
||||||
list), must the _vocabValue appear as selected?'''
|
list), must the _vocabValue appear as selected?'''
|
||||||
|
@ -853,32 +792,6 @@ class AbstractMixin:
|
||||||
rq.appyWrappers[uid] = wrapper
|
rq.appyWrappers[uid] = wrapper
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def _appy_getRefsBack(self, fieldName, relName, ploneObjects=False,
|
|
||||||
noListIfSingleObj=False):
|
|
||||||
'''This method returns the list of objects linked to this one
|
|
||||||
through the BackRef corresponding to the Archetypes
|
|
||||||
relationship named p_relName.'''
|
|
||||||
# Preamble: must I return a list or a single element?
|
|
||||||
maxOne = False
|
|
||||||
if noListIfSingleObj:
|
|
||||||
# I must get the referred appyType to know its maximum multiplicity.
|
|
||||||
appyType = self.getAppyType(fieldName)
|
|
||||||
if appyType.multiplicity[1] == 1:
|
|
||||||
maxOne = True
|
|
||||||
# Get the referred objects through the Archetypes relationship.
|
|
||||||
objs = self.getBRefs(relName)
|
|
||||||
if maxOne:
|
|
||||||
res = None
|
|
||||||
if objs:
|
|
||||||
res = objs[0]
|
|
||||||
if res and not ploneObjects:
|
|
||||||
res = res.appy()
|
|
||||||
else:
|
|
||||||
res = objs
|
|
||||||
if not ploneObjects:
|
|
||||||
res = [o.appy() for o in objs]
|
|
||||||
return res
|
|
||||||
|
|
||||||
def _appy_showState(self, workflow, stateShow):
|
def _appy_showState(self, workflow, stateShow):
|
||||||
'''Must I show a state whose "show value" is p_stateShow?'''
|
'''Must I show a state whose "show value" is p_stateShow?'''
|
||||||
if callable(stateShow):
|
if callable(stateShow):
|
||||||
|
@ -955,57 +868,6 @@ class AbstractMixin:
|
||||||
exec 'self.%s = pList()' % sortedFieldName
|
exec 'self.%s = pList()' % sortedFieldName
|
||||||
return getattr(self, sortedFieldName)
|
return getattr(self, sortedFieldName)
|
||||||
|
|
||||||
def _appy_manageRefs(self, created):
|
|
||||||
'''Every time an object is created or updated, this method updates
|
|
||||||
the Reference fields accordingly.'''
|
|
||||||
self._appy_manageRefsFromRequest()
|
|
||||||
rq = self.REQUEST
|
|
||||||
# If the creation was initiated by another object, update the ref.
|
|
||||||
if created and rq.get('nav', None):
|
|
||||||
# Get the initiator
|
|
||||||
splitted = rq['nav'].split('.')
|
|
||||||
if splitted[0] == 'search': return # Not an initiator but a search.
|
|
||||||
initiator = self.uid_catalog.searchResults(
|
|
||||||
UID=splitted[1])[0].getObject()
|
|
||||||
fieldName = splitted[2].split(':')[1]
|
|
||||||
initiator.appy().link(fieldName, self)
|
|
||||||
|
|
||||||
def _appy_manageRefsFromRequest(self):
|
|
||||||
'''Appy manages itself some Ref fields (with link=True). So here we must
|
|
||||||
update the Ref fields.'''
|
|
||||||
fieldsInRequest = [] # Fields present in the request
|
|
||||||
for requestKey in self.REQUEST.keys():
|
|
||||||
if requestKey.startswith('appy_ref_'):
|
|
||||||
fieldName = requestKey[9:]
|
|
||||||
# Security check
|
|
||||||
if not self.getAppyType(fieldName).isShowable(self, 'edit'):
|
|
||||||
continue
|
|
||||||
fieldsInRequest.append(fieldName)
|
|
||||||
fieldValue = self.REQUEST[requestKey]
|
|
||||||
sortedRefField = self._appy_getSortedField(fieldName)
|
|
||||||
del sortedRefField[:]
|
|
||||||
if not fieldValue: fieldValue = []
|
|
||||||
if isinstance(fieldValue, basestring):
|
|
||||||
fieldValue = [fieldValue]
|
|
||||||
refObjects = []
|
|
||||||
for uid in fieldValue:
|
|
||||||
obj = self.uid_catalog(UID=uid)[0].getObject()
|
|
||||||
refObjects.append(obj)
|
|
||||||
sortedRefField.append(uid)
|
|
||||||
exec 'self.set%s%s(refObjects)' % (fieldName[0].upper(),
|
|
||||||
fieldName[1:])
|
|
||||||
# Manage Ref fields that are not present in the request
|
|
||||||
currentPage = self.REQUEST.get('page', 'main')
|
|
||||||
for appyType in self.getAllAppyTypes():
|
|
||||||
if (appyType.type == 'Ref') and not appyType.isBack and \
|
|
||||||
(appyType.page == currentPage) and \
|
|
||||||
(appyType.name not in fieldsInRequest):
|
|
||||||
# If this field is visible, it was not present in the request:
|
|
||||||
# it means that we must remove any Ref from it.
|
|
||||||
if appyType.isShowable(self, 'edit'):
|
|
||||||
exec 'self.set%s%s([])' % (appyType.name[0].upper(),
|
|
||||||
appyType.name[1:])
|
|
||||||
|
|
||||||
getUrlDefaults = {'page':True, 'nav':True}
|
getUrlDefaults = {'page':True, 'nav':True}
|
||||||
def getUrl(self, base=None, mode='view', **kwargs):
|
def getUrl(self, base=None, mode='view', **kwargs):
|
||||||
'''Returns a Appy URL.
|
'''Returns a Appy URL.
|
||||||
|
@ -1039,12 +901,14 @@ class AbstractMixin:
|
||||||
params = ''
|
params = ''
|
||||||
return '%s%s' % (base, params)
|
return '%s%s' % (base, params)
|
||||||
|
|
||||||
def translate(self, label, mapping={}, domain=None, default=None):
|
def translate(self, label, mapping={}, domain=None, default=None,
|
||||||
|
language=None):
|
||||||
'''Translates a given p_label into p_domain with p_mapping.'''
|
'''Translates a given p_label into p_domain with p_mapping.'''
|
||||||
cfg = self.getProductConfig()
|
cfg = self.getProductConfig()
|
||||||
if not domain: domain = cfg.PROJECTNAME
|
if not domain: domain = cfg.PROJECTNAME
|
||||||
return self.translation_service.utranslate(
|
return self.Control_Panel.TranslationService.utranslate(
|
||||||
domain, label, mapping, self, default=default)
|
domain, label, mapping, self, default=default,
|
||||||
|
target_language=language)
|
||||||
|
|
||||||
def getPageLayout(self, layoutType):
|
def getPageLayout(self, layoutType):
|
||||||
'''Returns the layout corresponding to p_layoutType for p_self.'''
|
'''Returns the layout corresponding to p_layoutType for p_self.'''
|
||||||
|
|
|
@ -12,10 +12,10 @@ from appy.gen import *
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class ModelClass:
|
class ModelClass:
|
||||||
'''This class is the abstract class of all predefined application classes
|
'''This class is the abstract class of all predefined application classes
|
||||||
used in the Appy model: Tool, Flavour, PodTemplate, etc. All methods and
|
used in the Appy model: Tool, User, etc. All methods and attributes of
|
||||||
attributes of those classes are part of the Appy machinery and are
|
those classes are part of the Appy machinery and are prefixed with _appy_
|
||||||
prefixed with _appy_ in order to avoid name conflicts with user-defined
|
in order to avoid name conflicts with user-defined parts of the
|
||||||
parts of the application model.'''
|
application model.'''
|
||||||
_appy_attributes = [] # We need to keep track of attributes order.
|
_appy_attributes = [] # We need to keep track of attributes order.
|
||||||
# When creating a new instance of a ModelClass, the following attributes
|
# When creating a new instance of a ModelClass, the following attributes
|
||||||
# must not be given in the constructor (they are computed attributes).
|
# must not be given in the constructor (they are computed attributes).
|
||||||
|
@ -70,7 +70,11 @@ class ModelClass:
|
||||||
res += ' %s=%s\n' % (attrName, klass._appy_getTypeBody(appyType))
|
res += ' %s=%s\n' % (attrName, klass._appy_getTypeBody(appyType))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
# The User class ---------------------------------------------------------------
|
||||||
class User(ModelClass):
|
class User(ModelClass):
|
||||||
|
# In a ModelClass we need to declare attributes in the following list.
|
||||||
|
_appy_attributes = ['title', 'name', 'firstName', 'login', 'password1',
|
||||||
|
'password2', 'roles']
|
||||||
# All methods defined below are fake. Real versions are in the wrapper.
|
# All methods defined below are fake. Real versions are in the wrapper.
|
||||||
title = String(show=False)
|
title = String(show=False)
|
||||||
gm = {'group': 'main', 'multiplicity': (1,1)}
|
gm = {'group': 'main', 'multiplicity': (1,1)}
|
||||||
|
@ -86,47 +90,50 @@ class User(ModelClass):
|
||||||
password2 = String(format=String.PASSWORD, show=showPassword, **gm)
|
password2 = String(format=String.PASSWORD, show=showPassword, **gm)
|
||||||
gm['multiplicity'] = (0, None)
|
gm['multiplicity'] = (0, None)
|
||||||
roles = String(validator=Selection('getGrantableRoles'), **gm)
|
roles = String(validator=Selection('getGrantableRoles'), **gm)
|
||||||
_appy_attributes = ['title', 'name', 'firstName', 'login',
|
|
||||||
'password1', 'password2', 'roles']
|
|
||||||
|
|
||||||
class PodTemplate(ModelClass):
|
# The Tool class ---------------------------------------------------------------
|
||||||
description = String(format=String.TEXT)
|
|
||||||
podTemplate = File(multiplicity=(1,1))
|
|
||||||
podFormat = String(validator=['odt', 'pdf', 'rtf', 'doc'],
|
|
||||||
multiplicity=(1,1), default='odt')
|
|
||||||
podPhase = String(default='main')
|
|
||||||
_appy_attributes = ['description', 'podTemplate', 'podFormat', 'podPhase']
|
|
||||||
|
|
||||||
defaultFlavourAttrs = ('number', 'enableNotifications')
|
# Here are the prefixes of the fields generated on the Tool.
|
||||||
flavourAttributePrefixes = ('optionalFieldsFor', 'defaultValueFor',
|
toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns',
|
||||||
'podTemplatesFor', 'podMaxShownTemplatesFor', 'resultColumnsFor',
|
'enableAdvancedSearch', 'numberOfSearchColumns',
|
||||||
'showWorkflowFor', 'showWorkflowCommentFieldFor', 'showAllStatesInPhaseFor')
|
'searchFields', 'optionalFields', 'showWorkflow',
|
||||||
# Attribute prefixes of the fields generated on the Flavour for configuring
|
'showWorkflowCommentField', 'showAllStatesInPhase')
|
||||||
# the application classes.
|
defaultToolFields = ('users', 'enableNotifications', 'unoEnabledPython',
|
||||||
|
'openOfficePort', 'numberOfResultsPerPage',
|
||||||
|
'listBoxesMaximumWidth')
|
||||||
|
|
||||||
class Flavour(ModelClass):
|
class Tool(ModelClass):
|
||||||
'''For every application, the Flavour may be different (it depends on the
|
# The following dict allows us to remember the original classes related to
|
||||||
fields declared as optional, etc). Instead of creating a new way to
|
# the attributes we will add due to params in user attributes.
|
||||||
generate the Archetypes Flavour class, we create a silly
|
|
||||||
FlavourStub instance and we will use the standard Archetypes
|
|
||||||
generator that generates classes from the application to generate the
|
|
||||||
flavour class.'''
|
|
||||||
number = Integer(default=1, show=False)
|
|
||||||
enableNotifications = Boolean(default=True, page='notifications')
|
|
||||||
_appy_classes = {} # ~{s_attributeName: s_className}~
|
_appy_classes = {} # ~{s_attributeName: s_className}~
|
||||||
# We need to remember the original classes related to the flavour attributes
|
# In a ModelClass we need to declare attributes in the following list.
|
||||||
_appy_attributes = list(defaultFlavourAttrs)
|
_appy_attributes = list(defaultToolFields)
|
||||||
|
|
||||||
|
# Tool attributes
|
||||||
|
# First arg of Ref field below is None because we don't know yet if it will
|
||||||
|
# link to the predefined User class or a custom class defined in the
|
||||||
|
# application.
|
||||||
|
users = Ref(None, multiplicity=(0,None), add=True, link=False,
|
||||||
|
back=Ref(attribute='toTool'), page='users',
|
||||||
|
shownInfo=('login', 'title', 'roles'), showHeaders=True)
|
||||||
|
enableNotifications = Boolean(default=True, page='notifications')
|
||||||
|
def validPythonWithUno(self, value): pass # Real method in the wrapper
|
||||||
|
unoEnabledPython = String(group="connectionToOpenOffice",
|
||||||
|
validator=validPythonWithUno)
|
||||||
|
openOfficePort = Integer(default=2002, group="connectionToOpenOffice")
|
||||||
|
numberOfResultsPerPage = Integer(default=30)
|
||||||
|
listBoxesMaximumWidth = Integer(default=100)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _appy_clean(klass):
|
def _appy_clean(klass):
|
||||||
toClean = []
|
toClean = []
|
||||||
for k, v in klass.__dict__.iteritems():
|
for k, v in klass.__dict__.iteritems():
|
||||||
if not k.startswith('__') and (not k.startswith('_appy_')):
|
if not k.startswith('__') and (not k.startswith('_appy_')):
|
||||||
if k not in defaultFlavourAttrs:
|
if k not in defaultToolFields:
|
||||||
toClean.append(k)
|
toClean.append(k)
|
||||||
for k in toClean:
|
for k in toClean:
|
||||||
exec 'del klass.%s' % k
|
exec 'del klass.%s' % k
|
||||||
klass._appy_attributes = list(defaultFlavourAttrs)
|
klass._appy_attributes = list(defaultToolFields)
|
||||||
klass._appy_classes = {}
|
klass._appy_classes = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -134,20 +141,20 @@ class Flavour(ModelClass):
|
||||||
'''From a given p_appyType, produce a type definition suitable for
|
'''From a given p_appyType, produce a type definition suitable for
|
||||||
storing the default value for this field.'''
|
storing the default value for this field.'''
|
||||||
res = copy.copy(appyType)
|
res = copy.copy(appyType)
|
||||||
# A fiekd in the flavour can't have parameters that would lead to the
|
# A field added to the tool can't have parameters that would lead to the
|
||||||
# creation of new fields in the flavour.
|
# creation of new fields in the tool.
|
||||||
res.editDefault = False
|
res.editDefault = False
|
||||||
res.optional = False
|
res.optional = False
|
||||||
res.show = True
|
res.show = True
|
||||||
res.group = copy.copy(appyType.group)
|
res.group = copy.copy(appyType.group)
|
||||||
res.phase = 'main'
|
res.phase = 'main'
|
||||||
# Set default layouts for all Flavour fields
|
# Set default layouts for all Tool fields
|
||||||
res.layouts = res.formatLayouts(None)
|
res.layouts = res.formatLayouts(None)
|
||||||
res.specificReadPermission = False
|
res.specificReadPermission = False
|
||||||
res.specificWritePermission = False
|
res.specificWritePermission = False
|
||||||
res.multiplicity = (0, appyType.multiplicity[1])
|
res.multiplicity = (0, appyType.multiplicity[1])
|
||||||
if type(res.validator) == types.FunctionType:
|
if type(res.validator) == types.FunctionType:
|
||||||
# We will not be able to call this function from the flavour.
|
# We will not be able to call this function from the tool.
|
||||||
res.validator = None
|
res.validator = None
|
||||||
if isinstance(appyType, Ref):
|
if isinstance(appyType, Ref):
|
||||||
res.link = True
|
res.link = True
|
||||||
|
@ -155,7 +162,7 @@ class Flavour(ModelClass):
|
||||||
res.back = copy.copy(appyType.back)
|
res.back = copy.copy(appyType.back)
|
||||||
res.back.attribute += 'DefaultValue'
|
res.back.attribute += 'DefaultValue'
|
||||||
res.back.show = False
|
res.back.show = False
|
||||||
res.select = None # Not callable from flavour
|
res.select = None # Not callable from tool.
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -182,10 +189,7 @@ class Flavour(ModelClass):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _appy_addPodRelatedFields(klass, fieldDescr):
|
def _appy_addPodRelatedFields(klass, fieldDescr):
|
||||||
'''Adds the fields needed in the Flavour for configuring a Pod field.
|
'''Adds the fields needed in the Tool for configuring a Pod field.'''
|
||||||
The following method, m_appy_addPodField, is the previous way to
|
|
||||||
manage gen-pod integration. For the moment, both approaches coexist.
|
|
||||||
In the future, only this one may subsist.'''
|
|
||||||
className = fieldDescr.classDescr.name
|
className = fieldDescr.classDescr.name
|
||||||
# On what page and group to display those fields ?
|
# On what page and group to display those fields ?
|
||||||
pg = {'page': 'documentGeneration',
|
pg = {'page': 'documentGeneration',
|
||||||
|
@ -200,29 +204,9 @@ class Flavour(ModelClass):
|
||||||
multiplicity=(1,None), default=('odt',), **pg)
|
multiplicity=(1,None), default=('odt',), **pg)
|
||||||
klass._appy_addField(fieldName, fieldType, fieldDescr.classDescr)
|
klass._appy_addField(fieldName, fieldType, fieldDescr.classDescr)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _appy_addPodField(klass, classDescr):
|
|
||||||
'''Adds a POD field to the flavour and also an integer field that will
|
|
||||||
determine the maximum number of documents to show at once on consult
|
|
||||||
views. If this number is reached, a list is displayed.'''
|
|
||||||
# First, add the POD field that will hold PodTemplates.
|
|
||||||
fieldType = Ref(PodTemplate, multiplicity=(0,None), add=True,
|
|
||||||
link=False, back = Ref(attribute='flavour'),
|
|
||||||
page="documentGeneration",
|
|
||||||
group=classDescr.klass.__name__)
|
|
||||||
fieldName = 'podTemplatesFor%s' % classDescr.name
|
|
||||||
klass._appy_addField(fieldName, fieldType, classDescr)
|
|
||||||
# Then, add the integer field
|
|
||||||
fieldType = Integer(default=1, page='userInterface',
|
|
||||||
group=classDescr.klass.__name__)
|
|
||||||
fieldName = 'podMaxShownTemplatesFor%s' % classDescr.name
|
|
||||||
klass._appy_addField(fieldName, fieldType, classDescr)
|
|
||||||
classDescr.flavourFieldsToPropagate.append(
|
|
||||||
('podMaxShownTemplatesFor%s', copy.copy(fieldType)) )
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _appy_addQueryResultColumns(klass, classDescr):
|
def _appy_addQueryResultColumns(klass, classDescr):
|
||||||
'''Adds, for class p_classDescr, the attribute in the flavour that
|
'''Adds, for class p_classDescr, the attribute in the tool that
|
||||||
allows to select what default columns will be shown on query
|
allows to select what default columns will be shown on query
|
||||||
results.'''
|
results.'''
|
||||||
className = classDescr.name
|
className = classDescr.name
|
||||||
|
@ -302,25 +286,4 @@ class Flavour(ModelClass):
|
||||||
fieldType = Boolean(default=defaultValue, page='userInterface',
|
fieldType = Boolean(default=defaultValue, page='userInterface',
|
||||||
group=groupName)
|
group=groupName)
|
||||||
klass._appy_addField(fieldName, fieldType, classDescr)
|
klass._appy_addField(fieldName, fieldType, classDescr)
|
||||||
|
|
||||||
class Tool(ModelClass):
|
|
||||||
flavours = Ref(None, multiplicity=(1,None), add=True, link=False,
|
|
||||||
back=Ref(attribute='tool'))
|
|
||||||
# First arg is None because we don't know yet if it will link
|
|
||||||
# to the predefined Flavour class or a custom class defined
|
|
||||||
# in the application.
|
|
||||||
users = Ref(None, multiplicity=(0,None), add=True, link=False,
|
|
||||||
back=Ref(attribute='toTool'), page='users',
|
|
||||||
shownInfo=('login', 'title', 'roles'), showHeaders=True)
|
|
||||||
# First arg is None because we don't know yet if it will link to the
|
|
||||||
# predefined User class or a custom class defined in the application.
|
|
||||||
def validPythonWithUno(self, value): pass # Real method in the wrapper
|
|
||||||
unoEnabledPython = String(group="connectionToOpenOffice",
|
|
||||||
validator=validPythonWithUno)
|
|
||||||
openOfficePort = Integer(default=2002, group="connectionToOpenOffice")
|
|
||||||
numberOfResultsPerPage = Integer(default=30)
|
|
||||||
listBoxesMaximumWidth = Integer(default=100)
|
|
||||||
_appy_attributes = ['flavours', 'users', 'unoEnabledPython',
|
|
||||||
'openOfficePort', 'numberOfResultsPerPage',
|
|
||||||
'listBoxesMaximumWidth']
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
response request/RESPONSE;
|
response request/RESPONSE;
|
||||||
member context/portal_membership/getAuthenticatedMember;
|
member context/portal_membership/getAuthenticatedMember;
|
||||||
portal context/portal_url/getPortalObject;
|
portal context/portal_url/getPortalObject;
|
||||||
portal_url context/portal_url/getPortalPath;
|
portal_url python: context.portal_url();
|
||||||
template python: contextObj.getPageTemplate(portal.skyn, page);
|
template python: contextObj.getPageTemplate(portal.skyn, page);
|
||||||
dummy python: response.setHeader('Content-Type','text/html;;charset=utf-8');
|
dummy python: response.setHeader('Content-Type','text/html;;charset=utf-8');
|
||||||
dummy2 python: response.setHeader('Expires', 'Mon, 11 Dec 1975 12:05:05 GMT');
|
dummy2 python: response.setHeader('Expires', 'Mon, 11 Dec 1975 12:05:05 GMT');
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
layoutType python:'edit';
|
layoutType python:'edit';
|
||||||
layout python: contextObj.getPageLayout(layoutType);
|
layout python: contextObj.getPageLayout(layoutType);
|
||||||
tool contextObj/getTool;
|
tool contextObj/getTool;
|
||||||
flavour python: tool.getFlavour(contextObj);
|
|
||||||
appFolder tool/getAppFolder;
|
appFolder tool/getAppFolder;
|
||||||
appName appFolder/getId;
|
appName appFolder/getId;
|
||||||
page request/page|python:'main';
|
page request/page|python:'main';
|
||||||
|
|
|
@ -16,8 +16,7 @@
|
||||||
tal:define="appFolder context/getParentNode;
|
tal:define="appFolder context/getParentNode;
|
||||||
contentType request/type_name;
|
contentType request/type_name;
|
||||||
tool python: portal.get('portal_%s' % appFolder.id.lower());
|
tool python: portal.get('portal_%s' % appFolder.id.lower());
|
||||||
flavour python: tool.getFlavour(contentType);
|
importElems python: tool.getImportElements(contentType);
|
||||||
importElems python: flavour.getImportElements(contentType);
|
|
||||||
global allAreImported python:True">
|
global allAreImported python:True">
|
||||||
|
|
||||||
<div metal:use-macro="here/skyn/page/macros/prologue"/>
|
<div metal:use-macro="here/skyn/page/macros/prologue"/>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<metal:queryResults define-macro="queryResult"
|
<metal:queryResults define-macro="queryResult"
|
||||||
tal:define="tool python: contextObj;
|
tal:define="tool python: contextObj;
|
||||||
contentType request/type_name;
|
contentType request/type_name;
|
||||||
flavourNumber python: int(request['flavourNumber']);
|
|
||||||
startNumber request/startNumber|python:'0';
|
startNumber request/startNumber|python:'0';
|
||||||
startNumber python: int(startNumber);
|
startNumber python: int(startNumber);
|
||||||
searchName request/search;
|
searchName request/search;
|
||||||
|
@ -12,13 +11,13 @@
|
||||||
sortOrder request/sortOrder| python:'asc';
|
sortOrder request/sortOrder| python:'asc';
|
||||||
filterKey request/filterKey| python:'';
|
filterKey request/filterKey| python:'';
|
||||||
filterValue request/filterValue | python:'';
|
filterValue request/filterValue | python:'';
|
||||||
queryResult python: tool.executeQuery(contentType, flavourNumber, searchName, startNumber, remember=True, sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey, filterValue=filterValue);
|
queryResult python: tool.executeQuery(contentType, searchName, startNumber, remember=True, sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey, filterValue=filterValue);
|
||||||
objs queryResult/objects;
|
objs queryResult/objects;
|
||||||
totalNumber queryResult/totalNumber;
|
totalNumber queryResult/totalNumber;
|
||||||
batchSize queryResult/batchSize;
|
batchSize queryResult/batchSize;
|
||||||
ajaxHookId python:'queryResult';
|
ajaxHookId python:'queryResult';
|
||||||
navBaseCall python: 'askQueryResult(\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, tool.absolute_url(), contentType, flavourNumber, searchName);
|
navBaseCall python: 'askQueryResult(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, tool.absolute_url(), contentType, searchName);
|
||||||
newSearchUrl python: '%s/skyn/search?type_name=%s&flavourNumber=%d' % (tool.getAppFolder().absolute_url(), contentType, flavourNumber);">
|
newSearchUrl python: '%s/skyn/search?type_name=%s&' % (tool.getAppFolder().absolute_url(), contentType);">
|
||||||
|
|
||||||
<tal:result condition="objs">
|
<tal:result condition="objs">
|
||||||
|
|
||||||
|
@ -91,7 +90,7 @@
|
||||||
|
|
||||||
<tal:comment replace="nothing">Mandatory column "Title"/"Name"</tal:comment>
|
<tal:comment replace="nothing">Mandatory column "Title"/"Name"</tal:comment>
|
||||||
<td id="field_title"><a
|
<td id="field_title"><a
|
||||||
tal:define="navInfo python:'search.%s:%d.%s.%d.%d' % (contentType, flavourNumber, searchName, repeat['obj'].number()+startNumber, totalNumber);"
|
tal:define="navInfo python:'search.%s.%s.%d.%d' % (contentType, searchName, repeat['obj'].number()+startNumber, totalNumber);"
|
||||||
tal:content="obj/Title" tal:attributes="href python: obj.getUrl(nav=navInfo, page='main')"></a></td>
|
tal:content="obj/Title" tal:attributes="href python: obj.getUrl(nav=navInfo, page='main')"></a></td>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
|
<tal:comment replace="nothing">Columns corresponding to other fields</tal:comment>
|
||||||
|
@ -125,7 +124,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<tal:comment replace="nothing">Edit the element</tal:comment>
|
<tal:comment replace="nothing">Edit the element</tal:comment>
|
||||||
<td class="noPadding">
|
<td class="noPadding">
|
||||||
<a tal:define="navInfo python:'search.%s:%d.%s.%d.%d' % (contentType, flavourNumber, searchName, repeat['obj'].number()+startNumber, totalNumber);"
|
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (contentType, searchName, repeat['obj'].number()+startNumber, totalNumber);"
|
||||||
tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)"
|
tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)"
|
||||||
tal:condition="python: member.has_permission('Modify portal content', obj)">
|
tal:condition="python: member.has_permission('Modify portal content', obj)">
|
||||||
<img title="Edit" i18n:domain="plone" i18n:attributes="title"
|
<img title="Edit" i18n:domain="plone" i18n:attributes="title"
|
||||||
|
|
|
@ -122,11 +122,11 @@
|
||||||
|
|
||||||
/* The functions below wrap askAjaxChunk for getting specific content through
|
/* The functions below wrap askAjaxChunk for getting specific content through
|
||||||
an Ajax request. */
|
an Ajax request. */
|
||||||
function askQueryResult(hookId, objectUrl, contentType, flavourNumber,
|
function askQueryResult(hookId, objectUrl, contentType, searchName,
|
||||||
searchName, startNumber, sortKey, sortOrder, filterKey) {
|
startNumber, sortKey, sortOrder, filterKey) {
|
||||||
// Sends an Ajax request for getting the result of a query.
|
// Sends an Ajax request for getting the result of a query.
|
||||||
var params = {'type_name': contentType, 'flavourNumber': flavourNumber,
|
var params = {'type_name': contentType, 'search': searchName,
|
||||||
'search': searchName, 'startNumber': startNumber};
|
'startNumber': startNumber};
|
||||||
if (sortKey) params['sortKey'] = sortKey;
|
if (sortKey) params['sortKey'] = sortKey;
|
||||||
if (sortOrder) params['sortOrder'] = sortOrder;
|
if (sortOrder) params['sortOrder'] = sortOrder;
|
||||||
if (filterKey) {
|
if (filterKey) {
|
||||||
|
@ -208,11 +208,9 @@
|
||||||
theForm.submit();
|
theForm.submit();
|
||||||
}
|
}
|
||||||
function onDeleteObject(objectUid) {
|
function onDeleteObject(objectUid) {
|
||||||
if (confirm(delete_confirm)) {
|
f = document.getElementById('deleteForm');
|
||||||
f = document.getElementById('deleteForm');
|
f.objectUid.value = objectUid;
|
||||||
f.objectUid.value = objectUid;
|
askConfirm('form', 'deleteForm', delete_confirm);
|
||||||
f.submit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function toggleCookie(cookieId) {
|
function toggleCookie(cookieId) {
|
||||||
// What is the state of this boolean (expanded/collapsed) cookie?
|
// What is the state of this boolean (expanded/collapsed) cookie?
|
||||||
|
@ -241,10 +239,9 @@
|
||||||
createCookie(cookieId, newState);
|
createCookie(cookieId, newState);
|
||||||
}
|
}
|
||||||
// Function that allows to generate a document from a pod template.
|
// Function that allows to generate a document from a pod template.
|
||||||
function generatePodDocument(contextUid, templateUid, fieldName, podFormat) {
|
function generatePodDocument(contextUid, fieldName, podFormat) {
|
||||||
var theForm = document.getElementsByName("podTemplateForm")[0];
|
var theForm = document.getElementsByName("podTemplateForm")[0];
|
||||||
theForm.objectUid.value = contextUid;
|
theForm.objectUid.value = contextUid;
|
||||||
theForm.templateUid.value = templateUid;
|
|
||||||
theForm.fieldName.value = fieldName;
|
theForm.fieldName.value = fieldName;
|
||||||
theForm.podFormat.value = podFormat;
|
theForm.podFormat.value = podFormat;
|
||||||
theForm.askAction.value = "False";
|
theForm.askAction.value = "False";
|
||||||
|
@ -255,7 +252,10 @@
|
||||||
theForm.submit();
|
theForm.submit();
|
||||||
}
|
}
|
||||||
// Functions for opening and closing a popup
|
// Functions for opening and closing a popup
|
||||||
function openPopup(popupId) {
|
function openPopup(popupId, msg) {
|
||||||
|
// Put the message into the popup
|
||||||
|
var confirmElem = document.getElementById('appyConfirmText');
|
||||||
|
confirmElem.innerHTML = msg;
|
||||||
// Open the popup
|
// Open the popup
|
||||||
var popup = document.getElementById(popupId);
|
var popup = document.getElementById(popupId);
|
||||||
// Put it at the right place on the screen
|
// Put it at the right place on the screen
|
||||||
|
@ -276,19 +276,35 @@
|
||||||
greyed.style.display = "none";
|
greyed.style.display = "none";
|
||||||
}
|
}
|
||||||
// Function triggered when an action needs to be confirmed by the user
|
// Function triggered when an action needs to be confirmed by the user
|
||||||
function askConfirm(formId) {
|
function askConfirm(actionType, action, msg) {
|
||||||
// Store the ID of the form to send if the users confirms.
|
/* Store the actionType (send a form, call an URL or call a script) and the
|
||||||
|
related action, and shows the confirm popup. If the user confirms, we
|
||||||
|
will perform the action. */
|
||||||
var confirmForm = document.getElementById('confirmActionForm');
|
var confirmForm = document.getElementById('confirmActionForm');
|
||||||
confirmForm.actionFormId.value = formId;
|
confirmForm.actionType.value = actionType;
|
||||||
openPopup("confirmActionPopup");
|
confirmForm.action.value = action;
|
||||||
|
openPopup("confirmActionPopup", msg);
|
||||||
}
|
}
|
||||||
// Function triggered when an action confirmed by the user must be performed
|
// Function triggered when an action confirmed by the user must be performed
|
||||||
function doConfirm() {
|
function doConfirm() {
|
||||||
// The user confirmed: retrieve the form to send and send it.
|
// The user confirmed: perform the required action.
|
||||||
|
closePopup('confirmActionPopup');
|
||||||
var confirmForm = document.getElementById('confirmActionForm');
|
var confirmForm = document.getElementById('confirmActionForm');
|
||||||
var actionFormId = confirmForm.actionFormId.value;
|
var actionType = confirmForm.actionType.value;
|
||||||
var actionForm = document.getElementById(actionFormId);
|
var action = confirmForm.action.value;
|
||||||
actionForm.submit();
|
if (actionType == 'form') {
|
||||||
|
// We must submit the form whose id is in "action"
|
||||||
|
document.getElementById(action).submit();
|
||||||
|
}
|
||||||
|
else if (actionType == 'url') {
|
||||||
|
// We must go to the URL defined in "action"
|
||||||
|
window.location = action;
|
||||||
|
}
|
||||||
|
else if (actionType == 'script') {
|
||||||
|
// We must execute Javascript code in "action"
|
||||||
|
eval(action);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// Function that shows or hides a tab. p_action is 'show' or 'hide'.
|
// Function that shows or hides a tab. p_action is 'show' or 'hide'.
|
||||||
function manageTab(tabId, action) {
|
function manageTab(tabId, action) {
|
||||||
|
@ -344,10 +360,8 @@
|
||||||
</form>
|
</form>
|
||||||
<tal:comment replace="nothing">Global form for generating a document from a pod template.</tal:comment>
|
<tal:comment replace="nothing">Global form for generating a document from a pod template.</tal:comment>
|
||||||
<form name="podTemplateForm" method="post"
|
<form name="podTemplateForm" method="post"
|
||||||
tal:attributes="action python: flavour.absolute_url() + '/generateDocument'">
|
tal:attributes="action python: tool.absolute_url() + '/generateDocument'">
|
||||||
<input type="hidden" name="objectUid"/>
|
<input type="hidden" name="objectUid"/>
|
||||||
<tal:comment replace="nothing">templateUid is given if class-wide pod, fieldName and podFormat are given if podField.</tal:comment>
|
|
||||||
<input type="hidden" name="templateUid"/>
|
|
||||||
<input type="hidden" name="fieldName"/>
|
<input type="hidden" name="fieldName"/>
|
||||||
<input type="hidden" name="podFormat"/>
|
<input type="hidden" name="podFormat"/>
|
||||||
<input type="hidden" name="askAction"/>
|
<input type="hidden" name="askAction"/>
|
||||||
|
@ -384,31 +398,6 @@
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<tal:comment replace="nothing">
|
|
||||||
This macro lists the POD templates that are available. It is used by macro "header" below.
|
|
||||||
</tal:comment>
|
|
||||||
<div metal:define-macro="listPodTemplates" class="appyPod" tal:condition="podTemplates"
|
|
||||||
tal:define="podTemplates python: flavour.getAvailablePodTemplates(contextObj, phase);">
|
|
||||||
<tal:podTemplates define="maxShownTemplates python: flavour.getMaxShownTemplates(contextObj)">
|
|
||||||
<tal:comment replace="nothing">Display templates as links if a few number of templates must be shown</tal:comment>
|
|
||||||
<span class="discreet" tal:condition="python: len(podTemplates)<=maxShownTemplates"
|
|
||||||
tal:repeat="podTemplate podTemplates">
|
|
||||||
<a style="cursor: pointer"
|
|
||||||
tal:define="podFormat podTemplate/getPodFormat"
|
|
||||||
tal:attributes="onclick python: 'generatePodDocument(\'%s\',\'%s\', \'\', \'\')' % (contextObj.UID(), podTemplate.UID())" >
|
|
||||||
<img tal:attributes="src string: $portal_url/skyn/$podFormat.png"/>
|
|
||||||
<span tal:replace="podTemplate/Title"/>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<tal:comment replace="nothing">Display templates as a list if a lot of templates must be shown</tal:comment>
|
|
||||||
<select tal:condition="python: len(podTemplates)>maxShownTemplates">
|
|
||||||
<option value="" tal:content="python: tool.translate('choose_a_doc')"></option>
|
|
||||||
<option tal:repeat="podTemplate podTemplates" tal:content="podTemplate/Title"
|
|
||||||
tal:attributes="onclick python: 'generatePodDocument(\'%s\',\'%s\', \'\', \'\')' % (contextObj.UID(), podTemplate.UID())" />
|
|
||||||
</select>
|
|
||||||
</tal:podTemplates>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<tal:comment replace="nothing">
|
<tal:comment replace="nothing">
|
||||||
This macro displays an object's history. It is used by macro "header" below.
|
This macro displays an object's history. It is used by macro "header" below.
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
|
@ -460,7 +449,7 @@
|
||||||
<th align="left" width="70%" tal:content="python: tool.translate('previous_value')"></th>
|
<th align="left" width="70%" tal:content="python: tool.translate('previous_value')"></th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr tal:repeat="change event/changes/items" valign="top">
|
<tr tal:repeat="change event/changes/items" valign="top">
|
||||||
<td tal:content="python: tool.translate(change[1][1])"></td>
|
<td tal:content="structure python: tool.translate(change[1][1])"></td>
|
||||||
<td tal:define="appyValue python: contextObj.getFormattedFieldValue(change[0], change[1][0]);
|
<td tal:define="appyValue python: contextObj.getFormattedFieldValue(change[0], change[1][0]);
|
||||||
appyType python:contextObj.getAppyType(change[0], asDict=True);
|
appyType python:contextObj.getAppyType(change[0], asDict=True);
|
||||||
severalValues python: (appyType['multiplicity'][1] > 1) or (appyType['multiplicity'][1] == None)">
|
severalValues python: (appyType['multiplicity'][1] > 1) or (appyType['multiplicity'][1] == None)">
|
||||||
|
@ -482,13 +471,13 @@
|
||||||
This macro displays an object's state(s). It is used by macro "header" below.
|
This macro displays an object's state(s). It is used by macro "header" below.
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
<metal:states define-macro="states"
|
<metal:states define-macro="states"
|
||||||
tal:define="showAllStatesInPhase python: flavour.getAttr('showAllStatesInPhaseFor' + contextObj.meta_type);
|
tal:define="showAllStatesInPhase python: tool.getAttr('showAllStatesInPhaseFor' + contextObj.meta_type);
|
||||||
states python: contextObj.getAppyStates(phase, currentOnly=not showAllStatesInPhase)"
|
states python: contextObj.getAppyStates(phase, currentOnly=not showAllStatesInPhase)"
|
||||||
tal:condition="python: test(showAllStatesInPhase, len(states)>1, True)">
|
tal:condition="python: test(showAllStatesInPhase, len(states)>1, True)">
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<tal:state repeat="stateInfo states">
|
<tal:state repeat="stateInfo states">
|
||||||
<td tal:attributes="class python: 'appyState step' + stateInfo['stateStatus']"
|
<td tal:attributes="class python: 'appyState step%sState' % stateInfo['stateStatus']"
|
||||||
tal:content="python: tool.translate(contextObj.getWorkflowLabel(stateInfo['name']))">
|
tal:content="python: tool.translate(contextObj.getWorkflowLabel(stateInfo['name']))">
|
||||||
</td>
|
</td>
|
||||||
<td tal:condition="python: stateInfo['name'] != states[-1]['name']">
|
<td tal:condition="python: stateInfo['name'] != states[-1]['name']">
|
||||||
|
@ -512,7 +501,7 @@
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<tal:comment replace="nothing">Input field allowing to enter a comment before triggering a transition</tal:comment>
|
<tal:comment replace="nothing">Input field allowing to enter a comment before triggering a transition</tal:comment>
|
||||||
<td tal:define="showCommentsField python:flavour.getAttr('showWorkflowCommentFieldFor'+contextObj.meta_type)"
|
<td tal:define="showCommentsField python:tool.getAttr('showWorkflowCommentFieldFor'+contextObj.meta_type)"
|
||||||
align="right" tal:condition="showCommentsField">
|
align="right" tal:condition="showCommentsField">
|
||||||
<span tal:content="python: tool.translate('workflow_comment')" class="discreet"></span>
|
<span tal:content="python: tool.translate('workflow_comment')" class="discreet"></span>
|
||||||
<input type="text" id="comment" name="comment" size="35"/>
|
<input type="text" id="comment" name="comment" size="35"/>
|
||||||
|
@ -520,7 +509,7 @@
|
||||||
|
|
||||||
<tal:comment replace="nothing">Buttons for triggering transitions</tal:comment>
|
<tal:comment replace="nothing">Buttons for triggering transitions</tal:comment>
|
||||||
<td align="right" tal:repeat="transition transitions">
|
<td align="right" tal:repeat="transition transitions">
|
||||||
<input type="button" class="context"
|
<input type="button" class="appyButton"
|
||||||
tal:attributes="value python: tool.translate(transition['name']);
|
tal:attributes="value python: tool.translate(transition['name']);
|
||||||
onClick python: 'triggerTransition(\'%s\')' % transition['id'];"/>
|
onClick python: 'triggerTransition(\'%s\')' % transition['id'];"/>
|
||||||
</td>
|
</td>
|
||||||
|
@ -556,7 +545,7 @@
|
||||||
<td colspan="2" class="discreet" tal:content="descrLabel"/>
|
<td colspan="2" class="discreet" tal:content="descrLabel"/>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="documentByLine">
|
<td class="documentByLine" colspan="2">
|
||||||
<tal:comment replace="nothing">Creator and last modification date</tal:comment>
|
<tal:comment replace="nothing">Creator and last modification date</tal:comment>
|
||||||
<tal:comment replace="nothing">Plus/minus icon for accessing history</tal:comment>
|
<tal:comment replace="nothing">Plus/minus icon for accessing history</tal:comment>
|
||||||
<tal:accessHistory condition="hasHistory">
|
<tal:accessHistory condition="hasHistory">
|
||||||
|
@ -580,8 +569,6 @@
|
||||||
<span i18n:translate="box_last_modified" i18n:domain="plone"></span>
|
<span i18n:translate="box_last_modified" i18n:domain="plone"></span>
|
||||||
<span tal:replace="python:contextObj.restrictedTraverse('@@plone').toLocalizedTime(contextObj.ModificationDate(),long_format=1)"></span>
|
<span tal:replace="python:contextObj.restrictedTraverse('@@plone').toLocalizedTime(contextObj.ModificationDate(),long_format=1)"></span>
|
||||||
</td>
|
</td>
|
||||||
<td valign="top"><metal:pod use-macro="here/skyn/page/macros/listPodTemplates"/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tal:comment replace="nothing">Object history</tal:comment>
|
<tal:comment replace="nothing">Object history</tal:comment>
|
||||||
<tr tal:condition="hasHistory">
|
<tr tal:condition="hasHistory">
|
||||||
|
@ -631,7 +618,7 @@
|
||||||
masterValue.push(idField);
|
masterValue.push(idField);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (idField[0] == '(') {
|
if ((idField[0] == '(') || (idField[0] == '[')) {
|
||||||
// There are multiple values, split it
|
// There are multiple values, split it
|
||||||
var subValues = idField.substring(1, idField.length-1).split(',');
|
var subValues = idField.substring(1, idField.length-1).split(',');
|
||||||
for (var k=0; k < subValues.length; k++){
|
for (var k=0; k < subValues.length; k++){
|
||||||
|
|
|
@ -8,13 +8,13 @@
|
||||||
contextObj python: tool.getPublishedObject()">
|
contextObj python: tool.getPublishedObject()">
|
||||||
<tal:comment replace="nothing">Portlet title, with link to tool.</tal:comment>
|
<tal:comment replace="nothing">Portlet title, with link to tool.</tal:comment>
|
||||||
<dt class="portletHeader">
|
<dt class="portletHeader">
|
||||||
<tal:comment replace="nothing">If there is only one flavour, clicking on the portlet
|
<tal:comment replace="nothing">For the Manager, clicking on the portlet
|
||||||
title allows to see all root objects in the database.</tal:comment>
|
title allows to see all root objects in the database.</tal:comment>
|
||||||
<table cellpadding="0" cellspacing="0" width="100%">
|
<table cellpadding="0" cellspacing="0" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
<td tal:define="titleIsClickable python: member.has_role('Manager') and rootClasses">
|
<td tal:define="titleIsClickable python: member.has_role('Manager') and rootClasses">
|
||||||
<a tal:condition="titleIsClickable"
|
<a tal:condition="titleIsClickable"
|
||||||
tal:attributes="href python:'%s?type_name=%s&flavourNumber=1' % (queryUrl, ','.join(rootClasses))"
|
tal:attributes="href python:'%s?type_name=%s' % (queryUrl, ','.join(rootClasses))"
|
||||||
tal:content="python: tool.translate(appName)"></a>
|
tal:content="python: tool.translate(appName)"></a>
|
||||||
<span tal:condition="not: titleIsClickable"
|
<span tal:condition="not: titleIsClickable"
|
||||||
tal:replace="python: tool.translate(appName)"/>
|
tal:replace="python: tool.translate(appName)"/>
|
||||||
|
@ -37,16 +37,14 @@
|
||||||
</tal:publishedObject>
|
</tal:publishedObject>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Create a section for every root class.</tal:comment>
|
<tal:comment replace="nothing">Create a section for every root class.</tal:comment>
|
||||||
<tal:section repeat="rootClass rootClasses"
|
<tal:section repeat="rootClass rootClasses">
|
||||||
define="flavourNumber python:1;
|
|
||||||
flavour python: tool.getFlavour('Dummy_%d' % flavourNumber)">
|
|
||||||
<tal:comment replace="nothing">Section title, with action icons</tal:comment>
|
<tal:comment replace="nothing">Section title, with action icons</tal:comment>
|
||||||
<dt tal:condition="python: tool.userMaySearch(rootClass)"
|
<dt tal:condition="python: tool.userMaySearch(rootClass)"
|
||||||
tal:attributes="class python:test((repeat['rootClass'].number()==1) and not contextObj, 'portletAppyItem', 'portletAppyItem portletSep')">
|
tal:attributes="class python:test((repeat['rootClass'].number()==1) and not contextObj, 'portletAppyItem', 'portletAppyItem portletSep')">
|
||||||
<table width="100%" cellspacing="0" cellpadding="0" class="no-style-table">
|
<table width="100%" cellspacing="0" cellpadding="0" class="no-style-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a tal:attributes="href python: '%s?type_name=%s&flavourNumber=%s' % (queryUrl, rootClass, flavourNumber);
|
<a tal:attributes="href python: '%s?type_name=%s' % (queryUrl, rootClass);
|
||||||
class python:test(not currentSearch and (currentType==rootClass), 'portletCurrent', '')"
|
class python:test(not currentSearch and (currentType==rootClass), 'portletCurrent', '')"
|
||||||
tal:content="python: tool.translate(rootClass + '_plural')"></a>
|
tal:content="python: tool.translate(rootClass + '_plural')"></a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -66,11 +64,11 @@
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/import?type_name=%s\'' % (appFolder.absolute_url(), rootClass);
|
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/import?type_name=%s\'' % (appFolder.absolute_url(), rootClass);
|
||||||
src string: $portal_url/skyn/import.png;
|
src string: $portal_url/skyn/import.png;
|
||||||
title python: tool.translate('query_import')"/>
|
title python: tool.translate('query_import')"/>
|
||||||
<tal:comment replace="nothing">Search objects of this type (todo: update flavourNumber)</tal:comment>
|
<tal:comment replace="nothing">Search objects of this type</tal:comment>
|
||||||
<img style="cursor:pointer"
|
<img style="cursor:pointer"
|
||||||
tal:define="showSearch python: flavour.getAttr('enableAdvancedSearchFor%s' % rootClass)"
|
tal:define="showSearch python: tool.getAttr('enableAdvancedSearchFor%s' % rootClass)"
|
||||||
tal:condition="showSearch"
|
tal:condition="showSearch"
|
||||||
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/search?type_name=%s&flavourNumber=1\'' % (appFolder.absolute_url(), rootClass);
|
tal:attributes="onClick python: 'href: window.location=\'%s/skyn/search?type_name=%s\'' % (appFolder.absolute_url(), rootClass);
|
||||||
src string: $portal_url/skyn/search.gif;
|
src string: $portal_url/skyn/search.gif;
|
||||||
title python: tool.translate('search_objects')"/>
|
title python: tool.translate('search_objects')"/>
|
||||||
</td>
|
</td>
|
||||||
|
@ -94,7 +92,7 @@
|
||||||
<span tal:attributes="id group/labelId;
|
<span tal:attributes="id group/labelId;
|
||||||
style python:test(expanded, 'display:block', 'display:none')">
|
style python:test(expanded, 'display:block', 'display:none')">
|
||||||
<dt class="portletAppyItem portletSearch portletGroupItem" tal:repeat="search group/searches">
|
<dt class="portletAppyItem portletSearch portletGroupItem" tal:repeat="search group/searches">
|
||||||
<a tal:attributes="href python: '%s?type_name=%s&flavourNumber=%s&search=%s' % (queryUrl, rootClass, flavourNumber, search['name']);
|
<a tal:attributes="href python: '%s?type_name=%s&search=%s' % (queryUrl, rootClass, search['name']);
|
||||||
title search/descr;
|
title search/descr;
|
||||||
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
||||||
tal:content="structure search/label"></a>
|
tal:content="structure search/label"></a>
|
||||||
|
@ -105,7 +103,7 @@
|
||||||
<dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup"
|
<dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup"
|
||||||
class="portletAppyItem portletSearch">
|
class="portletAppyItem portletSearch">
|
||||||
|
|
||||||
<a tal:attributes="href python: '%s?type_name=%s&flavourNumber=%s&search=%s' % (queryUrl, rootClass, flavourNumber, search['name']);
|
<a tal:attributes="href python: '%s?type_name=%s&search=%s' % (queryUrl, rootClass, search['name']);
|
||||||
title search/descr;
|
title search/descr;
|
||||||
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
||||||
tal:content="structure search/label"></a>
|
tal:content="structure search/label"></a>
|
||||||
|
@ -113,16 +111,6 @@
|
||||||
</tal:searchOrGroup>
|
</tal:searchOrGroup>
|
||||||
</tal:section>
|
</tal:section>
|
||||||
|
|
||||||
<tal:comment replace="nothing">All objects in flavour</tal:comment>
|
|
||||||
<!--dt class="portletAppyItem" tal:define="flavourInfo python: flavours[0]">
|
|
||||||
<a tal:define="flavourNumber flavourInfo/number;
|
|
||||||
rootTypes python: test(flavourNumber==1, rootClasses, ['%s_%s' % (rc, flavourNumber) for rc in rootClasses]);
|
|
||||||
rootClassesQuery python:','.join(rootTypes)"
|
|
||||||
tal:content="flavourInfo/title"
|
|
||||||
tal:attributes="title python: tool.translate('query_consult_all');
|
|
||||||
href python:'%s?type_name=%s&flavourNumber=%d' % (queryUrl, rootClassesQuery, flavourNumber)"></a>
|
|
||||||
</dt-->
|
|
||||||
|
|
||||||
<tal:comment replace="nothing">
|
<tal:comment replace="nothing">
|
||||||
Greyed transparent zone that is deployed on the
|
Greyed transparent zone that is deployed on the
|
||||||
whole screen when a popup is displayed.
|
whole screen when a popup is displayed.
|
||||||
|
@ -133,8 +121,9 @@
|
||||||
<div id="confirmActionPopup" class="appyPopup">
|
<div id="confirmActionPopup" class="appyPopup">
|
||||||
<form id="confirmActionForm" method="post">
|
<form id="confirmActionForm" method="post">
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<p tal:content="python: tool.translate('confirm')"></p>
|
<p id="appyConfirmText"></p>
|
||||||
<input type="hidden" name="actionFormId"/>
|
<input type="hidden" name="actionType"/>
|
||||||
|
<input type="hidden" name="action"/>
|
||||||
<input type="button" onClick="doConfirm()"
|
<input type="button" onClick="doConfirm()"
|
||||||
tal:attributes="value python:tool.translate('yes')"/>
|
tal:attributes="value python:tool.translate('yes')"/>
|
||||||
<input type="button" value="No" onClick="closePopup('confirmActionPopup')"
|
<input type="button" value="No" onClick="closePopup('confirmActionPopup')"
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
appName appFolder/id;
|
appName appFolder/id;
|
||||||
tool python: portal.get('portal_%s' % appName.lower());
|
tool python: portal.get('portal_%s' % appName.lower());
|
||||||
contentType python:context.REQUEST.get('type_name');
|
contentType python:context.REQUEST.get('type_name');
|
||||||
flavour python: tool.getFlavour(contentType);
|
|
||||||
flavourNumber python:int(context.REQUEST.get('flavourNumber', 1));
|
|
||||||
searchName python:context.REQUEST.get('search', '')">
|
searchName python:context.REQUEST.get('search', '')">
|
||||||
|
|
||||||
<div metal:use-macro="here/skyn/page/macros/prologue"/>
|
<div metal:use-macro="here/skyn/page/macros/prologue"/>
|
||||||
|
@ -25,8 +23,8 @@
|
||||||
<div id="queryResult"></div>
|
<div id="queryResult"></div>
|
||||||
|
|
||||||
<script language="javascript"
|
<script language="javascript"
|
||||||
tal:define="ajaxUrl python: tool.getQueryUrl(contentType, flavourNumber, searchName)"
|
tal:define="ajaxUrl python: tool.getQueryUrl(contentType, searchName)"
|
||||||
tal:content="python: 'askQueryResult(\'queryResult\', \'%s\',\'%s\',\'%s\',\'%s\',0)' % (tool.absolute_url(), contentType, flavourNumber, searchName)">
|
tal:content="python: 'askQueryResult(\'queryResult\', \'%s\',\'%s\',\'%s\',0)' % (tool.absolute_url(), contentType, searchName)">
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -16,8 +16,7 @@
|
||||||
tal:define="appFolder context/getParentNode;
|
tal:define="appFolder context/getParentNode;
|
||||||
contentType request/type_name;
|
contentType request/type_name;
|
||||||
tool python: portal.get('portal_%s' % appFolder.id.lower());
|
tool python: portal.get('portal_%s' % appFolder.id.lower());
|
||||||
flavour python: tool.getFlavour('Dummy_%s' % request['flavourNumber']);
|
searchableFields python: tool.getSearchableFields(contentType)">
|
||||||
searchableFields python: flavour.getSearchableFields(contentType)">
|
|
||||||
|
|
||||||
<tal:comment replace="nothing">Search title</tal:comment>
|
<tal:comment replace="nothing">Search title</tal:comment>
|
||||||
<h1><span tal:replace="python: tool.translate('%s_plural' % contentType)"/> —
|
<h1><span tal:replace="python: tool.translate('%s_plural' % contentType)"/> —
|
||||||
|
@ -27,10 +26,9 @@
|
||||||
<form name="search" tal:attributes="action python: appFolder.absolute_url()+'/skyn/do'" method="post">
|
<form name="search" tal:attributes="action python: appFolder.absolute_url()+'/skyn/do'" method="post">
|
||||||
<input type="hidden" name="action" value="SearchObjects"/>
|
<input type="hidden" name="action" value="SearchObjects"/>
|
||||||
<input type="hidden" name="type_name" tal:attributes="value contentType"/>
|
<input type="hidden" name="type_name" tal:attributes="value contentType"/>
|
||||||
<input type="hidden" name="flavourNumber:int" tal:attributes="value request/flavourNumber"/>
|
|
||||||
|
|
||||||
<table class="no-style-table" cellpadding="0" cellspacing="0" width="100%"
|
<table class="no-style-table" cellpadding="0" cellspacing="0" width="100%"
|
||||||
tal:define="numberOfColumns python: flavour.getAttr('numberOfSearchColumnsFor%s' % contentType)">
|
tal:define="numberOfColumns python: tool.getAttr('numberOfSearchColumnsFor%s' % contentType)">
|
||||||
<tr tal:repeat="searchRow python: tool.tabularize(searchableFields, numberOfColumns)" valign="top">
|
<tr tal:repeat="searchRow python: tool.tabularize(searchableFields, numberOfColumns)" valign="top">
|
||||||
<td tal:repeat="widget searchRow" tal:attributes="width python:'%d%%' % (100/numberOfColumns)">
|
<td tal:repeat="widget searchRow" tal:attributes="width python:'%d%%' % (100/numberOfColumns)">
|
||||||
<tal:field condition="widget">
|
<tal:field condition="widget">
|
||||||
|
|
|
@ -21,13 +21,12 @@
|
||||||
layoutType python:'view';
|
layoutType python:'view';
|
||||||
layout python: contextObj.getPageLayout(layoutType);
|
layout python: contextObj.getPageLayout(layoutType);
|
||||||
tool contextObj/getTool;
|
tool contextObj/getTool;
|
||||||
flavour python: tool.getFlavour(contextObj);
|
|
||||||
appFolder tool/getAppFolder;
|
appFolder tool/getAppFolder;
|
||||||
appName appFolder/getId;
|
appName appFolder/getId;
|
||||||
page request/page|python:'main';
|
page request/page|python:'main';
|
||||||
phaseInfo python: contextObj.getAppyPhases(page=page);
|
phaseInfo python: contextObj.getAppyPhases(page=page);
|
||||||
phase phaseInfo/name;
|
phase phaseInfo/name;
|
||||||
showWorkflow python: flavour.getAttr('showWorkflowFor' + contextObj.meta_type)">
|
showWorkflow python: tool.getAttr('showWorkflowFor' + contextObj.meta_type)">
|
||||||
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
|
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
|
||||||
<metal:show use-macro="here/skyn/page/macros/show"/>
|
<metal:show use-macro="here/skyn/page/macros/show"/>
|
||||||
<metal:footer use-macro="here/skyn/page/macros/footer"/>
|
<metal:footer use-macro="here/skyn/page/macros/footer"/>
|
||||||
|
|
|
@ -2,14 +2,15 @@
|
||||||
<metal:view define-macro="view">
|
<metal:view define-macro="view">
|
||||||
<form name="executeAppyAction"
|
<form name="executeAppyAction"
|
||||||
tal:define="formId python: '%s_%s' % (contextObj.UID(), name);
|
tal:define="formId python: '%s_%s' % (contextObj.UID(), name);
|
||||||
label python: contextObj.translate(widget['labelId'])"
|
label python: contextObj.translate(widget['labelId']);
|
||||||
|
labelConfirm python: contextObj.translate(widget['labelId'] + '_confirm')"
|
||||||
tal:attributes="id formId; action python: contextObj.absolute_url()+'/skyn/do'">
|
tal:attributes="id formId; action python: contextObj.absolute_url()+'/skyn/do'">
|
||||||
<input type="hidden" name="action" value="ExecuteAppyAction"/>
|
<input type="hidden" name="action" value="ExecuteAppyAction"/>
|
||||||
<input type="hidden" name="objectUid" tal:attributes="value contextObj/UID"/>
|
<input type="hidden" name="objectUid" tal:attributes="value contextObj/UID"/>
|
||||||
<input type="hidden" name="fieldName" tal:attributes="value name"/>
|
<input type="hidden" name="fieldName" tal:attributes="value name"/>
|
||||||
<input type="button" tal:condition="widget/confirm"
|
<input type="button" tal:condition="widget/confirm"
|
||||||
tal:attributes="value label;
|
tal:attributes="value label;
|
||||||
onClick python: 'askConfirm(\'%s\')' % formId"/>
|
onClick python: 'askConfirm(\'form\', \'%s\', "%s")' % (formId, labelConfirm)"/>
|
||||||
<input type="submit" name="do" tal:condition="not: widget/confirm"
|
<input type="submit" name="do" tal:condition="not: widget/confirm"
|
||||||
tal:attributes="value label" onClick="javascript:;"/>
|
tal:attributes="value label" onClick="javascript:;"/>
|
||||||
<tal:comment replace="nothing">The previous onClick is simply used to prevent Plone
|
<tal:comment replace="nothing">The previous onClick is simply used to prevent Plone
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
<label tal:attributes="for chekboxId" class="discreet"
|
<label tal:attributes="for chekboxId" class="discreet"
|
||||||
tal:content="python: tool.translate(doLabel)"></label>
|
tal:content="python: tool.translate(doLabel)"></label>
|
||||||
</tal:askAction>
|
</tal:askAction>
|
||||||
<img tal:repeat="podFormat python:flavour.getPodInfo(contextObj, name)['formats']"
|
<img tal:repeat="podFormat python: tool.getPodInfo(contextObj, name)['formats']"
|
||||||
tal:attributes="src string: $portal_url/skyn/${podFormat}.png;
|
tal:attributes="src string: $portal_url/skyn/${podFormat}.png;
|
||||||
onClick python: 'generatePodDocument(\'%s\',\'\',\'%s\',\'%s\')' % (contextObj.UID(), name, podFormat);
|
onClick python: 'generatePodDocument(\'%s\',\'%s\',\'%s\')' % (contextObj.UID(), name, podFormat);
|
||||||
title podFormat/capitalize"
|
title podFormat/capitalize"
|
||||||
style="cursor:pointer"/>
|
style="cursor:pointer"/>
|
||||||
</metal:view>
|
</metal:view>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
from one object to the next/previous on skyn/view.</tal:comment>
|
from one object to the next/previous on skyn/view.</tal:comment>
|
||||||
<a tal:define="includeShownInfo includeShownInfo | python:False;
|
<a tal:define="includeShownInfo includeShownInfo | python:False;
|
||||||
navInfo python:'ref.%s.%s:%s.%d.%d' % (contextObj.UID(), fieldName, appyType['page'], repeat['obj'].number()+startNumber, totalNumber);
|
navInfo python:'ref.%s.%s:%s.%d.%d' % (contextObj.UID(), fieldName, appyType['page'], repeat['obj'].number()+startNumber, totalNumber);
|
||||||
navInfo python: appyType['isBack'] and '' or navInfo;
|
navInfo python: test(appyType['isBack'], '', navInfo);
|
||||||
pageName python: appyType['isBack'] and appyType['backd']['page'] or 'main';
|
pageName python: appyType['isBack'] and appyType['backd']['page'] or 'main';
|
||||||
fullUrl python: obj.getUrl(page=pageName, nav=navInfo)"
|
fullUrl python: obj.getUrl(page=pageName, nav=navInfo)"
|
||||||
tal:attributes="href fullUrl" tal:content="python: (not includeShownInfo) and obj.Title() or contextObj.getReferenceLabel(fieldName, obj.appy())"></a>
|
tal:attributes="href fullUrl" tal:content="python: (not includeShownInfo) and obj.Title() or contextObj.getReferenceLabel(fieldName, obj.appy())"></a>
|
||||||
|
@ -40,13 +40,13 @@
|
||||||
</tal:moveRef>
|
</tal:moveRef>
|
||||||
</td>
|
</td>
|
||||||
<tal:comment replace="nothing">Edit the element</tal:comment>
|
<tal:comment replace="nothing">Edit the element</tal:comment>
|
||||||
<td class="noPadding">
|
<td class="noPadding" tal:condition="python: member.has_permission('Modify portal content', obj) and not appyType['noForm']">
|
||||||
<a tal:define="navInfo python:'ref.%s.%s:%s.%d.%d' % (contextObj.UID(), fieldName, appyType['page'], repeat['obj'].number()+startNumber, totalNumber);"
|
<a tal:define="navInfo python:'ref.%s.%s:%s.%d.%d' % (contextObj.UID(), fieldName, appyType['page'], repeat['obj'].number()+startNumber, totalNumber);"
|
||||||
tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)"
|
tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)">
|
||||||
tal:condition="python: member.has_permission('Modify portal content', obj)">
|
|
||||||
<img title="label_edit" i18n:domain="plone" i18n:attributes="title"
|
<img title="label_edit" i18n:domain="plone" i18n:attributes="title"
|
||||||
tal:attributes="src string: $portal_url/skyn/edit.gif"/>
|
tal:attributes="src string: $portal_url/skyn/edit.gif"/>
|
||||||
</a></td>
|
</a>
|
||||||
|
</td>
|
||||||
<tal:comment replace="nothing">Delete the element</tal:comment>
|
<tal:comment replace="nothing">Delete the element</tal:comment>
|
||||||
<td class="noPadding">
|
<td class="noPadding">
|
||||||
<img tal:condition="python: member.has_permission('Delete objects', obj)"
|
<img tal:condition="python: member.has_permission('Delete objects', obj)"
|
||||||
|
@ -63,10 +63,14 @@
|
||||||
through a reference widget. Indeed, If field was declared as "addable", we must provide
|
through a reference widget. Indeed, If field was declared as "addable", we must provide
|
||||||
an icon for creating a new linked object (at least if multiplicities allow it).</tal:comment>
|
an icon for creating a new linked object (at least if multiplicities allow it).</tal:comment>
|
||||||
<img style="cursor:pointer" tal:condition="showPlusIcon"
|
<img style="cursor:pointer" tal:condition="showPlusIcon"
|
||||||
tal:define="navInfo python:'ref.%s.%s:%s.%d.%d' % (contextObj.UID(), fieldName, appyType['page'], 0, totalNumber);"
|
tal:define="navInfo python:'ref.%s.%s:%s.%d.%d' % (contextObj.UID(), fieldName, appyType['page'], 0, totalNumber);
|
||||||
|
formCall python:'window.location=\'%s/skyn/do?action=Create&type_name=%s&nav=%s\'' % (folder.absolute_url(), linkedPortalType, navInfo);
|
||||||
|
formCall python: test(appyType['addConfirm'], 'askConfirm(\'script\', "%s", "%s")' % (formCall, addConfirmMsg), formCall);
|
||||||
|
noFormCall python: navBaseCall.replace('**v**', '%d, \'CreateWithoutForm\'' % startNumber);
|
||||||
|
noFormCall python: test(appyType['addConfirm'], 'askConfirm(\'script\', "%s", "%s")' % (noFormCall, addConfirmMsg), noFormCall)"
|
||||||
tal:attributes="src string:$portal_url/skyn/plus.png;
|
tal:attributes="src string:$portal_url/skyn/plus.png;
|
||||||
title python: tool.translate('add_ref');
|
title python: tool.translate('add_ref');
|
||||||
onClick python: 'href: window.location=\'%s/skyn/do?action=Create&type_name=%s&nav=%s\'' % (folder.absolute_url(), linkedPortalType, navInfo)"/>
|
onClick python: test(appyType['noForm'], noFormCall, formCall)"/>
|
||||||
</metal:plusIcon>
|
</metal:plusIcon>
|
||||||
|
|
||||||
<tal:comment replace="nothing">
|
<tal:comment replace="nothing">
|
||||||
|
@ -109,8 +113,7 @@
|
||||||
totalNumber refObjects/totalNumber;
|
totalNumber refObjects/totalNumber;
|
||||||
batchSize refObjects/batchSize;
|
batchSize refObjects/batchSize;
|
||||||
folder python: contextObj.isPrincipiaFolderish and contextObj or contextObj.getParentNode();
|
folder python: contextObj.isPrincipiaFolderish and contextObj or contextObj.getParentNode();
|
||||||
flavour python:tool.getFlavour(contextObj);
|
linkedPortalType python: tool.getPortalType(appyType['klass']);
|
||||||
linkedPortalType python:flavour.getPortalType(appyType['klass']);
|
|
||||||
addPermission python: '%s: Add %s' % (tool.getAppName(), linkedPortalType);
|
addPermission python: '%s: Add %s' % (tool.getAppName(), linkedPortalType);
|
||||||
canWrite python: not appyType['isBack'] and member.has_permission(appyType['writePermission'], contextObj);
|
canWrite python: not appyType['isBack'] and member.has_permission(appyType['writePermission'], contextObj);
|
||||||
multiplicity appyType/multiplicity;
|
multiplicity appyType/multiplicity;
|
||||||
|
@ -118,6 +121,7 @@
|
||||||
showPlusIcon python:not appyType['isBack'] and appyType['add'] and not maxReached and member.has_permission(addPermission, folder) and canWrite;
|
showPlusIcon python:not appyType['isBack'] and appyType['add'] and not maxReached and member.has_permission(addPermission, folder) and canWrite;
|
||||||
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)<=1);
|
atMostOneRef python: (multiplicity[1] == 1) and (len(objs)<=1);
|
||||||
label python: tool.translate(appyType['labelId']);
|
label python: tool.translate(appyType['labelId']);
|
||||||
|
addConfirmMsg python: tool.translate('%s_addConfirm' % appyType['labelId']);
|
||||||
description python: tool.translate(appyType['descrId']);
|
description python: tool.translate(appyType['descrId']);
|
||||||
navBaseCall python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, contextObj.absolute_url(), fieldName, innerRef)">
|
navBaseCall python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, contextObj.absolute_url(), fieldName, innerRef)">
|
||||||
|
|
||||||
|
@ -240,13 +244,11 @@
|
||||||
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<tal:comment replace="nothing">A carriage return needed in some cases.</tal:comment>
|
<tal:comment replace="nothing">A carriage return needed in some cases.</tal:comment>
|
||||||
<br tal:define="widgetDescr widgetDescr|nothing"
|
|
||||||
tal:condition="python: not widgetDescr or (widgetDescr['widgetType'] != 'group')"/>
|
|
||||||
</tal:anyNumberOfReferences>
|
</tal:anyNumberOfReferences>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Edit macro for an Ref.</tal:comment>
|
<tal:comment replace="nothing">Edit macro for an Ref.</tal:comment>
|
||||||
<div metal:define-macro="edit"
|
<metal:editRef define-macro="edit"
|
||||||
tal:condition="widget/link"
|
tal:condition="widget/link"
|
||||||
tal:define="rname python: 'appy_ref_%s' % name;
|
tal:define="rname python: 'appy_ref_%s' % name;
|
||||||
requestValue python: request.get(rname, []);
|
requestValue python: request.get(rname, []);
|
||||||
|
@ -255,18 +257,18 @@
|
||||||
refUids python: [o.UID() for o in contextObj.getAppyRefs(name)['objects']];
|
refUids python: [o.UID() for o in contextObj.getAppyRefs(name)['objects']];
|
||||||
isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())">
|
isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())">
|
||||||
|
|
||||||
<select tal:attributes="name rname;
|
<select tal:attributes="name rname;
|
||||||
multiple python: isMultiple and 'multiple' or ''">
|
multiple python: isMultiple and 'multiple' or ''">
|
||||||
<option tal:condition="not: isMultiple" i18n:translate="choose_a_value"></option>
|
<option tal:condition="not: isMultiple" i18n:translate="choose_a_value"></option>
|
||||||
<tal:ref repeat="refObj allObjects">
|
<tal:ref repeat="refObj allObjects">
|
||||||
<option tal:define="uid python: contextObj.getReferenceUid(refObj)"
|
<option tal:define="uid python: contextObj.getReferenceUid(refObj)"
|
||||||
tal:content="python: contextObj.getReferenceLabel(name, refObj)"
|
tal:content="python: contextObj.getReferenceLabel(name, refObj)"
|
||||||
tal:attributes="value uid;
|
tal:attributes="value uid;
|
||||||
selected python:(inRequest and (uid in requestValue) or (not inRequest and ((uid in refUids)))) and True or False">
|
selected python:(inRequest and (uid in requestValue) or (not inRequest and ((uid in refUids)))) and True or False">
|
||||||
</option>
|
</option>
|
||||||
</tal:ref>
|
</tal:ref>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</metal:editRef>
|
||||||
|
|
||||||
<tal:comment replace="nothing">Cell macro for a Ref.</tal:comment>
|
<tal:comment replace="nothing">Cell macro for a Ref.</tal:comment>
|
||||||
<metal:cell define-macro="cell">
|
<metal:cell define-macro="cell">
|
||||||
|
|
|
@ -90,7 +90,9 @@
|
||||||
<metal:content use-macro="portal/skyn/widgets/show/macros/groupContent"/>
|
<metal:content use-macro="portal/skyn/widgets/show/macros/groupContent"/>
|
||||||
</tal:asSection>
|
</tal:asSection>
|
||||||
<tal:asTabs condition="python: widget['style'] == 'tabs'">
|
<tal:asTabs condition="python: widget['style'] == 'tabs'">
|
||||||
<table cellpadding="0" cellspacing="0" tal:attributes="width python: test(widget['wide'], '100%', '')">
|
<table cellpadding="0" cellspacing="0"
|
||||||
|
tal:attributes="width python: test(widget['wide'], '100%', '');
|
||||||
|
class widget/css_class">
|
||||||
<tal:comment replace="nothing">First row: the tabs.</tal:comment>
|
<tal:comment replace="nothing">First row: the tabs.</tal:comment>
|
||||||
<tr valign="middle"><td style="border-bottom: 1px solid #ff8040">
|
<tr valign="middle"><td style="border-bottom: 1px solid #ff8040">
|
||||||
<table cellpadding="0" cellspacing="0" style="position:relative; bottom:-1px;">
|
<table cellpadding="0" cellspacing="0" style="position:relative; bottom:-1px;">
|
||||||
|
@ -137,7 +139,8 @@
|
||||||
</tal:comment>
|
</tal:comment>
|
||||||
<table metal:define-macro="groupContent"
|
<table metal:define-macro="groupContent"
|
||||||
tal:attributes="width python: test(widget['wide'], '100%', '');
|
tal:attributes="width python: test(widget['wide'], '100%', '');
|
||||||
align widget/align">
|
align widget/align;
|
||||||
|
class widget/css_class">
|
||||||
<tal:comment replace="nothing">Display the title of the group if it is not rendered a fieldset.</tal:comment>
|
<tal:comment replace="nothing">Display the title of the group if it is not rendered a fieldset.</tal:comment>
|
||||||
<tr tal:condition="python: (widget['style'] != 'fieldset') and widget['hasLabel']">
|
<tr tal:condition="python: (widget['style'] != 'fieldset') and widget['hasLabel']">
|
||||||
<td tal:attributes="colspan python: len(widget['columnsWidths']);
|
<td tal:attributes="colspan python: len(widget['columnsWidths']);
|
||||||
|
|
|
@ -4,7 +4,7 @@ from DateTime import DateTime
|
||||||
from Products.Archetypes.atapi import *
|
from Products.Archetypes.atapi import *
|
||||||
import Products.<!applicationName!>.config
|
import Products.<!applicationName!>.config
|
||||||
from Extensions.appyWrappers import <!genClassName!>_Wrapper
|
from Extensions.appyWrappers import <!genClassName!>_Wrapper
|
||||||
from appy.gen.plone25.mixins.ClassMixin import ClassMixin
|
from appy.gen.plone25.mixins import BaseMixin
|
||||||
<!imports!>
|
<!imports!>
|
||||||
|
|
||||||
schema = Schema((<!fields!>
|
schema = Schema((<!fields!>
|
||||||
|
@ -29,7 +29,7 @@ class <!genClassName!>(<!parents!>):
|
||||||
i18nDomain = '<!applicationName!>'
|
i18nDomain = '<!applicationName!>'
|
||||||
schema = fullSchema
|
schema = fullSchema
|
||||||
wrapperClass = <!genClassName!>_Wrapper
|
wrapperClass = <!genClassName!>_Wrapper
|
||||||
for elem in dir(ClassMixin):
|
for elem in dir(BaseMixin):
|
||||||
if not elem.startswith('__'): security.declarePublic(elem)
|
if not elem.startswith('__'): security.declarePublic(elem)
|
||||||
<!commonMethods!>
|
<!commonMethods!>
|
||||||
<!methods!>
|
<!methods!>
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
<!codeHeader!>
|
|
||||||
from AccessControl import ClassSecurityInfo
|
|
||||||
from DateTime import DateTime
|
|
||||||
from Products.Archetypes.atapi import *
|
|
||||||
import Products.<!applicationName!>.config
|
|
||||||
from appy.gen.plone25.mixins.FlavourMixin import FlavourMixin
|
|
||||||
from Extensions.appyWrappers import <!wrapperClass!>
|
|
||||||
|
|
||||||
schema = Schema((<!fields!>
|
|
||||||
),)
|
|
||||||
fullSchema = OrderedBaseFolderSchema.copy() + schema.copy()
|
|
||||||
|
|
||||||
class <!flavourName!>(OrderedBaseFolder, FlavourMixin):
|
|
||||||
'''Configuration flavour class for <!applicationName!>.'''
|
|
||||||
security = ClassSecurityInfo()
|
|
||||||
__implements__ = (getattr(OrderedBaseFolderSchema,'__implements__',()),)
|
|
||||||
archetype_name = '<!flavourName!>'
|
|
||||||
meta_type = '<!flavourName!>'
|
|
||||||
portal_type = '<!flavourName!>'
|
|
||||||
allowed_content_types = []
|
|
||||||
filter_content_types = 0
|
|
||||||
global_allow = 1
|
|
||||||
immediate_view = 'skyn/view'
|
|
||||||
default_view = 'skyn/view'
|
|
||||||
suppl_views = ()
|
|
||||||
typeDescription = "<!flavourName!>"
|
|
||||||
typeDescMsgId = '<!flavourName!>_edit_descr'
|
|
||||||
i18nDomain = '<!applicationName!>'
|
|
||||||
schema = fullSchema
|
|
||||||
allMetaTypes = <!metaTypes!>
|
|
||||||
wrapperClass = <!wrapperClass!>
|
|
||||||
for elem in dir(FlavourMixin):
|
|
||||||
if not elem.startswith('__'): security.declarePublic(elem)
|
|
||||||
<!commonMethods!>
|
|
||||||
<!methods!>
|
|
||||||
registerType(<!flavourName!>, '<!applicationName!>')
|
|
|
@ -1,34 +0,0 @@
|
||||||
<!codeHeader!>
|
|
||||||
from AccessControl import ClassSecurityInfo
|
|
||||||
from DateTime import DateTime
|
|
||||||
from Products.Archetypes.atapi import *
|
|
||||||
import Products.<!applicationName!>.config
|
|
||||||
from appy.gen.plone25.mixins.PodTemplateMixin import PodTemplateMixin
|
|
||||||
from Extensions.appyWrappers import <!wrapperClass!>
|
|
||||||
|
|
||||||
schema = Schema((<!fields!>
|
|
||||||
),)
|
|
||||||
fullSchema = BaseSchema.copy() + schema.copy()
|
|
||||||
|
|
||||||
class <!applicationName!>PodTemplate(BaseContent, PodTemplateMixin):
|
|
||||||
'''POD template.'''
|
|
||||||
security = ClassSecurityInfo()
|
|
||||||
__implements__ = (getattr(BaseContent,'__implements__',()),)
|
|
||||||
archetype_name = '<!applicationName!>PodTemplate'
|
|
||||||
meta_type = '<!applicationName!>PodTemplate'
|
|
||||||
portal_type = '<!applicationName!>PodTemplate'
|
|
||||||
allowed_content_types = []
|
|
||||||
filter_content_types = 0
|
|
||||||
global_allow = 1
|
|
||||||
immediate_view = 'skyn/view'
|
|
||||||
default_view = 'skyn/view'
|
|
||||||
suppl_views = ()
|
|
||||||
typeDescription = "<!applicationName!>PodTemplate"
|
|
||||||
typeDescMsgId = '<!applicationName!>PodTemplate_edit_descr'
|
|
||||||
wrapperClass = <!wrapperClass!>
|
|
||||||
schema = fullSchema
|
|
||||||
for elem in dir(PodTemplateMixin):
|
|
||||||
if not elem.startswith('__'): security.declarePublic(elem)
|
|
||||||
<!commonMethods!>
|
|
||||||
<!methods!>
|
|
||||||
registerType(<!applicationName!>PodTemplate, '<!applicationName!>')
|
|
|
@ -3,15 +3,13 @@
|
||||||
i18n:domain="<!applicationName!>">
|
i18n:domain="<!applicationName!>">
|
||||||
<body>
|
<body>
|
||||||
<div metal:define-macro="portlet"
|
<div metal:define-macro="portlet"
|
||||||
tal:define="tool python: context.<!toolInstanceName!>;
|
tal:define="tool python: context.<!toolInstanceName!>;"
|
||||||
flavour python: tool.getFlavour(tool);"
|
|
||||||
tal:condition="python: tool.showPortlet(context)">
|
tal:condition="python: tool.showPortlet(context)">
|
||||||
<metal:block metal:use-macro="here/global_defines/macros/defines" />
|
<metal:block metal:use-macro="here/global_defines/macros/defines" />
|
||||||
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
|
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
|
||||||
<dl tal:define="rootClasses tool/getRootClasses;
|
<dl tal:define="rootClasses tool/getRootClasses;
|
||||||
appName string:<!applicationName!>;
|
appName string:<!applicationName!>;
|
||||||
appFolder tool/getAppFolder;
|
appFolder tool/getAppFolder" class="portlet">
|
||||||
flavours tool/getFlavoursInfo" class="portlet">
|
|
||||||
<metal:content use-macro="here/skyn/portlet/macros/portletContent"/>
|
<metal:content use-macro="here/skyn/portlet/macros/portletContent"/>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -36,24 +36,22 @@ label { font-weight: bold; font-style: italic; line-height: 1.4em;}
|
||||||
border-width: thin;
|
border-width: thin;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0.1em 1em 0.1em 1.3em;
|
padding: 0.1em 1em 0.1em 1.3em;
|
||||||
|
background-position: -1px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appyChanges th {
|
.appyChanges th {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-bottom: 1px dashed #8CACBB;
|
border: 0 none transparent;
|
||||||
border-top: 0 none transparent;
|
|
||||||
border-left: 0 none transparent;
|
|
||||||
border-right: 0 none transparent;
|
|
||||||
padding: 0.1em 0.1em 0.1em 0.1em;
|
padding: 0.1em 0.1em 0.1em 0.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appyChanges td {
|
.appyChanges td {
|
||||||
padding: 0.1em 0.1em 0.1em 0.1em !important;
|
padding: 0.1em 0.2em 0.1em 0.2em !important;
|
||||||
|
border-top: 1px dashed #8CACBB !important;
|
||||||
border-right: 0 none transparent !important;
|
border-right: 0 none transparent !important;
|
||||||
border-top: 0 none transparent;
|
border-left: 0 none transparent !important;
|
||||||
border-left: 0 none transparent;
|
border-bottom: 0 none transparent !important;
|
||||||
border-right: 0 none transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.appyHistory {
|
.appyHistory {
|
||||||
|
@ -70,6 +68,12 @@ label { font-weight: bold; font-style: italic; line-height: 1.4em;}
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: -1px 7px;
|
background-position: -1px 7px;
|
||||||
}
|
}
|
||||||
|
.stepDoneState {
|
||||||
|
background-color: #cde2a7;
|
||||||
|
background-image: url(&dtml-portal_url;/skyn/done.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: -1px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.stepCurrent {
|
.stepCurrent {
|
||||||
background-color: #eef3f5;
|
background-color: #eef3f5;
|
||||||
|
@ -77,6 +81,12 @@ label { font-weight: bold; font-style: italic; line-height: 1.4em;}
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: -1px 7px;
|
background-position: -1px 7px;
|
||||||
}
|
}
|
||||||
|
.stepCurrentState {
|
||||||
|
background-color: #eef3f5;
|
||||||
|
background-image: url(&dtml-portal_url;/skyn/current.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: -1px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.stepFuture {
|
.stepFuture {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
|
|
|
@ -29,6 +29,7 @@ class <!toolName!>(UniqueObject, OrderedBaseFolder, ToolMixin):
|
||||||
typeDescription = "<!toolName!>"
|
typeDescription = "<!toolName!>"
|
||||||
typeDescMsgId = '<!toolName!>_edit_descr'
|
typeDescMsgId = '<!toolName!>_edit_descr'
|
||||||
i18nDomain = '<!applicationName!>'
|
i18nDomain = '<!applicationName!>'
|
||||||
|
allMetaTypes = <!metaTypes!>
|
||||||
wrapperClass = <!wrapperClass!>
|
wrapperClass = <!wrapperClass!>
|
||||||
schema = fullSchema
|
schema = fullSchema
|
||||||
schema["id"].widget.visible = False
|
schema["id"].widget.visible = False
|
||||||
|
|
|
@ -2,15 +2,15 @@
|
||||||
from AccessControl import ClassSecurityInfo
|
from AccessControl import ClassSecurityInfo
|
||||||
from Products.Archetypes.atapi import *
|
from Products.Archetypes.atapi import *
|
||||||
import Products.<!applicationName!>.config
|
import Products.<!applicationName!>.config
|
||||||
from appy.gen.plone25.mixins.UserMixin import UserMixin
|
from appy.gen.plone25.mixins import BaseMixin
|
||||||
from Extensions.appyWrappers import <!wrapperClass!>
|
from Extensions.appyWrappers import <!wrapperClass!>
|
||||||
|
|
||||||
schema = Schema((<!fields!>
|
schema = Schema((<!fields!>
|
||||||
),)
|
),)
|
||||||
fullSchema = BaseSchema.copy() + schema.copy()
|
fullSchema = BaseSchema.copy() + schema.copy()
|
||||||
|
|
||||||
class <!applicationName!>User(BaseContent, UserMixin):
|
class <!applicationName!>User(BaseContent, BaseMixin):
|
||||||
'''Configuration flavour class for <!applicationName!>.'''
|
'''User mixin.'''
|
||||||
security = ClassSecurityInfo()
|
security = ClassSecurityInfo()
|
||||||
__implements__ = (getattr(BaseContent,'__implements__',()),)
|
__implements__ = (getattr(BaseContent,'__implements__',()),)
|
||||||
archetype_name = '<!applicationName!>User'
|
archetype_name = '<!applicationName!>User'
|
||||||
|
@ -27,7 +27,7 @@ class <!applicationName!>User(BaseContent, UserMixin):
|
||||||
i18nDomain = '<!applicationName!>'
|
i18nDomain = '<!applicationName!>'
|
||||||
schema = fullSchema
|
schema = fullSchema
|
||||||
wrapperClass = <!wrapperClass!>
|
wrapperClass = <!wrapperClass!>
|
||||||
for elem in dir(UserMixin):
|
for elem in dir(BaseMixin):
|
||||||
if not elem.startswith('__'): security.declarePublic(elem)
|
if not elem.startswith('__'): security.declarePublic(elem)
|
||||||
<!commonMethods!>
|
<!commonMethods!>
|
||||||
<!methods!>
|
<!methods!>
|
||||||
|
|
|
@ -2,23 +2,14 @@
|
||||||
from appy.gen import *
|
from appy.gen import *
|
||||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
from appy.gen.plone25.wrappers import AbstractWrapper
|
||||||
from appy.gen.plone25.wrappers.ToolWrapper import ToolWrapper
|
from appy.gen.plone25.wrappers.ToolWrapper import ToolWrapper
|
||||||
from appy.gen.plone25.wrappers.FlavourWrapper import FlavourWrapper
|
|
||||||
from appy.gen.plone25.wrappers.PodTemplateWrapper import PodTemplateWrapper
|
|
||||||
from appy.gen.plone25.wrappers.UserWrapper import UserWrapper
|
from appy.gen.plone25.wrappers.UserWrapper import UserWrapper
|
||||||
from Globals import InitializeClass
|
from Globals import InitializeClass
|
||||||
from AccessControl import ClassSecurityInfo
|
from AccessControl import ClassSecurityInfo
|
||||||
<!imports!>
|
<!imports!>
|
||||||
|
|
||||||
class PodTemplate(PodTemplateWrapper):
|
|
||||||
'''This class represents a POD template for this application.'''
|
|
||||||
<!podTemplateBody!>
|
|
||||||
class User(UserWrapper):
|
class User(UserWrapper):
|
||||||
'''This class represents a user.'''
|
'''This class represents a user.'''
|
||||||
<!userBody!>
|
<!userBody!>
|
||||||
class Flavour(FlavourWrapper):
|
|
||||||
'''This class represents the Appy class used for defining a flavour.'''
|
|
||||||
folder=True
|
|
||||||
<!flavourBody!>
|
|
||||||
class Tool(ToolWrapper):
|
class Tool(ToolWrapper):
|
||||||
'''This class represents the tool for this application.'''
|
'''This class represents the tool for this application.'''
|
||||||
folder=True
|
folder=True
|
||||||
|
|
|
@ -42,7 +42,7 @@ ADD_CONTENT_PERMISSIONS = {
|
||||||
<!addPermissions!>}
|
<!addPermissions!>}
|
||||||
setDefaultRoles(DEFAULT_ADD_CONTENT_PERMISSION, tuple(defaultAddRoles))
|
setDefaultRoles(DEFAULT_ADD_CONTENT_PERMISSION, tuple(defaultAddRoles))
|
||||||
|
|
||||||
# Applications classes, in various formats and flavours
|
# Applications classes, in various formats
|
||||||
rootClasses = [<!rootClasses!>]
|
rootClasses = [<!rootClasses!>]
|
||||||
appClasses = <!appClasses!>
|
appClasses = <!appClasses!>
|
||||||
appClassNames = [<!appClassNames!>]
|
appClassNames = [<!appClassNames!>]
|
||||||
|
|
|
@ -155,9 +155,8 @@ def do(transitionName, stateChange, logger):
|
||||||
if hasattr(ploneObj, '_v_appy_do') and \
|
if hasattr(ploneObj, '_v_appy_do') and \
|
||||||
not ploneObj._v_appy_do['doNotify']:
|
not ploneObj._v_appy_do['doNotify']:
|
||||||
doNotify = False
|
doNotify = False
|
||||||
elif not ploneObj.getTool().getFlavour(
|
elif not getattr(ploneObj.getTool().appy(), 'enableNotifications'):
|
||||||
ploneObj).getEnableNotifications():
|
# We do not notify if the "notify" flag in the tool is disabled.
|
||||||
# We do not notify if the "notify" flag in the flavour is disabled.
|
|
||||||
doNotify = False
|
doNotify = False
|
||||||
if doAction or doNotify:
|
if doAction or doNotify:
|
||||||
obj = ploneObj.appy()
|
obj = ploneObj.appy()
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
class FlavourWrapper(AbstractWrapper):
|
|
||||||
def onEdit(self, created):
|
|
||||||
if created:
|
|
||||||
nbOfFlavours = len(self.tool.flavours)
|
|
||||||
if nbOfFlavours != 1:
|
|
||||||
self.number = nbOfFlavours
|
|
||||||
self.o.registerPortalTypes()
|
|
||||||
# Call the custom flavour "onEdit" method if it exists
|
|
||||||
if len(self.__class__.__bases__) > 1:
|
|
||||||
# There is a custom flavour
|
|
||||||
if customFlavour.__dict__.has_key('onEdit'):
|
|
||||||
customFlavour.__dict__['onEdit'](self, created)
|
|
||||||
|
|
||||||
def getAttributeName(self, attributeType, klass, attrName=None):
|
|
||||||
'''Some names of Flavour attributes are not easy to guess. For example,
|
|
||||||
the attribute that stores, for a given flavour, the POD templates
|
|
||||||
for class A that is in package x.y is "flavour.podTemplatesForx_y_A".
|
|
||||||
Other example: the attribute that stores the editable default value
|
|
||||||
of field "f1" of class x.y.A is "flavour.defaultValueForx_y_A_f1".
|
|
||||||
This method generates the attribute name based on p_attributeType,
|
|
||||||
a p_klass from the application, and a p_attrName (given only if
|
|
||||||
needed, for example if p_attributeType is "defaultValue").
|
|
||||||
p_attributeType may be:
|
|
||||||
|
|
||||||
"defaultValue"
|
|
||||||
Stores the editable default value for a given p_attrName of a
|
|
||||||
given p_klass.
|
|
||||||
|
|
||||||
"podTemplates"
|
|
||||||
Stores the POD templates that are defined for a given p_klass.
|
|
||||||
|
|
||||||
"podMaxShownTemplates"
|
|
||||||
Stores the maximum number of POD templates shown at once. If the
|
|
||||||
number of available templates is higher, templates are shown in a
|
|
||||||
drop-down list.
|
|
||||||
|
|
||||||
"podTemplate"
|
|
||||||
Stores the pod template for p_attrName.
|
|
||||||
|
|
||||||
"formats"
|
|
||||||
Stores the output format(s) of a given pod template for
|
|
||||||
p_attrName.
|
|
||||||
|
|
||||||
"resultColumns"
|
|
||||||
Stores the list of columns that must be shown when displaying
|
|
||||||
instances of the a given root p_klass.
|
|
||||||
|
|
||||||
"enableAdvancedSearch"
|
|
||||||
Determines if the advanced search screen must be enabled for
|
|
||||||
p_klass.
|
|
||||||
|
|
||||||
"numberOfSearchColumns"
|
|
||||||
Determines in how many columns the search screen for p_klass
|
|
||||||
is rendered.
|
|
||||||
|
|
||||||
"searchFields"
|
|
||||||
Determines, among all indexed fields for p_klass, which one will
|
|
||||||
really be used in the search screen.
|
|
||||||
|
|
||||||
"optionalFields"
|
|
||||||
Stores the list of optional attributes that are in use in the
|
|
||||||
current flavour for the given p_klass.
|
|
||||||
|
|
||||||
"showWorkflow"
|
|
||||||
Stores the boolean field indicating if we must show workflow-
|
|
||||||
related information for p_klass or not.
|
|
||||||
|
|
||||||
"showWorkflowCommentField"
|
|
||||||
Stores the boolean field indicating if we must show the field
|
|
||||||
allowing to enter a comment every time a transition is triggered.
|
|
||||||
|
|
||||||
"showAllStatesInPhase"
|
|
||||||
Stores the boolean field indicating if we must show all states
|
|
||||||
linked to the current phase or not. If this field is False, we
|
|
||||||
simply show the current state, be it linked to the current phase
|
|
||||||
or not.
|
|
||||||
'''
|
|
||||||
fullClassName = self.o.getPortalType(klass)
|
|
||||||
res = '%sFor%s' % (attributeType, fullClassName)
|
|
||||||
if attrName: res += '_%s' % attrName
|
|
||||||
return res
|
|
||||||
# ------------------------------------------------------------------------------
|
|
|
@ -1,6 +0,0 @@
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
from appy.gen.plone25.wrappers import AbstractWrapper
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
class PodTemplateWrapper(AbstractWrapper): pass
|
|
||||||
# ------------------------------------------------------------------------------
|
|
|
@ -44,4 +44,65 @@ class ToolWrapper(AbstractWrapper):
|
||||||
def getDiskFolder(self):
|
def getDiskFolder(self):
|
||||||
'''Returns the disk folder where the Appy application is stored.'''
|
'''Returns the disk folder where the Appy application is stored.'''
|
||||||
return self.o.getProductConfig().diskFolder
|
return self.o.getProductConfig().diskFolder
|
||||||
|
|
||||||
|
def getAttributeName(self, attributeType, klass, attrName=None):
|
||||||
|
'''Some names of Tool attributes are not easy to guess. For example,
|
||||||
|
the attribute that stores the names of the columns to display in
|
||||||
|
query results for class A that is in package x.y is
|
||||||
|
"tool.resultColumnsForx_y_A". Other example: the attribute that
|
||||||
|
stores the editable default value of field "f1" of class x.y.A is
|
||||||
|
"tool.defaultValueForx_y_A_f1". This method generates the attribute
|
||||||
|
name based on p_attributeType, a p_klass from the application, and a
|
||||||
|
p_attrName (given only if needed, for example if p_attributeType is
|
||||||
|
"defaultValue"). p_attributeType may be:
|
||||||
|
|
||||||
|
"defaultValue"
|
||||||
|
Stores the editable default value for a given p_attrName of a
|
||||||
|
given p_klass.
|
||||||
|
|
||||||
|
"podTemplate"
|
||||||
|
Stores the pod template for p_attrName.
|
||||||
|
|
||||||
|
"formats"
|
||||||
|
Stores the output format(s) of a given pod template for
|
||||||
|
p_attrName.
|
||||||
|
|
||||||
|
"resultColumns"
|
||||||
|
Stores the list of columns that must be shown when displaying
|
||||||
|
instances of the a given root p_klass.
|
||||||
|
|
||||||
|
"enableAdvancedSearch"
|
||||||
|
Determines if the advanced search screen must be enabled for
|
||||||
|
p_klass.
|
||||||
|
|
||||||
|
"numberOfSearchColumns"
|
||||||
|
Determines in how many columns the search screen for p_klass
|
||||||
|
is rendered.
|
||||||
|
|
||||||
|
"searchFields"
|
||||||
|
Determines, among all indexed fields for p_klass, which one will
|
||||||
|
really be used in the search screen.
|
||||||
|
|
||||||
|
"optionalFields"
|
||||||
|
Stores the list of optional attributes that are in use in the
|
||||||
|
tool for the given p_klass.
|
||||||
|
|
||||||
|
"showWorkflow"
|
||||||
|
Stores the boolean field indicating if we must show workflow-
|
||||||
|
related information for p_klass or not.
|
||||||
|
|
||||||
|
"showWorkflowCommentField"
|
||||||
|
Stores the boolean field indicating if we must show the field
|
||||||
|
allowing to enter a comment every time a transition is triggered.
|
||||||
|
|
||||||
|
"showAllStatesInPhase"
|
||||||
|
Stores the boolean field indicating if we must show all states
|
||||||
|
linked to the current phase or not. If this field is False, we
|
||||||
|
simply show the current state, be it linked to the current phase
|
||||||
|
or not.
|
||||||
|
'''
|
||||||
|
fullClassName = self.o.getPortalType(klass)
|
||||||
|
res = '%sFor%s' % (attributeType, fullClassName)
|
||||||
|
if attrName: res += '_%s' % attrName
|
||||||
|
return res
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -3,6 +3,18 @@ from appy.gen.plone25.wrappers import AbstractWrapper
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class UserWrapper(AbstractWrapper):
|
class UserWrapper(AbstractWrapper):
|
||||||
|
|
||||||
|
def _callCustom(self, methodName, *args, **kwargs):
|
||||||
|
'''This wrapper implements some methods like "validate" and "onEdit".
|
||||||
|
If the user has defined its own wrapper, its methods will not be
|
||||||
|
called. So this method allows, from the methods here, to call the
|
||||||
|
user versions.'''
|
||||||
|
if len(self.__class__.__bases__) > 1:
|
||||||
|
# There is a custom user class
|
||||||
|
customUser = self.__class__.__bases__[-1]
|
||||||
|
if customUser.__dict__.has_key(methodName):
|
||||||
|
customUser.__dict__[methodName](self, *args, **kwargs)
|
||||||
|
|
||||||
def showLogin(self):
|
def showLogin(self):
|
||||||
'''When must we show the login field?'''
|
'''When must we show the login field?'''
|
||||||
if self.o.isTemporary(): return 'edit'
|
if self.o.isTemporary(): return 'edit'
|
||||||
|
@ -51,6 +63,7 @@ class UserWrapper(AbstractWrapper):
|
||||||
msg = self.translate(u'Passwords do not match.', domain='plone')
|
msg = self.translate(u'Passwords do not match.', domain='plone')
|
||||||
errors.password1 = msg
|
errors.password1 = msg
|
||||||
errors.password2 = msg
|
errors.password2 = msg
|
||||||
|
self._callCustom('validate', new, errors)
|
||||||
|
|
||||||
def onEdit(self, created):
|
def onEdit(self, created):
|
||||||
self.title = self.firstName + ' ' + self.name
|
self.title = self.firstName + ' ' + self.name
|
||||||
|
@ -86,11 +99,5 @@ class UserWrapper(AbstractWrapper):
|
||||||
# Remove the user if it was in the corresponding group
|
# Remove the user if it was in the corresponding group
|
||||||
if groupName in userGroups:
|
if groupName in userGroups:
|
||||||
group.removeMember(self.login)
|
group.removeMember(self.login)
|
||||||
# Call the custom user "onEdit" method if it exists
|
self._callCustom('onEdit', created)
|
||||||
# XXX This code does not work.
|
|
||||||
if len(self.__class__.__bases__) > 1:
|
|
||||||
customUser = self.__class__.__bases__[-1]
|
|
||||||
# There is a custom user class
|
|
||||||
if customUser.__dict__.has_key('onEdit'):
|
|
||||||
customUser.__dict__['onEdit'](self, created)
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -82,8 +82,6 @@ class AbstractWrapper:
|
||||||
else: return 1
|
else: return 1
|
||||||
def get_tool(self): return self.o.getTool().appy()
|
def get_tool(self): return self.o.getTool().appy()
|
||||||
tool = property(get_tool)
|
tool = property(get_tool)
|
||||||
def get_flavour(self): return self.o.getTool().getFlavour(self.o, appy=True)
|
|
||||||
flavour = property(get_flavour)
|
|
||||||
def get_request(self): return self.o.REQUEST
|
def get_request(self): return self.o.REQUEST
|
||||||
request = property(get_request)
|
request = property(get_request)
|
||||||
def get_session(self): return self.o.REQUEST.SESSION
|
def get_session(self): return self.o.REQUEST.SESSION
|
||||||
|
@ -204,9 +202,9 @@ class AbstractWrapper:
|
||||||
ploneObj.reindexObject()
|
ploneObj.reindexObject()
|
||||||
return appyObj
|
return appyObj
|
||||||
|
|
||||||
def translate(self, label, mapping={}, domain=None):
|
def translate(self, label, mapping={}, domain=None, language=None):
|
||||||
'''Check documentation of self.o.translate.'''
|
'''Check documentation of self.o.translate.'''
|
||||||
return self.o.translate(label, mapping, domain)
|
return self.o.translate(label, mapping, domain, language=language)
|
||||||
|
|
||||||
def do(self, transition, comment='', doAction=False, doNotify=False,
|
def do(self, transition, comment='', doAction=False, doNotify=False,
|
||||||
doHistory=True):
|
doHistory=True):
|
||||||
|
@ -276,27 +274,25 @@ class AbstractWrapper:
|
||||||
p_maxResults. If p_noSecurity is specified, you get all objects,
|
p_maxResults. If p_noSecurity is specified, you get all objects,
|
||||||
even if the logged user does not have the permission to view it.'''
|
even if the logged user does not have the permission to view it.'''
|
||||||
# Find the content type corresponding to p_klass
|
# Find the content type corresponding to p_klass
|
||||||
flavour = self.flavour
|
contentType = self.tool.o.getPortalType(klass)
|
||||||
contentType = flavour.o.getPortalType(klass)
|
|
||||||
# Create the Search object
|
# Create the Search object
|
||||||
search = Search('customSearch', sortBy=sortBy, **fields)
|
search = Search('customSearch', sortBy=sortBy, **fields)
|
||||||
if not maxResults:
|
if not maxResults:
|
||||||
maxResults = 'NO_LIMIT'
|
maxResults = 'NO_LIMIT'
|
||||||
# If I let maxResults=None, only a subset of the results will be
|
# If I let maxResults=None, only a subset of the results will be
|
||||||
# returned by method executeResult.
|
# returned by method executeResult.
|
||||||
res = self.tool.o.executeQuery(contentType,flavour.number,search=search,
|
res = self.tool.o.executeQuery(contentType, search=search,
|
||||||
maxResults=maxResults, noSecurity=noSecurity)
|
maxResults=maxResults, noSecurity=noSecurity)
|
||||||
return [o.appy() for o in res['objects']]
|
return [o.appy() for o in res['objects']]
|
||||||
|
|
||||||
def count(self, klass, noSecurity=False, **fields):
|
def count(self, klass, noSecurity=False, **fields):
|
||||||
'''Identical to m_search above, but returns the number of objects that
|
'''Identical to m_search above, but returns the number of objects that
|
||||||
match the search instead of returning the objects themselves. Use
|
match the search instead of returning the objects themselves. Use
|
||||||
this method instead of writing len(self.search(...)).'''
|
this method instead of writing len(self.search(...)).'''
|
||||||
flavour = self.flavour
|
contentType = self.tool.o.getPortalType(klass)
|
||||||
contentType = flavour.o.getPortalType(klass)
|
|
||||||
search = Search('customSearch', **fields)
|
search = Search('customSearch', **fields)
|
||||||
res = self.tool.o.executeQuery(contentType,flavour.number,search=search,
|
res = self.tool.o.executeQuery(contentType, search=search,
|
||||||
brainsOnly=True, noSecurity=noSecurity)
|
brainsOnly=True, noSecurity=noSecurity)
|
||||||
if res: return res._len # It is a LazyMap instance
|
if res: return res._len # It is a LazyMap instance
|
||||||
else: return 0
|
else: return 0
|
||||||
|
|
||||||
|
@ -325,14 +321,12 @@ class AbstractWrapper:
|
||||||
|
|
||||||
"for obj in self.search(MyClass,...)"
|
"for obj in self.search(MyClass,...)"
|
||||||
'''
|
'''
|
||||||
flavour = self.flavour
|
contentType = self.tool.o.getPortalType(klass)
|
||||||
contentType = flavour.o.getPortalType(klass)
|
|
||||||
search = Search('customSearch', sortBy=sortBy, **fields)
|
search = Search('customSearch', sortBy=sortBy, **fields)
|
||||||
# Initialize the context variable "ctx"
|
# Initialize the context variable "ctx"
|
||||||
ctx = context
|
ctx = context
|
||||||
for brain in self.tool.o.executeQuery(contentType, flavour.number, \
|
for brain in self.tool.o.executeQuery(contentType, search=search, \
|
||||||
search=search, brainsOnly=True, maxResults=maxResults,
|
brainsOnly=True, maxResults=maxResults, noSecurity=noSecurity):
|
||||||
noSecurity=noSecurity):
|
|
||||||
# Get the Appy object from the brain
|
# Get the Appy object from the brain
|
||||||
obj = brain.getObject().appy()
|
obj = brain.getObject().appy()
|
||||||
exec expression
|
exec expression
|
||||||
|
@ -379,5 +373,5 @@ class AbstractWrapper:
|
||||||
|
|
||||||
p_data must be a dictionary whose keys are field names (strings) and
|
p_data must be a dictionary whose keys are field names (strings) and
|
||||||
whose values are the previous field values.'''
|
whose values are the previous field values.'''
|
||||||
self.o.addDataChange(data, labels=False)
|
self.o.addDataChange(data)
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
28
gen/po.py
28
gen/po.py
|
@ -26,20 +26,22 @@ fallbacks = {'en': 'en-us en-ca',
|
||||||
class PoMessage:
|
class PoMessage:
|
||||||
'''Represents a i18n message (po format).'''
|
'''Represents a i18n message (po format).'''
|
||||||
CONFIG = "Configuration panel for product '%s'"
|
CONFIG = "Configuration panel for product '%s'"
|
||||||
FLAVOUR = "Configuration flavour"
|
# The following messages (starting with MSG_) correspond to tool
|
||||||
# The following messages (starting with MSG_) correspond to flavour
|
|
||||||
# attributes added for every gen-class (warning: the message IDs correspond
|
# attributes added for every gen-class (warning: the message IDs correspond
|
||||||
# to MSG_<attributePrexix>).
|
# to MSG_<attributePrefix>).
|
||||||
MSG_optionalFieldsFor = 'Optional fields'
|
MSG_defaultValue = "Default value for field '%s'"
|
||||||
MSG_defaultValueFor = "Default value for field '%s'"
|
MSG_podTemplate = "POD template for field '%s'"
|
||||||
MSG_podTemplatesFor = "POD templates"
|
MSG_formats = "Output format(s) for field '%s'"
|
||||||
MSG_podMaxShownTemplatesFor = "Max shown POD templates"
|
MSG_resultColumns = "Columns to display while showing query results"
|
||||||
MSG_resultColumnsFor = "Columns to display while showing query results"
|
MSG_enableAdvancedSearch = "Enable advanced search"
|
||||||
MSG_showWorkflowFor = 'Show workflow-related information'
|
MSG_numberOfSearchColumns = "Number of search columns"
|
||||||
MSG_showWorkflowCommentFieldFor = 'Show field allowing to enter a ' \
|
MSG_searchFields = "Search fields"
|
||||||
'comment every time a transition is triggered'
|
MSG_optionalFields = 'Optional fields'
|
||||||
MSG_showAllStatesInPhaseFor = 'Show all states in phase'
|
MSG_showWorkflow = 'Show workflow-related information'
|
||||||
POD_TEMPLATE = 'POD template'
|
MSG_showWorkflowCommentField = 'Show field allowing to enter a ' \
|
||||||
|
'comment every time a transition is ' \
|
||||||
|
'triggered'
|
||||||
|
MSG_showAllStatesInPhase = 'Show all states in phase'
|
||||||
USER = 'User'
|
USER = 'User'
|
||||||
POD_ASKACTION = 'Trigger related action'
|
POD_ASKACTION = 'Trigger related action'
|
||||||
DEFAULT_VALID_ERROR = 'Please fill or correct this.'
|
DEFAULT_VALID_ERROR = 'Please fill or correct this.'
|
||||||
|
|
|
@ -32,6 +32,3 @@ class StandardRadio(Radio):
|
||||||
|
|
||||||
c = Config()
|
c = Config()
|
||||||
c.languages = ('en', 'fr')
|
c.languages = ('en', 'fr')
|
||||||
|
|
||||||
class CarFlavour(Flavour):
|
|
||||||
explanation = String(group="userInterface")
|
|
||||||
|
|
|
@ -9,16 +9,6 @@ class ZopeComponentTool(Tool):
|
||||||
self.someUsefulConfigurationOption = 'My app is configured now!'
|
self.someUsefulConfigurationOption = 'My app is configured now!'
|
||||||
install = Action(action=onInstall)
|
install = Action(action=onInstall)
|
||||||
|
|
||||||
class ZopeComponentFlavour(Flavour):
|
|
||||||
anIntegerOption = Integer()
|
|
||||||
bunchesOfGeeks = Ref(BunchOfGeek, multiplicity=(0,None), add=True,
|
|
||||||
link=False, back=Ref(attribute='backToTool'),
|
|
||||||
shownInfo=('description',), page='data')
|
|
||||||
def onEdit(self, created):
|
|
||||||
if 'Escadron de la mort' not in [b.title for b in self.bunchesOfGeeks]:
|
|
||||||
self.create('bunchesOfGeeks', title='Escadron de la mort',
|
|
||||||
description='I want those guys everywhere!')
|
|
||||||
|
|
||||||
class ZopeComponentWorkflow:
|
class ZopeComponentWorkflow:
|
||||||
# Specific permissions
|
# Specific permissions
|
||||||
wf = WritePermission('ZopeComponent.funeralDate')
|
wf = WritePermission('ZopeComponent.funeralDate')
|
||||||
|
|
107
shared/dav.py
Normal file
107
shared/dav.py
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
import os, re, httplib, sys
|
||||||
|
from StringIO import StringIO
|
||||||
|
from mimetypes import guess_type
|
||||||
|
from base64 import encodestring
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
urlRex = re.compile(r'http://([^:/]+)(:[0-9]+)?(/.+)?', re.I)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class Resource:
|
||||||
|
'''Every instance of this class represents some web resource accessible
|
||||||
|
through WebDAV.'''
|
||||||
|
|
||||||
|
def __init__(self, url, username=None, password=None):
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.url = url
|
||||||
|
|
||||||
|
# Split the URL into its components
|
||||||
|
res = urlRex.match(url)
|
||||||
|
if res:
|
||||||
|
host, port, uri = res.group(1,2,3)
|
||||||
|
self.host = host
|
||||||
|
self.port = port and int(port[1:]) or 80
|
||||||
|
self.uri = uri or '/'
|
||||||
|
else: raise 'Wrong URL: %s' % str(url)
|
||||||
|
|
||||||
|
def updateHeaders(self, headers):
|
||||||
|
# Add credentials if present
|
||||||
|
if not (self.username and self.password): return
|
||||||
|
if headers.has_key('Authorization'): return
|
||||||
|
credentials = '%s:%s' % (self.username,self.password)
|
||||||
|
credentials = credentials.replace('\012','')
|
||||||
|
headers['Authorization'] = "Basic %s" % encodestring(credentials)
|
||||||
|
headers['User-Agent'] = 'WebDAV.client'
|
||||||
|
headers['Host'] = self.host
|
||||||
|
headers['Connection'] = 'close'
|
||||||
|
headers['Accept'] = '*/*'
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def sendRequest(self, method, uri, body=None, headers={}):
|
||||||
|
'''Sends a HTTP request with p_method, for p_uri.'''
|
||||||
|
conn = httplib.HTTP()
|
||||||
|
conn.connect(self.host, self.port)
|
||||||
|
conn.putrequest(method, uri)
|
||||||
|
# Add HTTP headers
|
||||||
|
self.updateHeaders(headers)
|
||||||
|
for n, v in headers.items(): conn.putheader(n, v)
|
||||||
|
conn.endheaders()
|
||||||
|
if body: conn.send(body)
|
||||||
|
ver, code, msg = conn.getreply()
|
||||||
|
data = conn.getfile().read()
|
||||||
|
conn.close()
|
||||||
|
return data
|
||||||
|
|
||||||
|
def mkdir(self, name):
|
||||||
|
'''Creates a folder named p_name in this resource.'''
|
||||||
|
folderUri = self.uri + '/' + name
|
||||||
|
#body = '<d:propertyupdate xmlns:d="DAV:"><d:set><d:prop>' \
|
||||||
|
# '<d:displayname>%s</d:displayname></d:prop></d:set>' \
|
||||||
|
# '</d:propertyupdate>' % name
|
||||||
|
return self.sendRequest('MKCOL', folderUri)
|
||||||
|
|
||||||
|
def delete(self, name):
|
||||||
|
'''Deletes a file or a folder (and all contained files if any) named
|
||||||
|
p_name within this resource.'''
|
||||||
|
toDeleteUri = self.uri + '/' + name
|
||||||
|
return self.sendRequest('DELETE', toDeleteUri)
|
||||||
|
|
||||||
|
def add(self, content, type='fileName', name=''):
|
||||||
|
'''Adds a file in this resource. p_type can be:
|
||||||
|
- "fileName" In this case, p_content is the path to a file on disk
|
||||||
|
and p_name is ignored;
|
||||||
|
- "zope" In this case, p_content is an instance of
|
||||||
|
OFS.Image.File and the name of the file is given in
|
||||||
|
p_name.
|
||||||
|
'''
|
||||||
|
if type == 'fileName':
|
||||||
|
# p_content is the name of a file on disk
|
||||||
|
f = file(content, 'rb')
|
||||||
|
body = f.read()
|
||||||
|
f.close()
|
||||||
|
fileName = os.path.basename(content)
|
||||||
|
fileType, encoding = guess_type(fileName)
|
||||||
|
elif type == 'zope':
|
||||||
|
# p_content is a "Zope" file, ie a OFS.Image.File instance
|
||||||
|
fileName = name
|
||||||
|
fileType = content.content_type
|
||||||
|
encoding = None
|
||||||
|
if isinstance(content.data, basestring):
|
||||||
|
# The file content is here, in one piece
|
||||||
|
body = content.data
|
||||||
|
else:
|
||||||
|
# There are several parts to this file.
|
||||||
|
body = ''
|
||||||
|
data = content.data
|
||||||
|
while data is not None:
|
||||||
|
body += data.data
|
||||||
|
data = data.next
|
||||||
|
fileUri = self.uri + '/' + fileName
|
||||||
|
headers = {}
|
||||||
|
if fileType: headers['Content-Type'] = fileType
|
||||||
|
if encoding: headers['Content-Encoding'] = encoding
|
||||||
|
headers['Content-Length'] = str(len(body))
|
||||||
|
return self.sendRequest('PUT', fileUri, body, headers)
|
||||||
|
# ------------------------------------------------------------------------------
|
149
shared/utils.py
149
shared/utils.py
|
@ -125,4 +125,153 @@ def normalizeString(s, usage='fileName'):
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
typeLetters = {'b': bool, 'i': int, 'j': long, 'f':float, 's':str, 'u':unicode,
|
typeLetters = {'b': bool, 'i': int, 'j': long, 'f':float, 's':str, 'u':unicode,
|
||||||
'l': list, 'd': dict}
|
'l': list, 'd': dict}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class CodeAnalysis:
|
||||||
|
'''This class holds information about some code analysis (line counts) that
|
||||||
|
spans some folder hierarchy.'''
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name # Let's give a name for the analysis
|
||||||
|
self.numberOfFiles = 0 # The total number of analysed files
|
||||||
|
self.emptyLines = 0 # The number of empty lines within those files
|
||||||
|
self.commentLines = 0 # The number of comment lines
|
||||||
|
# A code line is defined as anything that is not an empty or comment
|
||||||
|
# line.
|
||||||
|
self.codeLines = 0
|
||||||
|
|
||||||
|
def numberOfLines(self):
|
||||||
|
'''Computes the total number of lines within analysed files.'''
|
||||||
|
return self.emptyLines + self.commentLines + self.codeLines
|
||||||
|
|
||||||
|
def analyseZptFile(self, theFile):
|
||||||
|
'''Analyses the ZPT file named p_fileName.'''
|
||||||
|
inDoc = False
|
||||||
|
for line in theFile:
|
||||||
|
stripped = line.strip()
|
||||||
|
# Manage a comment
|
||||||
|
if not inDoc and (line.find('<tal:comment ') != -1):
|
||||||
|
inDoc = True
|
||||||
|
if inDoc:
|
||||||
|
self.commentLines += 1
|
||||||
|
if line.find('</tal:comment>') != -1:
|
||||||
|
inDoc = False
|
||||||
|
continue
|
||||||
|
# Manage an empty line
|
||||||
|
if not stripped:
|
||||||
|
self.emptyLines += 1
|
||||||
|
else:
|
||||||
|
self.codeLines += 1
|
||||||
|
|
||||||
|
docSeps = ('"""', "'''")
|
||||||
|
def isPythonDoc(self, line, start, isStart=False):
|
||||||
|
'''Returns True if we find, in p_line, the start of a docstring (if
|
||||||
|
p_start is True) or the end of a docstring (if p_start is False).
|
||||||
|
p_isStart indicates if p_line is the start of the docstring.'''
|
||||||
|
if start:
|
||||||
|
res = line.startswith(self.docSeps[0]) or \
|
||||||
|
line.startswith(self.docSeps[1])
|
||||||
|
else:
|
||||||
|
sepOnly = (line == self.docSeps[0]) or (line == self.docSeps[1])
|
||||||
|
if sepOnly:
|
||||||
|
# If the line contains the separator only, is this the start or
|
||||||
|
# the end of the docstring?
|
||||||
|
if isStart: res = False
|
||||||
|
else: res = True
|
||||||
|
else:
|
||||||
|
res = line.endswith(self.docSeps[0]) or \
|
||||||
|
line.endswith(self.docSeps[1])
|
||||||
|
return res
|
||||||
|
|
||||||
|
def analysePythonFile(self, theFile):
|
||||||
|
'''Analyses the Python file named p_fileName.'''
|
||||||
|
# Are we in a docstring ?
|
||||||
|
inDoc = False
|
||||||
|
for line in theFile:
|
||||||
|
stripped = line.strip()
|
||||||
|
# Manage a line that is within a docstring
|
||||||
|
inDocStart = False
|
||||||
|
if not inDoc and self.isPythonDoc(stripped, start=True):
|
||||||
|
inDoc = True
|
||||||
|
inDocStart = True
|
||||||
|
if inDoc:
|
||||||
|
self.commentLines += 1
|
||||||
|
if self.isPythonDoc(stripped, start=False, isStart=inDocStart):
|
||||||
|
inDoc = False
|
||||||
|
continue
|
||||||
|
# Manage an empty line
|
||||||
|
if not stripped:
|
||||||
|
self.emptyLines += 1
|
||||||
|
continue
|
||||||
|
# Manage a comment line
|
||||||
|
if line.startswith('#'):
|
||||||
|
self.commentLines += 1
|
||||||
|
continue
|
||||||
|
# If we are here, we have a code line.
|
||||||
|
self.codeLines += 1
|
||||||
|
|
||||||
|
def analyseFile(self, fileName):
|
||||||
|
'''Analyses file named p_fileName.'''
|
||||||
|
self.numberOfFiles += 1
|
||||||
|
theFile = file(fileName)
|
||||||
|
if fileName.endswith('.py'):
|
||||||
|
self.analysePythonFile(theFile)
|
||||||
|
elif fileName.endswith('.pt'):
|
||||||
|
self.analyseZptFile(theFile)
|
||||||
|
theFile.close()
|
||||||
|
|
||||||
|
def printReport(self):
|
||||||
|
'''Returns the analysis report as a string, only if there is at least
|
||||||
|
one analysed line.'''
|
||||||
|
lines = self.numberOfLines()
|
||||||
|
if not lines: return
|
||||||
|
commentRate = (self.commentLines / float(lines)) * 100.0
|
||||||
|
blankRate = (self.emptyLines / float(lines)) * 100.0
|
||||||
|
print '%s: %d files, %d lines (%.0f%% comments, %.0f%% blank)' % \
|
||||||
|
(self.name, self.numberOfFiles, lines, commentRate, blankRate)
|
||||||
|
|
||||||
|
class LinesCounter:
|
||||||
|
'''Counts and classifies the lines of code within a folder hierarchy.'''
|
||||||
|
def __init__(self, folderOrModule):
|
||||||
|
if isinstance(folderOrModule, basestring):
|
||||||
|
# It is the path of some folder
|
||||||
|
self.folder = folderOrModule
|
||||||
|
else:
|
||||||
|
# It is a Python module
|
||||||
|
self.folder = os.path.dirname(folderOrModule.__file__)
|
||||||
|
# These dicts will hold information about analysed files
|
||||||
|
self.python = {False: CodeAnalysis('Python'),
|
||||||
|
True: CodeAnalysis('Python (test)')}
|
||||||
|
self.zpt = {False: CodeAnalysis('ZPT'),
|
||||||
|
True: CodeAnalysis('ZPT (test)')}
|
||||||
|
# Are we currently analysing real or test code?
|
||||||
|
self.inTest = False
|
||||||
|
|
||||||
|
def printReport(self):
|
||||||
|
'''Displays on stdout a small analysis report about self.folder.'''
|
||||||
|
for zone in (False, True): self.python[zone].printReport()
|
||||||
|
for zone in (False, True): self.zpt[zone].printReport()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
'''Let's start the analysis of self.folder.'''
|
||||||
|
# The test markers will allow us to know if we are analysing test code
|
||||||
|
# or real code within a given part of self.folder code hierarchy.
|
||||||
|
testMarker1 = '%stest%s' % (os.sep, os.sep)
|
||||||
|
testMarker2 = '%stest' % os.sep
|
||||||
|
j = os.path.join
|
||||||
|
for root, folders, files in os.walk(self.folder):
|
||||||
|
rootName = os.path.basename(root)
|
||||||
|
if rootName.startswith('.') or \
|
||||||
|
(rootName in ('tmp', 'temp')):
|
||||||
|
continue
|
||||||
|
# Are we in real code or in test code ?
|
||||||
|
self.inTest = False
|
||||||
|
if root.endswith(testMarker2) or (root.find(testMarker1) != -1):
|
||||||
|
self.inTest = True
|
||||||
|
# Scan the files in this folder
|
||||||
|
for fileName in files:
|
||||||
|
if fileName.endswith('.py'):
|
||||||
|
self.python[self.inTest].analyseFile(j(root, fileName))
|
||||||
|
elif fileName.endswith('.pt'):
|
||||||
|
self.zpt[self.inTest].analyseFile(j(root, fileName))
|
||||||
|
self.printReport()
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
Loading…
Reference in a new issue