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:
Gaetan Delannay 2010-10-14 14:43:56 +02:00
parent 9f4db88bdf
commit 990e16c6e7
47 changed files with 1006 additions and 1297 deletions

View file

@ -5,7 +5,7 @@ from appy.gen.layout import Table
from appy.gen.layout import defaultFieldLayouts
from appy.gen.po import PoMessage
from appy.gen.utils import sequenceTypes, PageDescr, GroupDescr, Keywords, \
FileWrapper, getClassName
FileWrapper, getClassName, SomeObjects
from appy.shared.data import languages
# Default Appy permissions -----------------------------------------------------
@ -29,10 +29,10 @@ class Page:
class Group:
'''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,
hasHeaders=False, group=None, colspan=1, align='center',
valign='top'):
valign='top', css_class='', master=None, masterValue=None):
self.name = name
# 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
@ -77,6 +77,26 @@ class Group:
self.columns = self.columns[:1]
# Header labels will be used as labels for the tabs.
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):
'''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
field?'''
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:
tool = obj.getTool()
flavour = tool.getFlavour(obj, appy=True)
flavourAttrName = 'optionalFieldsFor%s' % obj.meta_type
flavourAttrValue = getattr(flavour, flavourAttrName, ())
if self.name not in flavourAttrValue:
tool = obj.getTool().appy()
fieldName = 'optionalFieldsFor%s' % obj.meta_type
fieldValue = getattr(tool, fieldName, ())
if self.name not in fieldValue:
return False
# Check if the user has the permission to view or edit the field
user = obj.portal_membership.getAuthenticatedMember()
@ -568,11 +587,10 @@ class Type:
return self.default(obj.appy())
else:
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)
tool = obj.getTool()
flavour = tool.getFlavour(portalTypeName, appy=True)
return getattr(flavour, 'defaultValueFor%s' % self.labelId)
tool = obj.getTool().appy()
return getattr(tool, 'defaultValueFor%s' % self.labelId)
return value
def getFormattedValue(self, obj, value):
@ -1188,17 +1206,24 @@ class File(Type):
class Ref(Type):
def __init__(self, klass=None, attribute=None, validator=None,
multiplicity=(0,1), index=None, default=None, optional=False,
editDefault=False, add=False, link=True, unlink=False,
back=None, show=True, page='main', group=None, layouts=None,
showHeaders=False, shownInfo=(), select=None, maxPerPage=30,
move=0, indexed=False, searchable=False,
specificReadPermission=False, specificWritePermission=False,
width=None, height=None, colspan=1, master=None,
masterValue=None, focus=False, historized=False):
editDefault=False, add=False, addConfirm=False, noForm=False,
link=True, unlink=False, back=None, show=True, page='main',
group=None, layouts=None, showHeaders=False, shownInfo=(),
select=None, maxPerPage=30, move=0, indexed=False,
searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=None,
colspan=1, master=None, masterValue=None, focus=False,
historized=False):
self.klass = klass
self.attribute = attribute
# May the user add new objects through this ref ?
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?
self.link = link
# May the user unlink existing objects?
@ -1246,12 +1271,70 @@ class Ref(Type):
return obj.getBRefs(self.relationship)
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:
return obj._appy_getRefsBack(self.name, self.relationship,
noListIfSingleObj=True)
getRefs = obj.reference_catalog.getBackReferences
uids = [r.sourceUID for r in getRefs(obj, self.relationship)]
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):
return value
@ -1281,6 +1364,24 @@ class Ref(Type):
elif nbOfRefs > maxRef:
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):
def __init__(self, validator=None, multiplicity=(0,1), index=None,
default=None, optional=False, editDefault=False, show='view',
@ -1645,10 +1746,6 @@ class Model: pass
class Tool(Model):
'''If you want so define a custom tool class, she must inherit from this
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):
'''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
# application, set the following parameter to False.
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