[gen] Added for Ref field, param 'insert' that defines at what position a newly tied object is inserted among referred objects.

This commit is contained in:
Gaetan Delannay 2014-06-24 17:07:59 +02:00
parent 477db8a21c
commit 98b748cfb1
3 changed files with 75 additions and 41 deletions

View file

@ -471,8 +471,8 @@ class Ref(Field):
def __init__(self, klass=None, attribute=None, validator=None, def __init__(self, klass=None, attribute=None, validator=None,
multiplicity=(0,1), default=None, add=False, addConfirm=False, multiplicity=(0,1), default=None, add=False, addConfirm=False,
delete=None, noForm=False, link=True, unlink=None, back=None, delete=None, noForm=False, link=True, unlink=None, insert=None,
show=True, page='main', group=None, layouts=None, back=None, show=True, page='main', group=None, layouts=None,
showHeaders=False, shownInfo=(), select=None, maxPerPage=30, showHeaders=False, shownInfo=(), select=None, maxPerPage=30,
move=0, indexed=False, searchable=False, move=0, indexed=False, searchable=False,
specificReadPermission=False, specificWritePermission=False, specificReadPermission=False, specificWritePermission=False,
@ -486,7 +486,8 @@ class Ref(Field):
menuInfoMethod=None, menuUrlMethod=None): menuInfoMethod=None, menuUrlMethod=None):
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 ? "add" may also contain
# a method whose result must be a boolean value.
self.add = add self.add = add
# When the user adds a new object, must a confirmation popup be shown? # When the user adds a new object, must a confirmation popup be shown?
self.addConfirm = addConfirm self.addConfirm = addConfirm
@ -510,6 +511,24 @@ class Ref(Field):
self.link = link self.link = link
# May the user unlink existing objects? # May the user unlink existing objects?
self.unlink = unlink self.unlink = unlink
# When an object is inserted through this Ref field, at what position is
# it inserted? If "insert" is:
# None, it will be inserted at the end;
# "start", it will be inserted at the start of the tied objects;
# a method, (called with the object to insert as single arg), its return
# value (a number or a tuple of numbers) will be
# used to insert the object at the corresponding position
# (this method will also be applied to other objects to know
# where to insert the new one);
# a tuple, ('sort', method), the given method (called with the object
# to insert as single arg) will be used to sort tied objects
# and will be given as param "key" of the standard Python
# method "sort" applied on the list of tied objects.
# With value ('sort', method), a full sort is performed and may hardly
# reshake the tied objects; with value "method" alone, the tied
# object is inserted at some given place: tied objects are more
# maintained in the order of their insertion.
self.insert = insert
if unlink == None: if unlink == None:
# By default, one may unlink objects via a Ref for which one can # By default, one may unlink objects via a Ref for which one can
# link objects. # link objects.
@ -863,47 +882,64 @@ class Ref(Field):
def linkObject(self, obj, value, back=False, noSecurity=True): def linkObject(self, obj, value, back=False, noSecurity=True):
'''This method links p_value (which can be a list of objects) to p_obj '''This method links p_value (which can be a list of objects) to p_obj
through this Ref field.''' through this Ref field.'''
zobj = obj.o
# Security check # Security check
if not noSecurity: obj.mayEdit(self.writePermission, raiseError=True) if not noSecurity: zobj.mayEdit(self.writePermission, raiseError=True)
# p_value can be a list of objects # p_value can be a list of objects
if type(value) in sutils.sequenceTypes: if type(value) in sutils.sequenceTypes:
for v in value: self.linkObject(obj, v, back=back) for v in value: self.linkObject(obj, v, back=back)
return return
# Gets the list of referred objects (=list of uids), or create it. # Gets the list of referred objects (=list of uids), or create it.
obj = obj.o refs = getattr(zobj.aq_base, self.name, None)
refs = getattr(obj.aq_base, self.name, None)
if refs == None: if refs == None:
refs = obj.getProductConfig().PersistentList() refs = zobj.getProductConfig().PersistentList()
setattr(obj, self.name, refs) setattr(zobj, self.name, refs)
# Insert p_value into it. # Insert p_value into it.
uid = value.o.id uid = value.o.id
if uid not in refs: if uid in refs: return
# Where must we insert the object? At the start? At the end? # Where must we insert the object?
if callable(self.add): if not self.insert:
add = self.callMethod(obj, self.add)
else:
add = self.add
if add == 'start':
refs.insert(0, uid)
else:
refs.append(uid) refs.append(uid)
elif self.insert == 'start':
refs.insert(0, uid)
elif callable(self.insert):
# It is a method. Use it on every tied object until we find where to
# insert the new object.
tool = zobj.getTool()
insertOrder = self.insert(obj, value)
i = 0
inserted = False
while i < len(refs):
tied = tool.getObject(refs[i], appy=True)
if self.insert(obj, tied) > insertOrder:
refs.insert(i, uid)
inserted = True
break
i += 1
if not inserted: refs.append(uid)
else:
# It is a tuple ('sort', method). Perform a full sort.
refs.append(uid)
tool = zobj.getTool()
refs.sort(key=lambda uid:self.insert[1](obj, \
tool.getObject(uid, appy=True)))
# Update the back reference # Update the back reference
if not back: self.back.linkObject(value, obj, back=True) if not back: self.back.linkObject(value, obj, back=True)
def unlinkObject(self, obj, value, back=False, noSecurity=True): def unlinkObject(self, obj, value, back=False, noSecurity=True):
'''This method unlinks p_value (which can be a list of objects) from '''This method unlinks p_value (which can be a list of objects) from
p_obj through this Ref field.''' p_obj through this Ref field.'''
zobj = obj.o
# Security check # Security check
if not noSecurity: obj.mayEdit(self.writePermission, raiseError=True) if not noSecurity: zobj.mayEdit(self.writePermission, raiseError=True)
# p_value can be a list of objects # p_value can be a list of objects
if type(value) in sutils.sequenceTypes: if type(value) in sutils.sequenceTypes:
for v in value: self.unlinkObject(obj, v, back=back) for v in value: self.unlinkObject(obj, v, back=back)
return return
obj = obj.o refs = getattr(zobj.aq_base, self.name, None)
refs = getattr(obj.aq_base, self.name, None)
if not refs: return if not refs: return
# Unlink p_value # Unlink p_value
uid = value.o.UID() uid = value.o.id
if uid in refs: if uid in refs:
refs.remove(uid) refs.remove(uid)
# Update the back reference # Update the back reference
@ -919,19 +955,20 @@ class Ref(Field):
* a Appy object; * a Appy object;
* a list of Appy or Zope objects.''' * a list of Appy or Zope objects.'''
if not self.persist: return if not self.persist: return
# Standardize p_value into a list of Zope objects # Standardize p_value into a list of Appy objects
objects = value objects = value
if not objects: objects = [] if not objects: objects = []
if type(objects) not in sutils.sequenceTypes: objects = [objects] if type(objects) not in sutils.sequenceTypes: objects = [objects]
tool = obj.getTool() tool = obj.getTool()
for i in range(len(objects)): for i in range(len(objects)):
if isinstance(objects[i], basestring): if isinstance(objects[i], basestring):
# We have a UID here # We have an UID here
objects[i] = tool.getObject(objects[i]) objects[i] = tool.getObject(objects[i], appy=True)
else: else:
# Be sure to have a Zope object # Be sure to have an Appy object
objects[i] = objects[i].o objects[i] = objects[i].appy()
uids = [o.UID() for o in objects] uids = [o.o.id for o in objects]
appyObj = obj.appy()
# Unlink objects that are not referred anymore # Unlink objects that are not referred anymore
refs = getattr(obj.aq_base, self.name, None) refs = getattr(obj.aq_base, self.name, None)
if refs: if refs:
@ -939,11 +976,11 @@ class Ref(Field):
while i >= 0: while i >= 0:
if refs[i] not in uids: if refs[i] not in uids:
# Object having this UID must unlink p_obj # Object having this UID must unlink p_obj
self.back.unlinkObject(tool.getObject(refs[i]), obj) tied = tool.getObject(refs[i], appy=True)
self.back.unlinkObject(tied, appyObj)
i -= 1 i -= 1
# Link new objects # Link new objects
if objects: if objects: self.linkObject(appyObj, objects)
self.linkObject(obj, objects)
def mayAdd(self, obj, checkMayEdit=True): def mayAdd(self, obj, checkMayEdit=True):
'''May the user create a new referred object from p_obj via this Ref? '''May the user create a new referred object from p_obj via this Ref?
@ -954,10 +991,7 @@ class Ref(Field):
# We can't (yet) do that on back references. # We can't (yet) do that on back references.
if self.isBack: return gutils.No('is_back') if self.isBack: return gutils.No('is_back')
# Check if this Ref is addable # Check if this Ref is addable
if callable(self.add): add = self.getAttribute(obj, 'add')
add = self.callMethod(obj, self.add)
else:
add = self.add
if not add: return gutils.No('no_add') if not add: return gutils.No('no_add')
# Have we reached the maximum number of referred elements? # Have we reached the maximum number of referred elements?
if self.multiplicity[1] != None: if self.multiplicity[1] != None:

View file

@ -135,7 +135,7 @@ class BaseMixin:
for field in self.getAllAppyTypes(): for field in self.getAllAppyTypes():
if field.type != 'Ref': continue if field.type != 'Ref': continue
for obj in field.getValue(self): for obj in field.getValue(self):
field.back.unlinkObject(obj, self, back=True) field.back.unlinkObject(obj, appyObj, back=True)
# Uncatalog the object # Uncatalog the object
self.reindex(unindex=True) self.reindex(unindex=True)
# Delete the filesystem folder corresponding to this object # Delete the filesystem folder corresponding to this object

View file

@ -788,12 +788,12 @@ class AbstractWrapper(object):
def link(self, fieldName, obj): def link(self, fieldName, obj):
'''This method links p_obj (which can be a list of objects) to this one '''This method links p_obj (which can be a list of objects) to this one
through reference field p_fieldName.''' through reference field p_fieldName.'''
return self.getField(fieldName).linkObject(self.o, obj) return self.getField(fieldName).linkObject(self, obj)
def unlink(self, fieldName, obj): def unlink(self, fieldName, obj):
'''This method unlinks p_obj (which can be a list of objects) from this '''This method unlinks p_obj (which can be a list of objects) from this
one through reference field p_fieldName.''' one through reference field p_fieldName.'''
return self.getField(fieldName).unlinkObject(self.o, obj) return self.getField(fieldName).unlinkObject(self, obj)
def sort(self, fieldName, sortKey='title', reverse=False): def sort(self, fieldName, sortKey='title', reverse=False):
'''Sorts referred elements linked to p_self via p_fieldName according '''Sorts referred elements linked to p_self via p_fieldName according
@ -854,7 +854,7 @@ class AbstractWrapper(object):
setattr(appyObj, attrName, attrValue) setattr(appyObj, attrName, attrValue)
if isField: if isField:
# Link the object to this one # Link the object to this one
appyType.linkObject(self.o, zopeObj) appyType.linkObject(self, appyObj)
# Call custom initialization # Call custom initialization
if externalData: param = externalData if externalData: param = externalData
else: param = True else: param = True