Commit c558d5c3 authored by Jean-Paul Smets's avatar Jean-Paul Smets

Finalised and simplified the revision system spec.

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@13731 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent d3d479eb
...@@ -366,6 +366,19 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin): ...@@ -366,6 +366,19 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin):
Default implementation uses DocumentReferenceConstraint to check if the Default implementation uses DocumentReferenceConstraint to check if the
reference/language/version triplet is unique. Additional constraints reference/language/version triplet is unique. Additional constraints
can be added if necessary. can be added if necessary.
NOTE: Document.py supports a notion of revision which is very specific.
The underlying concept is that, as soon as a document has a reference,
the association of (reference, version, language) must be unique
accross the whole system. This means that a given document in a given
version in a given language is unique. The underlying idea is similar
to the one in a Wiki system in which each page is unique and acts
the the atom of collaboration. In the case of ERP5, if a team collaborates
on a Text document written with an offline word processor, all
updates should be placed inside the same object. A Contribution
will thus modify an existing document, if allowed from security
point of view, and increase the revision number. Same goes for
properties (title). Each change generates a new revision.
""" """
meta_type = 'ERP5 Document' meta_type = 'ERP5 Document'
...@@ -489,7 +502,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin): ...@@ -489,7 +502,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin):
return bool(self.getUrlString()) return bool(self.getUrlString())
### Relation getters ### Relation getters
security.declareProtected(Permissions.View, 'getSearchableReferenceList') security.declareProtected(Permissions.AccessContentsInformation, 'getSearchableReferenceList')
def getSearchableReferenceList(self): def getSearchableReferenceList(self):
""" """
This method returns a list of dictionaries which can This method returns a list of dictionaries which can
...@@ -509,7 +522,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin): ...@@ -509,7 +522,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin):
res = [(r.group(), r.groupdict()) for r in res] res = [(r.group(), r.groupdict()) for r in res]
return res return res
security.declareProtected(Permissions.View, 'getImplicitSuccessorValueList') security.declareProtected(Permissions.AccessContentsInformation, 'getImplicitSuccessorValueList')
def getImplicitSuccessorValueList(self): def getImplicitSuccessorValueList(self):
""" """
Find objects which we are referencing (if our text_content contains Find objects which we are referencing (if our text_content contains
...@@ -543,7 +556,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin): ...@@ -543,7 +556,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin):
res_dict = dict.fromkeys(res) res_dict = dict.fromkeys(res)
return res_dict.keys() return res_dict.keys()
security.declareProtected(Permissions.View, 'getImplicitPredecessorValueList') security.declareProtected(Permissions.AccessContentsInformation, 'getImplicitPredecessorValueList')
def getImplicitPredecessorValueList(self): def getImplicitPredecessorValueList(self):
""" """
This function tries to find document which are referencing us - by reference only, or This function tries to find document which are referencing us - by reference only, or
...@@ -574,7 +587,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin): ...@@ -574,7 +587,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin):
ref = self.getReference() ref = self.getReference()
return [o for o in di.keys() if o.getReference() != ref] # every object has its own reference in SearchableText return [o for o in di.keys() if o.getReference() != ref] # every object has its own reference in SearchableText
security.declareProtected(Permissions.View, 'getImplicitSimilarValueList') security.declareProtected(Permissions.AccessContentsInformation, 'getImplicitSimilarValueList')
def getImplicitSimilarValueList(self): def getImplicitSimilarValueList(self):
""" """
Analyses content of documents to find out by the content which documents Analyses content of documents to find out by the content which documents
...@@ -584,7 +597,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin): ...@@ -584,7 +597,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin):
""" """
return [] return []
security.declareProtected(Permissions.View, 'getSimilarCloudValueList') security.declareProtected(Permissions.AccessContentsInformation, 'getSimilarCloudValueList')
def getSimilarCloudValueList(self, depth=0): def getSimilarCloudValueList(self, depth=0):
""" """
Returns all documents which are similar to us, directly or indirectly, and Returns all documents which are similar to us, directly or indirectly, and
...@@ -620,17 +633,6 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin): ...@@ -620,17 +633,6 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin):
return lista_latest.keys() return lista_latest.keys()
security.declareProtected(Permissions.View, 'hasFile')
def hasFile(self):
"""
Checks whether we have an initial file
"""
_marker = []
if getattr(self,'data', _marker) is not _marker: # XXX-JPS - use propertysheet accessors
d = getattr(self, 'data')
return d is not None and d != ''
return False
### Version and language getters - might be moved one day to a mixin class in base ### Version and language getters - might be moved one day to a mixin class in base
security.declareProtected(Permissions.View, 'getLatestVersionValue') security.declareProtected(Permissions.View, 'getLatestVersionValue')
def getLatestVersionValue(self, language=None): def getLatestVersionValue(self, language=None):
...@@ -693,101 +695,58 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin): ...@@ -693,101 +695,58 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin):
if language: kw['language'] = language if language: kw['language'] = language
return catalog(**kw) return catalog(**kw)
security.declareProtected(Permissions.View, 'isVersionUnique') security.declareProtected(Permissions.AccessContentsInformation, 'isVersionUnique')
def isVersionUnique(self): def isVersionUnique(self):
""" """
Returns true if no other document of the same Returns true if no other document exists with the same
portal_type and reference has the same version and language reference, version and language, or if the current
document has no reference.
XXX should delegate to script with proxy roles
XXX-JPS revision ?
""" """
if not self.getReference():
return 1
catalog = getToolByName(self, 'portal_catalog', None) catalog = getToolByName(self, 'portal_catalog', None)
# XXX why this does not work??? # This method has been reported not to work. Extra test needed.
#return catalog.countResults(portal_type=self.getPortalType(), return catalog.unrestrictedCountResults(portal_type=self.getPortalDocumentTypeList(),
#reference=self.getReference(),
#version=self.getVersion(),
#language=self.getLanguage(),
#) <= 1
return len(catalog(portal_type=self.getPortalType(),
reference=self.getReference(), reference=self.getReference(),
version=self.getVersion(), version=self.getVersion(),
language=self.getLanguage(), language=self.getLanguage(),
)) <= 1 ) <= 1
security.declareProtected(Permissions.View, 'getLatestRevisionValue') security.declareProtected(Permissions.AccessContentsInformation, 'getRevision')
def getLatestRevisionValue(self): def getRevision(self):
""" """
Returns the latest revision of ourselves Returns the current revision by analysing the change log
""" of the current object.
if not self._checkCompleteCoordinates():
return None NOTE: for now, workflow choice is hardcoded. This is
catalog = getToolByName(self, 'portal_catalog', None) an optimisation hack. If a document does neither use
res = catalog( edit_workflow or processing_status_workflow, the
reference=self.getReference(), first workflow in the chain has prioriot. Better
language=self.getLanguage(), implementation would require to be able to define
version=self.getVersion(), which workflow in a chain is the default one for
sort_on=(('revision','descending'),) revision tracking (and for modification date).
) """
if len(res) == 0: portal_workflow = getToolByName(self, 'portal_workflow')
wf_list = list(portal_workflow.getWorkflowsFor(self))
wf = portal_workflow.getWorkflowById('edit_workflow')
if wf is not None: wf_list = [wf] + wf_list
wf = portal_workflow.getWorkflowById('processing_status_workflow')
if wf is not None: wf_list = [wf] + wf_list
for wf in wf_list:
history = wf.getInfoFor(self, 'history', None)
if history:
return len(filter(lambda x:x.get('action', None) in ('edit', 'upload'), history))
return None return None
return res[0].getObject()
security.declareProtected(Permissions.View, 'getRevisionValueList')
def getRevisionValueList(self):
"""
Returns a list revision strings for a given reference, version, language
XXX should it return revision strings, or docs (as the func name would suggest)?
XXX-JPS return values - getRevisionList returns revisions security.declareProtected(Permissions.AccessContentsInformation, 'getRevisionList')
def getRevisionList(self):
""" """
# Use portal_catalog Returns the list of revision numbers of the current document
if not self._checkCompleteCoordinates(): by by analysing the change log of the current object.
return []
res = self.portal_catalog(reference=self.getReference(),
language=self.getLanguage(),
version=self.getVersion()
)
d = {}
for r in res:
d[r.getRevision()] = True
revs = d.keys()
revs.sort(reverse=True)
return revs
security.declarePrivate('_checkCompleteCoordinates')
def _checkCompleteCoordinates(self):
""" """
test if the doc has all coordinates return range(0, self.getRevision())
XXX-JPS - revision ? security.declareProtected(Permissions.AccessContentsInformation, 'getLanguageList')
"""
reference = self.getReference()
version = self.getVersion()
language = self.getLanguage()
return (reference and version and language)
security.declareProtected(Permissions.ModifyPortalContent, 'setNewRevision')
def setNewRevision(self, immediate_reindex=False):
"""
Set a new revision number automatically
Delegates to ZMI script because revision numbering policies can be different.
Should be called by interaction workflow upon appropriate action.
Sometimes we should reindex immediately, to avoid other doc setting
the same revision (if revisions are global and there is heavy traffic)
"""
# Use portal_catalog without security (proxy roles on scripts)
method = self._getTypeBasedMethod('getNewRevision',
fallback_script_id = 'Document_getNewRevision')
new_rev = method()
self.setRevision(new_rev)
if immediate_reindex:
self.immediateReindexObject()
else:
self.reindexObject()
security.declareProtected(Permissions.View, 'getLanguageList')
def getLanguageList(self, version=None): def getLanguageList(self, version=None):
""" """
Returns a list of languages which this document is available in Returns a list of languages which this document is available in
...@@ -802,7 +761,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin): ...@@ -802,7 +761,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin):
kw['version'] = version kw['version'] = version
return map(lambda o:o.getLanguage(), catalog(**kw)) return map(lambda o:o.getLanguage(), catalog(**kw))
security.declareProtected(Permissions.View, 'getOriginalLanguage') security.declareProtected(Permissions.AccessContentsInformation, 'getOriginalLanguage')
def getOriginalLanguage(self): def getOriginalLanguage(self):
""" """
Returns the original language of this document. Returns the original language of this document.
...@@ -877,6 +836,23 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin): ...@@ -877,6 +836,23 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin):
# disappear within a given transaction # disappear within a given transaction
return kw return kw
security.declareProtected(Permissions.AccessContentsInformation, 'getStandardFileName')
def getStandardFileName(self):
"""
Returns the document coordinates as a standard file name. This
method is the reverse of getPropertyDictFromFileName.
NOTE: this method must be overloadable by types base method with fallback
"""
if self.getReference():
file_name = self.getReference()
else:
file_name = self.getTitleOrId()
if self.getVersion():
file_name = file_name + '-%s' % self.getVersion()
if self.getLanguage():
file_name = file_name + '-%s' % self.getLanguage()
return file_name
### Metadata disovery and ingestion methods ### Metadata disovery and ingestion methods
security.declareProtected(Permissions.ModifyPortalContent, 'discoverMetadata') security.declareProtected(Permissions.ModifyPortalContent, 'discoverMetadata')
...@@ -1151,7 +1127,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin): ...@@ -1151,7 +1127,7 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin):
base_url = '/'.join(base_url_list[:-1]) base_url = '/'.join(base_url_list[:-1])
return base_url return base_url
# Alarm date calculation # Alarm date calculation - this method should be moved out ASAP
security.declareProtected(Permissions.AccessContentsInformation, 'getNextAlarmDate') security.declareProtected(Permissions.AccessContentsInformation, 'getNextAlarmDate')
def getNextAlarmDate(self): def getNextAlarmDate(self):
""" """
...@@ -1160,22 +1136,3 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin): ...@@ -1160,22 +1136,3 @@ class Document(XMLObject, UrlMixIn, ConversionCacheMixin, SnapshotMixin):
classes is needed. classes is needed.
""" """
return DateTime() + 10 return DateTime() + 10
# Standard File Naming
security.declareProtected(Permissions.AccessContentsInformation, 'getStandardFileName')
def getStandardFileName(self):
"""
Returns the document coordinates as a standard file name.
NOTE: this method must be overloadable by types base method with fallback
"""
if self.getReference():
file_name = self.getReference()
else:
file_name = self.getTitleOrId()
if self.getVersion():
file_name = file_name + '-%s' % self.getVersion()
if self.getLanguage():
file_name = file_name + '-%s' % self.getLanguage()
return file_name
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment