merge branch master

This commit is contained in:
Helmut Merz 2015-08-29 11:12:58 +02:00
commit 07bb68ae9d
15 changed files with 152 additions and 57 deletions

View file

@ -1066,6 +1066,7 @@ class LoggedIn(object):
params = parse_qsl(qs) params = parse_qsl(qs)
params = [(k, v) for k, v in params if k != 'loops.messages.top:record'] params = [(k, v) for k, v in params if k != 'loops.messages.top:record']
params.append(('loops.messages.top:record', message.encode('UTF-8'))) params.append(('loops.messages.top:record', message.encode('UTF-8')))
url = url.encode('utf-8')
return '%s?%s' % (url, urlencode(params)) return '%s?%s' % (url, urlencode(params))
# vocabulary stuff # vocabulary stuff

View file

@ -954,7 +954,8 @@ class NodeTraverser(ItemTraverser):
if context.nodeType == 'menu': if context.nodeType == 'menu':
setViewConfiguration(context, request) setViewConfiguration(context, request)
if name == '.loops': if name == '.loops':
return self.context.getLoopsRoot() name = self.getTargetUid(request)
#return self.context.getLoopsRoot()
if name.startswith('.'): if name.startswith('.'):
name = self.cleanUpTraversalStack(request, name)[1:] name = self.cleanUpTraversalStack(request, name)[1:]
target = self.getTarget(name) target = self.getTarget(name)
@ -981,17 +982,34 @@ class NodeTraverser(ItemTraverser):
obj = super(NodeTraverser, self).publishTraverse(request, name) obj = super(NodeTraverser, self).publishTraverse(request, name)
return obj return obj
def getTargetUid(self, request):
parent = self.context.getLoopsRoot()
stack = request._traversal_stack
for i in range(2):
name = stack.pop()
obj = parent.get(name)
if not obj:
return name
parent = obj
return '.' + util.getUidForObject(obj)
def cleanUpTraversalStack(self, request, name): def cleanUpTraversalStack(self, request, name):
traversalStack = request._traversal_stack #traversalStack = request._traversal_stack
while traversalStack and traversalStack[0].startswith('.'): #while traversalStack and traversalStack[0].startswith('.'):
# skip obsolete target references in the url # skip obsolete target references in the url
name = traversalStack.pop(0) # name = traversalStack.pop(0)
traversedNames = request._traversed_names traversedNames = request._traversed_names
if traversedNames: for n in list(traversedNames):
lastTraversed = traversedNames[-1] if n.startswith('.'):
if lastTraversed.startswith('.') and lastTraversed != name: # remove obsolete target refs
traversedNames.remove(n)
#if traversedNames:
# lastTraversed = traversedNames[-1]
# if lastTraversed.startswith('.') and lastTraversed != name:
# let <base .../> tag show the current object # let <base .../> tag show the current object
traversedNames[-1] = name # traversedNames[-1] = name
# let <base .../> tag show the current object
traversedNames.append(name)
return name return name
def getTarget(self, name): def getTarget(self, name):

View file

@ -1,6 +1,8 @@
# types # types
type(u'query', u'Abfrage', options=u'', type(u'query', u'Abfrage', options=u'',
typeInterface='loops.expert.concept.IQueryConcept', viewName=u'') typeInterface='loops.expert.concept.IQueryConcept', viewName=u'')
type(u'datatable', u'Datentabelle', options=u'action.portlet:edit_concept',
typeInterface='loops.table.IDataTable', viewName=u'')
type(u'task', u'Aufgabe', options=u'', type(u'task', u'Aufgabe', options=u'',
typeInterface='loops.knowledge.interfaces.ITask', viewName=u'') typeInterface='loops.knowledge.interfaces.ITask', viewName=u'')
type(u'domain', u'Bereich', options=u'', typeInterface=u'', viewName=u'') type(u'domain', u'Bereich', options=u'', typeInterface=u'', viewName=u'')

View file

@ -0,0 +1,9 @@
# update for old loops sites
type(u'datatable', u'Datentabelle', options=u'action.portlet:edit_concept',
typeInterface='loops.table.IDataTable', viewName=u'')
concept(u'issubtype', u'is Subtype', u'predicate')
child(u'general', u'issubtype', u'datatable')
child(u'system', u'issubtype', u'standard')

6
external/pyfunc.py vendored
View file

@ -44,11 +44,15 @@ class PyReader(object):
class InputProcessor(dict): class InputProcessor(dict):
_constants = dict(True=True, False=False)
def __init__(self): def __init__(self):
self.elements = [] self.elements = []
self['__builtins__'] = {} # security! self['__builtins__'] = dict() # security!
def __getitem__(self, key): def __getitem__(self, key):
if key in self._constants:
return self._constants[key]
def factory(*args, **kw): def factory(*args, **kw):
element = elementTypes[key](*args, **kw) element = elementTypes[key](*args, **kw)
if key in toplevelElements: if key in toplevelElements:

View file

@ -44,5 +44,7 @@ class ExternalCollectionView(ConceptView):
cta.update() cta.update()
if cta.updateMessage is not None: if cta.updateMessage is not None:
self.request.form['message'] = cta.updateMessage self.request.form['message'] = cta.updateMessage
if 'no_show_page' in self.request.form:
return False
return True return True

View file

@ -24,8 +24,10 @@ file system.
from datetime import datetime from datetime import datetime
from logging import getLogger from logging import getLogger
import os, re, stat import os, re, stat
import transaction
from zope.app.container.interfaces import INameChooser from zope.app.container.interfaces import INameChooser
from zope.app.container.contained import ObjectRemovedEvent
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from zope import component from zope import component
from zope.component import adapts from zope.component import adapts
@ -51,6 +53,8 @@ from loops.versioning.interfaces import IVersionable
TypeInterfaceSourceList.typeInterfaces += (IExternalCollection,) TypeInterfaceSourceList.typeInterfaces += (IExternalCollection,)
logger = getLogger('loops.integrator.collection')
class ExternalCollectionAdapter(AdapterBase): class ExternalCollectionAdapter(AdapterBase):
""" A concept adapter for accessing an external collection. """ A concept adapter for accessing an external collection.
@ -83,10 +87,11 @@ class ExternalCollectionAdapter(AdapterBase):
print '###', vaddr, vobj, vid print '###', vaddr, vobj, vid
versions.add(vaddr) versions.add(vaddr)
new = [] new = []
oldFound = [] oldFound = set([])
provider = component.getUtility(IExternalCollectionProvider, provider = component.getUtility(IExternalCollectionProvider,
name=self.providerName or '') name=self.providerName or '')
#print '*** old', old, versions, self.lastUpdated #print '*** old', old, versions, self.lastUpdated
changeCount = 0
for addr, mdate in provider.collect(self): for addr, mdate in provider.collect(self):
#print '***', addr, mdate #print '***', addr, mdate
if addr in versions: if addr in versions:
@ -94,8 +99,9 @@ class ExternalCollectionAdapter(AdapterBase):
if addr in old: if addr in old:
# may be it would be better to return a file's hash # may be it would be better to return a file's hash
# for checking for changes... # for checking for changes...
oldFound.append(addr) oldFound.add(addr)
if self.lastUpdated is None or (mdate and mdate > self.lastUpdated): if self.lastUpdated is None or (mdate and mdate > self.lastUpdated):
changeCount +=1
obj = old[addr] obj = old[addr]
# update settings and regenerate scale variant for media asset # update settings and regenerate scale variant for media asset
adobj = adapted(obj) adobj = adapted(obj)
@ -110,29 +116,41 @@ class ExternalCollectionAdapter(AdapterBase):
self.updateMessage = message self.updateMessage = message
# force reindexing # force reindexing
notify(ObjectModifiedEvent(obj)) notify(ObjectModifiedEvent(obj))
if changeCount % 10 == 0:
logger.info('Updated: %i.' % changeCount)
transaction.commit()
else: else:
new.append(addr) new.append(addr)
logger.info('%i objects updated.' % changeCount)
transaction.commit()
if new: if new:
self.newResources = provider.createExtFileObjects(self, new) self.newResources = provider.createExtFileObjects(self, new)
for r in self.newResources: for r in self.newResources:
self.context.assignResource(r) self.context.assignResource(r)
logger.info('%i objects created.' % len(new))
transaction.commit()
for addr in old: for addr in old:
if str(addr) not in oldFound: if str(addr) not in oldFound:
# not part of the collection any more # not part of the collection any more
# TODO: only remove from collection but keep object? # TODO: only remove from collection but keep object?
self.remove(old[addr]) self.remove(old[addr])
transaction.commit()
for r in self.context.getResources(): for r in self.context.getResources():
adobj = adapted(r) adobj = adapted(r)
if self.metaInfo != adobj.metaInfo and ( if self.metaInfo != adobj.metaInfo and (
not adobj.metaInfo or self.overwriteMetaInfo): not adobj.metaInfo or self.overwriteMetaInfo):
adobj.metaInfo = self.metaInfo adobj.metaInfo = self.metaInfo
self.lastUpdated = datetime.today() self.lastUpdated = datetime.today()
logger.info('External collection updated.')
transaction.commit()
def clear(self): def clear(self):
for obj in self.context.getResources(): for obj in self.context.getResources():
self.remove(obj) self.remove(obj)
def remove(self, obj): def remove(self, obj):
logger.info('Removing object: %s.' % getName(obj))
notify(ObjectRemovedEvent(obj))
del self.resourceManager[getName(obj)] del self.resourceManager[getName(obj)]
@Lazy @Lazy
@ -187,7 +205,7 @@ class DirectoryCollectionProvider(object):
for k, v in self.extFileTypeMapping.items()) for k, v in self.extFileTypeMapping.items())
container = client.context.getLoopsRoot().getResourceManager() container = client.context.getLoopsRoot().getResourceManager()
directory = self.getDirectory(client) directory = self.getDirectory(client)
for addr in addresses: for idx, addr in enumerate(addresses):
name = self.generateName(container, addr) name = self.generateName(container, addr)
title = self.generateTitle(addr) title = self.generateTitle(addr)
contentType = guess_content_type(addr, contentType = guess_content_type(addr,
@ -200,9 +218,8 @@ class DirectoryCollectionProvider(object):
if extFileType is None: if extFileType is None:
extFileType = extFileTypes['image/*'] extFileType = extFileTypes['image/*']
if extFileType is None: if extFileType is None:
getLogger('loops.integrator.collection.DirectoryCollectionProvider' logger.warn('No external file type found for %r, '
).warn('No external file type found for %r, ' 'content type: %r' % (name, contentType))
'content type: %r' % (name, contentType))
obj = addAndConfigureObject( obj = addAndConfigureObject(
container, Resource, name, container, Resource, name,
title=title, title=title,
@ -219,6 +236,9 @@ class DirectoryCollectionProvider(object):
message = client.updateMessage or u'' message = client.updateMessage or u''
message += u'<br />'.join(adobj.processingErrors) message += u'<br />'.join(adobj.processingErrors)
client.updateMessage = message client.updateMessage = message
if idx and idx % 10 == 0:
logger.info('Created: %i.' % idx)
transaction.commit()
yield obj yield obj
def getDirectory(self, client): def getDirectory(self, client):

View file

@ -2,7 +2,7 @@
<metal:block define-macro="render_collection" <metal:block define-macro="render_collection"
tal:define="dummy item/update"> tal:condition="item/update">
<metal:block use-macro="view/concept_macros/conceptdata"> <metal:block use-macro="view/concept_macros/conceptdata">
<metal:fill tal:condition="item/editable" <metal:fill tal:condition="item/editable"

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de # Copyright (c) 2015 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -46,11 +46,17 @@ class Questionnaire(AdapterBase, Questionnaire):
@property @property
def questionGroups(self): def questionGroups(self):
return self.getQuestionGroups()
def getQuestionGroups(self, personId=None):
return [adapted(c) for c in self.context.getChildren()] return [adapted(c) for c in self.context.getChildren()]
@property @property
def questions(self): def questions(self):
for qug in self.questionGroups: return self.getQuestions()
def getQuestions(self, personId=None):
for qug in self.getQuestionGroups(personId):
for qu in qug.questions: for qu in qug.questions:
#qu.questionnaire = self #qu.questionnaire = self
yield qu yield qu
@ -65,12 +71,18 @@ class QuestionGroup(AdapterBase, QuestionGroup):
'questionnaire', 'questions', 'feedbackItems') 'questionnaire', 'questions', 'feedbackItems')
_noexportAttributes = _adapterAttributes _noexportAttributes = _adapterAttributes
@property def getQuestionnaires(self):
def questionnaire(self): result = []
for p in self.context.getParents(): for p in self.context.getParents():
ap = adapted(p) ap = adapted(p)
if IQuestionnaire.providedBy(ap): if IQuestionnaire.providedBy(ap):
return ap result.append(ap)
return result
@property
def questionnaire(self):
for qu in self.getQuestionnaires():
return qu
@property @property
def subobjects(self): def subobjects(self):

View file

@ -52,7 +52,7 @@ class SurveyView(InstitutionMixin, ConceptView):
template = template template = template
adminMaySelectAllInstitutions = False #adminMaySelectAllInstitutions = False
@Lazy @Lazy
def macro(self): def macro(self):
@ -62,9 +62,8 @@ class SurveyView(InstitutionMixin, ConceptView):
@Lazy @Lazy
def title(self): def title(self):
title = self.context.title title = self.context.title
personId = self.request.form.get('person') if self.personId:
if personId: person = adapted(getObjectForUid(self.personId))
person = adapted(getObjectForUid(personId))
if person is not None: if person is not None:
return '%s: %s' % (title, person.title) return '%s: %s' % (title, person.title)
return title return title
@ -80,6 +79,10 @@ class SurveyView(InstitutionMixin, ConceptView):
return '' return ''
return qs return qs
@Lazy
def personId(self):
return self.request.form.get('person')
@Lazy @Lazy
def report(self): def report(self):
return self.request.form.get('report') return self.request.form.get('report')
@ -104,7 +107,8 @@ class SurveyView(InstitutionMixin, ConceptView):
def groups(self): def groups(self):
result = [] result = []
if self.questionnaireType == 'pref_selection': if self.questionnaireType == 'pref_selection':
groups = [g.questions for g in self.adapted.questionGroups] groups = [g.questions for g in
self.adapted.getQuestionGroups(self.personId)]
questions = [] questions = []
for idxg, g in enumerate(groups): for idxg, g in enumerate(groups):
qus = [] qus = []
@ -118,7 +122,7 @@ class SurveyView(InstitutionMixin, ConceptView):
questions=questions[idx:idx+3])) questions=questions[idx:idx+3]))
return [g for g in result if len(g['questions']) == 3] return [g for g in result if len(g['questions']) == 3]
if self.adapted.noGrouping: if self.adapted.noGrouping:
questions = list(self.adapted.questions) questions = list(self.adapted.getQuestions(self.personId))
questions.sort(key=lambda x: x.title) questions.sort(key=lambda x: x.title)
size = len(questions) size = len(questions)
bs = self.batchSize bs = self.batchSize
@ -126,7 +130,7 @@ class SurveyView(InstitutionMixin, ConceptView):
result.append(dict(title=u'Question', infoText=None, result.append(dict(title=u'Question', infoText=None,
questions=questions[idx:idx+bs])) questions=questions[idx:idx+bs]))
else: else:
for group in self.adapted.questionGroups: for group in self.adapted.getQuestionGroups(self.personId):
result.append(dict(title=group.title, result.append(dict(title=group.title,
infoText=self.getInfoText(group), infoText=self.getInfoText(group),
questions=group.questions)) questions=group.questions))
@ -185,8 +189,8 @@ class SurveyView(InstitutionMixin, ConceptView):
uid = self.getUidForObject(c) uid = self.getUidForObject(c)
data = respManager.load(uid, instUid) data = respManager.load(uid, instUid)
if data: if data:
resp = Response(self.adapted, None) resp = Response(self.adapted, self.personId)
for qu in self.adapted.questions: for qu in self.adapted.getQuestions(self.personId):
if qu.questionType in (None, 'value_selection'): if qu.questionType in (None, 'value_selection'):
if qu.uid in data: if qu.uid in data:
value = data[qu.uid] value = data[qu.uid]
@ -195,7 +199,7 @@ class SurveyView(InstitutionMixin, ConceptView):
else: else:
resp.texts[qu] = data.get(qu.uid) or u'' resp.texts[qu] = data.get(qu.uid) or u''
qgAvailable = True qgAvailable = True
for qg in self.adapted.questionGroups: for qg in self.adapted.getQuestionGroups(self.personId):
if qg.uid in data: if qg.uid in data:
resp.values[qg] = data[qg.uid] resp.values[qg] = data[qg.uid]
else: else:
@ -227,7 +231,7 @@ class SurveyView(InstitutionMixin, ConceptView):
if self.adapted.questionnaireType == 'pref_selection': if self.adapted.questionnaireType == 'pref_selection':
return self.prefsResults(respManager, form, action) return self.prefsResults(respManager, form, action)
data = {} data = {}
response = Response(self.adapted, None) response = Response(self.adapted, self.personId)
for key, value in form.items(): for key, value in form.items():
if key.startswith('question_'): if key.startswith('question_'):
if value != 'none': if value != 'none':
@ -265,7 +269,7 @@ class SurveyView(InstitutionMixin, ConceptView):
respManager = Responses(self.context) respManager = Responses(self.context)
self.teamData = self.getTeamData(respManager) self.teamData = self.getTeamData(respManager)
response = Response(self.adapted, None) response = Response(self.adapted, None)
groups = self.adapted.questionGroups groups = self.adapted.getQuestionGroups(self.personId)
teamValues = response.getTeamResult(groups, self.teamData) teamValues = response.getTeamResult(groups, self.teamData)
for idx, r in enumerate(teamValues): for idx, r in enumerate(teamValues):
group = r['group'] group = r['group']
@ -314,7 +318,7 @@ class SurveyView(InstitutionMixin, ConceptView):
#self.errors = self.check(response) #self.errors = self.check(response)
if self.errors: if self.errors:
return [] return []
for group in self.adapted.questionGroups: for group in self.adapted.getQuestionGroups(self.personId):
score = 0 score = 0
for qu in group.questions: for qu in group.questions:
value = data.get(qu.uid) or 0 value = data.get(qu.uid) or 0
@ -327,13 +331,13 @@ class SurveyView(InstitutionMixin, ConceptView):
def check(self, response): def check(self, response):
errors = [] errors = []
values = response.values values = response.values
for qu in self.adapted.questions: for qu in self.adapted.getQuestions(self.personId):
if qu.required and qu not in values: if qu.required and qu not in values:
errors.append(dict(uid=qu.uid, errors.append(dict(uid=qu.uid,
text='Please answer the obligatory questions.')) text='Please answer the obligatory questions.'))
break break
qugroups = {} qugroups = {}
for qugroup in self.adapted.questionGroups: for qugroup in self.adapted.getQuestionGroups(self.personId):
qugroups[qugroup] = 0 qugroups[qugroup] = 0
for qu in values: for qu in values:
qugroups[qu.questionGroup] += 1 qugroups[qu.questionGroup] += 1

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de # Copyright (c) 2015 Helmut Merz helmutm@cy55.de
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -133,6 +133,10 @@ class SendEmailForm(NodeView):
__call__ = innerHtml __call__ = innerHtml
def checkPermissions(self):
return (not self.isAnonymous and
super(SendEmailForm, self).checkPermissions())
@property @property
def macro(self): def macro(self):
return organize_macros.macros['send_email'] return organize_macros.macros['send_email']
@ -181,12 +185,16 @@ class SendEmailForm(NodeView):
class SendEmail(FormController): class SendEmail(FormController):
def checkPermissions(self):
return (not self.isAnonymous and
super(SendEmail, self).checkPermissions())
def update(self): def update(self):
form = self.request.form form = self.request.form
subject = form.get('subject') or u'' subject = form.get('subject') or u''
message = form.get('mailbody') or u'' message = form.get('mailbody') or u''
recipients = form.get('recipients') or [] recipients = form.get('recipients') or []
recipients += (form.get('addrRecipients') or u'').split('\n') recipients += (form.get('addrecipients') or u'').split('\n')
# TODO: remove duplicates # TODO: remove duplicates
person = getPersonForUser(self.context, self.request) person = getPersonForUser(self.context, self.request)
sender = person and adapted(person).email or 'loops@unknown.com' sender = person and adapted(person).email or 'loops@unknown.com'

View file

@ -123,20 +123,23 @@
<div class="heading"> <div class="heading">
<span i18n:translate="">Send Link by Email</span> - <span i18n:translate="">Send Link by Email</span> -
<span tal:content="view/target/title"></span></div> <span tal:content="view/target/title"></span></div>
<div> <metal:content define-macro="mail_content">
<label i18n:translate="" for="subject">Subject</label>
<div> <div>
<input name="subject" id="subject" style="width: 60em" <label i18n:translate="" for="subject">Subject</label>
dojoType="dijit.form.ValidationTextBox" required <div>
tal:attributes="value view/subject" /></div> <input name="subject" id="subject" style="width: 60em"
</div> dojoType="dijit.form.ValidationTextBox" required
<div> tal:attributes="value view/subject" /></div>
<label i18n:translate="" for="mailbody">Mail Body</label> </div>
<div> <div>
<textarea name="mailbody" cols="80" rows="4" id="mailbody" <label i18n:translate="" for="mailbody">Mail Body</label>
dojoType="dijit.form.SimpleTextarea" style="width: 60em" <div>
tal:content="view/mailBody"></textarea></div> <textarea name="mailbody" cols="80" rows="4" id="mailbody"
</div> dojoType="dijit.form.SimpleTextarea" style="width: 60em"
tal:attributes="rows view/contentHeight|string:4"
tal:content="view/mailBody"></textarea></div>
</div>
</metal:content>
<div> <div>
<label i18n:translate="">Recipients</label> <label i18n:translate="">Recipients</label>
<div tal:repeat="member view/members"> <div tal:repeat="member view/members">

View file

@ -24,6 +24,7 @@ from persistent.mapping import PersistentMapping
from zope import interface, component from zope import interface, component
from zope.app.principalannotation import annotations from zope.app.principalannotation import annotations
from zope.app.security.interfaces import IAuthentication, PrincipalLookupError from zope.app.security.interfaces import IAuthentication, PrincipalLookupError
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.component import adapts from zope.component import adapts
from zope.interface import implements from zope.interface import implements
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
@ -60,10 +61,10 @@ def getPersonForUser(context, request=None, principal=None):
if context is None: if context is None:
return None return None
if principal is None: if principal is None:
if request is None: if request is not None:
principal = getCurrentPrincipal()
else:
principal = getattr(request, 'principal', None) principal = getattr(request, 'principal', None)
else:
principal = getPrincipal(context)
if principal is None: if principal is None:
return None return None
loops = context.getLoopsRoot() loops = context.getLoopsRoot()
@ -78,6 +79,15 @@ def getPersonForUser(context, request=None, principal=None):
return pa.get(util.getUidForObject(loops)) return pa.get(util.getUidForObject(loops))
def getPrincipal(context):
principal = getCurrentPrincipal()
if principal is not None:
if IUnauthenticatedPrincipal.providedBy(principal):
return None
return principal
return None
class Person(AdapterBase, BasePerson): class Person(AdapterBase, BasePerson):
""" typeInterface adapter for concepts of type 'person'. """ typeInterface adapter for concepts of type 'person'.
""" """

View file

@ -79,7 +79,7 @@
<tal:version tal:condition="view/useVersioning"> <tal:version tal:condition="view/useVersioning">
<td class="center" <td class="center"
tal:define="version object/version"> tal:define="version object/version">
<a tal:omit-tag="python: version == '1.1'" <a tal:omit-tag="python: version in ('1.1', '1', '')"
tal:attributes="href string:$url?loops.viewName=listversions" tal:attributes="href string:$url?loops.viewName=listversions"
tal:content="version">1.1</a> tal:content="version">1.1</a>
</td> </td>

View file

@ -275,7 +275,9 @@ class TrackDetails(BaseView):
else: else:
title = view.listingTitle title = view.listingTitle
versionable = IVersionable(self.object, None) versionable = IVersionable(self.object, None)
version = versionable is not None and versionable.versionId or '' version = ((versionable is not None and
not (versionable.notVersioned) and
versionable.versionId) or '')
return dict(object=obj, title=title, return dict(object=obj, title=title,
type=self.longTypeTitle, url=url, version=version, type=self.longTypeTitle, url=url, version=version,
canAccess=canAccessObject(obj)) canAccess=canAccessObject(obj))