1161 lines
38 KiB
Python
Executable file
1161 lines
38 KiB
Python
Executable file
#
|
|
# Copyright (c) 2016 Helmut Merz helmutm@cy55.de
|
|
#
|
|
# 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
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
|
|
"""
|
|
Common base class for loops browser view classes.
|
|
"""
|
|
|
|
from cgi import parse_qsl
|
|
#import mimetypes # use more specific assignments from cybertools.text
|
|
from datetime import date, datetime
|
|
from logging import getLogger
|
|
import re
|
|
from time import strptime
|
|
from urllib import urlencode
|
|
from urlparse import parse_qs
|
|
from zope import component
|
|
from zope.app.form.browser.interfaces import ITerms
|
|
from zope.app.i18n.interfaces import ITranslationDomain
|
|
from zope.app.security.interfaces import IAuthentication, IUnauthenticatedPrincipal
|
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
|
from zope.app.security.interfaces import IUnauthenticatedPrincipal
|
|
from zope.app.security.interfaces import PrincipalLookupError
|
|
from zope.cachedescriptors.property import Lazy
|
|
from zope.dottedname.resolve import resolve
|
|
from zope.dublincore.interfaces import IZopeDublinCore
|
|
from zope.formlib import form
|
|
from zope.formlib.form import FormFields
|
|
from zope.formlib.namedtemplate import NamedTemplate
|
|
from zope.interface import Interface, implements
|
|
from zope.proxy import removeAllProxies
|
|
from zope.publisher.browser import applySkin
|
|
from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView
|
|
from zope import schema
|
|
from zope.schema.vocabulary import SimpleTerm
|
|
from zope.security import canAccess
|
|
from zope.security.interfaces import ForbiddenAttribute, Unauthorized
|
|
from zope.security.proxy import removeSecurityProxy
|
|
from zope.traversing.browser import absoluteURL
|
|
from zope.traversing.api import getName, getParent, traverse
|
|
|
|
from cybertools.ajax.dojo import dojoMacroTemplate
|
|
from cybertools.browser.action import actions
|
|
from cybertools.browser.view import GenericView
|
|
from cybertools.meta.interfaces import IOptions
|
|
from cybertools.meta.element import Element
|
|
from cybertools.relation.interfaces import IRelationRegistry
|
|
from cybertools.stateful.interfaces import IStateful
|
|
from cybertools.text import mimetypes
|
|
from cybertools.typology.interfaces import IType, ITypeManager
|
|
from cybertools.util.date import toLocalTime
|
|
from cybertools.util.format import formatDate
|
|
from cybertools.util.jeep import Jeep
|
|
from loops.browser.util import normalizeForUrl
|
|
from loops.common import adapted, baseObject
|
|
from loops.config.base import DummyOptions
|
|
from loops.i18n.browser import I18NView
|
|
from loops.interfaces import IResource, IView, INode, ITypeConcept
|
|
from loops.organize.personal import favorite
|
|
from loops.organize.party import getPersonForUser
|
|
from loops.organize.tracking import access
|
|
from loops.organize.util import getRolesForPrincipal
|
|
from loops.resource import Resource
|
|
from loops.security.common import checkPermission
|
|
from loops.security.common import canAccessObject, canListObject, canWriteObject
|
|
from loops.security.common import canEditRestricted
|
|
from loops.type import ITypeConcept, LoopsTypeInfo
|
|
from loops import util
|
|
from loops.util import _, saveRequest
|
|
from loops import version
|
|
from loops.versioning.interfaces import IVersionable
|
|
|
|
|
|
concept_macros = ViewPageTemplateFile('concept_macros.pt')
|
|
conceptMacrosTemplate = concept_macros
|
|
resource_macros = ViewPageTemplateFile('resource_macros.pt')
|
|
form_macros = ViewPageTemplateFile('form_macros.pt')
|
|
|
|
|
|
class NameField(schema.ASCIILine):
|
|
|
|
def _validate(self, value):
|
|
super(NameField, self)._validate(value)
|
|
|
|
|
|
class ViewMode(object):
|
|
|
|
def __init__(self, name='view', title=None, url=None, active=False,
|
|
description=u'', subViewModes=Jeep()):
|
|
self.name = name
|
|
self.title = title
|
|
self.url = url
|
|
self.active = active
|
|
self.description = description
|
|
self.subViewModes = subViewModes
|
|
|
|
@property
|
|
def cssClass(self):
|
|
result = self.active and u'active' or u'inactive'
|
|
if self.subViewModes:
|
|
result += u' sub-modes'
|
|
return result
|
|
|
|
|
|
class IAddForm(Interface):
|
|
|
|
name = NameField(
|
|
title=_(u'Object name'),
|
|
description=_(u'Name of the object - will be used for addressing the '
|
|
u'object via a URL; should therefore be unique within '
|
|
u'the container and not contain special characters'))
|
|
|
|
|
|
class AddForm(form.AddForm):
|
|
|
|
form_fields = FormFields(IAddForm)
|
|
template = NamedTemplate('loops.pageform')
|
|
|
|
|
|
class EditForm(form.EditForm):
|
|
|
|
template = NamedTemplate('loops.pageform')
|
|
|
|
def deleteObjectAction(self):
|
|
return None # better not to show the delete button at the moment
|
|
parent = getParent(self.context)
|
|
parentUrl = absoluteURL(parent, self.request)
|
|
return parentUrl + '/contents.html'
|
|
|
|
|
|
class SortableMixin(object):
|
|
|
|
@Lazy
|
|
def sortInfo(self):
|
|
result = {}
|
|
for k, v in self.request.form.items():
|
|
if k.startswith('sortinfo_'):
|
|
tableName = k[len('sortinfo_'):]
|
|
if ',' in v:
|
|
fn, dir = v.split(',')
|
|
else:
|
|
fn = v
|
|
dir = 'asc'
|
|
result[tableName] = dict(
|
|
colName=fn, ascending=(dir=='asc'), fparam=v)
|
|
result = favorite.updateSortInfo(getPersonForUser(
|
|
self.context, self.request), self.target, result)
|
|
return result
|
|
|
|
def isSortableColumn(self, tableName, colName):
|
|
return False # overwrite in subclass
|
|
|
|
def getSortUrl(self, tableName, colName):
|
|
url = str(self.request.URL)
|
|
paramChar = '?' in url and '&' or '?'
|
|
si = self.sortInfo.get(tableName)
|
|
if si is not None and si.get('colName') == colName:
|
|
dir = si['ascending'] and 'desc' or 'asc'
|
|
else:
|
|
dir = 'asc'
|
|
return '%s%ssortinfo_%s=%s,%s' % (url, paramChar, tableName, colName, dir)
|
|
|
|
def getSortParams(self, tableName):
|
|
url = str(self.request.URL)
|
|
paramChar = '?' in url and '&' or '?'
|
|
si = self.sortInfo.get(tableName)
|
|
if si is not None:
|
|
colName = si['colName']
|
|
dir = si['ascending'] and 'asc' or 'desc'
|
|
return '%ssortinfo_%s=%s,%s' % (paramChar, tableName, colName, dir)
|
|
return ''
|
|
|
|
def getSortImage(self, tableName, colName):
|
|
si = self.sortInfo.get(tableName)
|
|
if si is not None and si.get('colName') == colName:
|
|
if si['ascending']:
|
|
return '/@@/cybertools.icons/arrowdown.gif'
|
|
else:
|
|
return '/@@/cybertools.icons/arrowup.gif'
|
|
|
|
|
|
class BaseView(GenericView, I18NView, SortableMixin):
|
|
|
|
actions = {}
|
|
portlet_actions = []
|
|
parts = ()
|
|
subparts = ()
|
|
icon = None
|
|
modeName = 'view'
|
|
isToplevel = False
|
|
isVisible = True
|
|
|
|
def __init__(self, context, request):
|
|
context = baseObject(context)
|
|
super(BaseView, self).__init__(context, request)
|
|
# TODO: get rid of removeSecurityProxy() call - not yet...
|
|
self.context = removeSecurityProxy(context)
|
|
try:
|
|
if not self.checkPermissions():
|
|
logger = getLogger('loops.browser.common-153')
|
|
principal = request.principal and request.principal.id
|
|
msg = 'Unauthorized: %s, %s' % (self.contextInfo, principal)
|
|
logger.warn(msg)
|
|
raise Unauthorized(str(self.contextInfo))
|
|
except ForbiddenAttribute: # ignore when testing
|
|
pass
|
|
saveRequest(request)
|
|
|
|
def todayFormatted(self):
|
|
return formatDate(date.today(), 'date', 'short',
|
|
self.languageInfo.language)
|
|
|
|
def checkPermissions(self):
|
|
return canAccessObject(self.context)
|
|
|
|
def translate(self, text, msgFactory=_):
|
|
if msgFactory is None:
|
|
return text
|
|
return msgFactory(text)
|
|
|
|
@Lazy
|
|
def contextInfo(self):
|
|
return dict(view=self, context=getName(self.context))
|
|
|
|
@Lazy
|
|
def conceptMacros(self):
|
|
return self.controller.getTemplateMacros('concept', concept_macros)
|
|
#return concept_macros.macros
|
|
|
|
concept_macros = conceptMacros
|
|
|
|
@Lazy
|
|
def resource_macros(self):
|
|
return self.controller.getTemplateMacros('resource', resource_macros)
|
|
|
|
@Lazy
|
|
def form_macros(self):
|
|
return self.controller.getTemplateMacros('form', form_macros)
|
|
|
|
def breadcrumbs(self):
|
|
return []
|
|
|
|
def viewModes(self):
|
|
return Jeep()
|
|
|
|
@Lazy
|
|
def name(self):
|
|
return getName(self.context)
|
|
|
|
def makeTargetUrl(self, baseUrl, targetId, title=None):
|
|
if self.globalOptions('useInformativeURLs') and title:
|
|
return '%s/.%s-%s' % (baseUrl, targetId, normalizeForUrl(title))
|
|
return '%s/.%s' % (baseUrl, targetId)
|
|
|
|
def filterInput(self):
|
|
result = []
|
|
for name in self.getOptions('filter_input'):
|
|
view = component.queryMultiAdapter(
|
|
(self.context, self.request), name='filter_input.' + name)
|
|
if view is not None:
|
|
result.append(view)
|
|
return result
|
|
|
|
@Lazy
|
|
def urlParamString(self):
|
|
return self.getUrlParamString()
|
|
|
|
def getUrlParamString(self):
|
|
qs = self.request.get('QUERY_STRING')
|
|
if qs:
|
|
return '?' + qs
|
|
return ''
|
|
|
|
@Lazy
|
|
def principalId(self):
|
|
principal = self.request.principal
|
|
return principal and principal.id or ''
|
|
|
|
@Lazy
|
|
def isAnonymous(self):
|
|
return IUnauthenticatedPrincipal.providedBy(self.request.principal)
|
|
|
|
def recordAccess(self, viewName, **kw):
|
|
access.record(self.request, principal=self.principalId, view=viewName, **kw)
|
|
|
|
@Lazy
|
|
def versions(self):
|
|
return version.versions
|
|
|
|
@Lazy
|
|
def longVersions(self):
|
|
return version.longVersions
|
|
|
|
def update(self):
|
|
result = super(BaseView, self).update()
|
|
self.checkLanguage()
|
|
return result
|
|
|
|
def registerPortlets(self):
|
|
pass
|
|
|
|
@Lazy
|
|
def target(self):
|
|
# allow for having a separate object the view acts upon
|
|
return self.context
|
|
|
|
@Lazy
|
|
def viewAnnotations(self):
|
|
return self.request.annotations.setdefault('loops.view', {})
|
|
|
|
@Lazy
|
|
def node(self):
|
|
return self.viewAnnotations.get('node')
|
|
|
|
@Lazy
|
|
def nodeView(self):
|
|
ann = self.request.annotations.get('loops.view', {})
|
|
return self.viewAnnotations.get('nodeView')
|
|
|
|
@Lazy
|
|
def params(self):
|
|
result = {}
|
|
paramString = self.request.annotations.get('loops.view', {}).get('params')
|
|
if paramString:
|
|
result = parse_qs(paramString)
|
|
for k, v in result.items():
|
|
if len(v) == 1:
|
|
v = [x.strip() for x in v[0].split(',')]
|
|
result[k] = v
|
|
return result
|
|
|
|
def setSkin(self, skinName):
|
|
skin = None
|
|
if skinName and IView.providedBy(self.context):
|
|
skin = component.queryUtility(IBrowserSkinType, skinName)
|
|
if skin:
|
|
applySkin(self.request, skin)
|
|
self.skin = skin
|
|
|
|
@Lazy
|
|
def modifiedRaw(self):
|
|
d = getattr(self.adapted, 'modified', None)
|
|
if not d:
|
|
dc = IZopeDublinCore(self.context)
|
|
d = dc.modified or dc.created
|
|
if isinstance(d, str):
|
|
d = datetime(*(strptime(d, '%Y-%m-%dT%H:%M')[:6]))
|
|
else:
|
|
d = toLocalTime(d)
|
|
return d
|
|
|
|
@Lazy
|
|
def modified(self):
|
|
d = self.modifiedRaw
|
|
return d and d.strftime('%Y-%m-%d %H:%M') or ''
|
|
|
|
@Lazy
|
|
def creatorsRaw(self):
|
|
# TODO: use an IAuthorInfo (or similar) adapter
|
|
creators = getattr(self.adapted, 'authors', None) or []
|
|
if not creators:
|
|
cr = IZopeDublinCore(self.context).creators or []
|
|
pau = component.getUtility(IAuthentication)
|
|
for c in cr:
|
|
try:
|
|
principal = pau.getPrincipal(c)
|
|
if principal is None:
|
|
creators.append(c)
|
|
else:
|
|
creators.append(principal.title)
|
|
except PrincipalLookupError:
|
|
creators.append(c)
|
|
return creators
|
|
|
|
@Lazy
|
|
def creators(self):
|
|
return ', '.join(self.creatorsRaw)
|
|
|
|
@Lazy
|
|
def lastCreator(self):
|
|
return self.creatorsRaw and self.creatorsRaw[-1] or u''
|
|
|
|
@Lazy
|
|
def loopsRoot(self):
|
|
return self.context.getLoopsRoot()
|
|
|
|
@Lazy
|
|
def conceptManager(self):
|
|
return self.loopsRoot.getConceptManager()
|
|
|
|
@Lazy
|
|
def resourceManager(self):
|
|
return self.loopsRoot.getResourceManager()
|
|
|
|
@Lazy
|
|
def typePredicate(self):
|
|
return self.conceptManager.getTypePredicate()
|
|
|
|
@Lazy
|
|
def defaultPredicate(self):
|
|
return self.conceptManager.getDefaultPredicate()
|
|
|
|
@Lazy
|
|
def isPartOfPredicate(self):
|
|
return self.conceptManager.get('ispartof')
|
|
|
|
@Lazy
|
|
def queryTargetPredicate(self):
|
|
return self.conceptManager.get('querytarget')
|
|
|
|
@Lazy
|
|
def memberPredicate(self):
|
|
return self.conceptManager.get('ismember')
|
|
|
|
@Lazy
|
|
def masterPredicate(self):
|
|
return self.conceptManager.get('ismaster')
|
|
|
|
@Lazy
|
|
def ownerPredicate(self):
|
|
return self.conceptManager.get('isowner')
|
|
|
|
@Lazy
|
|
def personAssignmentPredicates(self):
|
|
return [self.memberPredicate, self.masterPredicate, self.ownerPredicate]
|
|
|
|
@Lazy
|
|
def url(self):
|
|
return absoluteURL(self.context, self.request)
|
|
|
|
@Lazy
|
|
def rootUrl(self):
|
|
return absoluteURL(self.loopsRoot, self.request)
|
|
|
|
@Lazy
|
|
def view(self):
|
|
return self
|
|
|
|
@Lazy
|
|
def token(self):
|
|
return self.loopsRoot.getLoopsUri(self.context)
|
|
|
|
@Lazy
|
|
def adapted(self):
|
|
return adapted(self.context, self.languageInfo)
|
|
|
|
@Lazy
|
|
def baseObject(self):
|
|
return baseObject(self.context)
|
|
|
|
@Lazy
|
|
def title(self):
|
|
return self.adapted.title or getName(self.context)
|
|
|
|
@Lazy
|
|
def description(self):
|
|
return self.adapted.description
|
|
|
|
@Lazy
|
|
def tabTitle(self):
|
|
return u'Info'
|
|
|
|
@Lazy
|
|
def additionalInfos(self):
|
|
return []
|
|
|
|
@Lazy
|
|
def dublincore(self):
|
|
zdc = IZopeDublinCore(self.context)
|
|
zdc.languageInfo = self.languageInfo
|
|
return zdc
|
|
|
|
@Lazy
|
|
def dcTitle(self):
|
|
return self.dublincore.title or self.title
|
|
|
|
@Lazy
|
|
def dcDescription(self):
|
|
return self.dublincore.description or u'' #self.description
|
|
|
|
@Lazy
|
|
def headTitle(self):
|
|
return self.dcTitle
|
|
|
|
@Lazy
|
|
def value(self):
|
|
return self.context
|
|
|
|
@Lazy
|
|
def uniqueId(self):
|
|
return util.getUidForObject(self.context)
|
|
|
|
@Lazy
|
|
def breadcrumbsTitle(self):
|
|
return self.title
|
|
|
|
@Lazy
|
|
def listingTitle(self):
|
|
return self.title
|
|
|
|
def getViewForObject(self, obj):
|
|
if obj is not None:
|
|
obj = baseObject(obj)
|
|
basicView = component.getMultiAdapter((obj, self.request))
|
|
if hasattr(basicView, 'view'):
|
|
return basicView.view
|
|
|
|
def viewIterator(self, objs):
|
|
request = self.request
|
|
for obj in objs:
|
|
view = self.getViewForObject(obj)
|
|
if view is None:
|
|
view = BaseView(obj, request)
|
|
yield view
|
|
|
|
def xx_viewIterator(self,obj):
|
|
view = component.queryMultiAdapter(
|
|
(o, request), name='index.html')
|
|
#if view is None:
|
|
# view = component.queryMultiAdapter((o, request), IBrowserView)
|
|
if view is None:
|
|
view = BaseView(o, request)
|
|
if hasattr(view, 'view'): # use view setting for type
|
|
view = view.view
|
|
yield view
|
|
|
|
# type stuff
|
|
|
|
@Lazy
|
|
def type(self):
|
|
return IType(self.baseObject)
|
|
|
|
@Lazy
|
|
def typeProvider(self):
|
|
return self.type.typeProvider
|
|
|
|
@Lazy
|
|
def typeInterface(self):
|
|
return self.type.typeInterface
|
|
|
|
@Lazy
|
|
def typeAdapter(self):
|
|
ifc = self.typeInterface
|
|
if ifc is not None:
|
|
return ifc(self.context)
|
|
|
|
@Lazy
|
|
def typeTitle(self):
|
|
return self.type.title
|
|
|
|
@Lazy
|
|
def longTypeTitle(self):
|
|
ct = getattr(self.context, 'contentType', None)
|
|
if ct:
|
|
ext = mimetypes.extensions.get(ct)
|
|
#ext = mimetypes.guess_extension(ct)
|
|
if ext:
|
|
#return '%s (%s)' % (t, ext.upper())
|
|
return ext.upper() #.lstrip('.')
|
|
return self.typeTitle
|
|
|
|
@Lazy
|
|
def typeUrl(self):
|
|
provider = self.typeProvider
|
|
if provider is not None:
|
|
return absoluteURL(provider, self.request)
|
|
return None
|
|
|
|
def renderText(self, text, contentType='text/restructured'):
|
|
text = util.toUnicode(text)
|
|
typeKey = util.renderingFactories.get(contentType, None)
|
|
if typeKey is None:
|
|
if contentType == u'text/html':
|
|
return text
|
|
return u'<pre>%s</pre>' % util.html_quote(text)
|
|
source = removeAllProxies(component.createObject(typeKey, text))
|
|
view = component.getMultiAdapter((source, self.request))
|
|
return view.render()
|
|
|
|
def renderDescription(self, text=None):
|
|
if text is None:
|
|
text = self.description
|
|
if text is None:
|
|
return u''
|
|
htmlPattern = re.compile(r'<(.+)>.+</\1>')
|
|
if '<br />' in text or htmlPattern.search(text):
|
|
return text
|
|
return self.renderText(text, 'text/restructured')
|
|
|
|
@Lazy
|
|
def renderedDescription(self):
|
|
return self.renderDescription()
|
|
|
|
def getObjectForUid(self, uid):
|
|
return util.getObjectForUid(uid)
|
|
|
|
def getUidForObject(self, obj):
|
|
return util.getUidForObject(baseObject(obj))
|
|
|
|
# type listings
|
|
|
|
def listTypes(self, include=None, exclude=None, sortOn='title'):
|
|
types = [dict(token=t.token, title=t.title)
|
|
for t in ITypeManager(self.context).listTypes(include, exclude)]
|
|
#if sortOn:
|
|
# types.sort(key=lambda x: x[sortOn])
|
|
return types
|
|
|
|
def getTypesVocabulary(self, include=None):
|
|
return util.KeywordVocabulary(self.listTypes(include, ('hidden',)))
|
|
|
|
def resourceTypes(self):
|
|
return util.KeywordVocabulary(self.listTypes(('resource',), ('hidden',)))
|
|
#if t.factory == Resource]) # ? if necessary -> type.qualifiers
|
|
|
|
def conceptTypes(self):
|
|
return util.KeywordVocabulary(self.listTypes(('concept',), ('hidden',)))
|
|
|
|
def parentTypesFromOtherSites(self):
|
|
result = []
|
|
typeNames = self.typeOptions('foreign_parent_types') or []
|
|
for path in self.typeOptions('foreign_parent_sites') or []:
|
|
site = traverse(self.loopsRoot, path, None)
|
|
if site is None:
|
|
continue
|
|
cm = site.getConceptManager()
|
|
for tname in typeNames:
|
|
t = cm.get(tname)
|
|
if t is not None:
|
|
type = LoopsTypeInfo(t)
|
|
type.isForeignReference = True
|
|
result.append(type)
|
|
return result
|
|
|
|
def listTypesForSearch(self, include=None, exclude=None, sortOn='title'):
|
|
types = [dict(token=t.tokenForSearch, title=t.title)
|
|
for t in ITypeManager(self.context).listTypes(include, exclude)]
|
|
if sortOn:
|
|
types.sort(key=lambda x: x[sortOn])
|
|
for t in self.parentTypesFromOtherSites():
|
|
types.append(dict(token=t.tokenForSearch, title=t.title))
|
|
return types
|
|
|
|
def typesForSearch(self):
|
|
general = [('loops:resource:*', 'Any Resource'),
|
|
('loops:concept:*', 'Any Concept'),]
|
|
return util.KeywordVocabulary(general
|
|
+ self.listTypesForSearch(exclude=('system', 'hidden',))
|
|
+ [('loops:*', 'Any')])
|
|
|
|
def conceptTypesForSearch(self):
|
|
general = [('loops:concept:*', 'Any Concept'),]
|
|
return util.KeywordVocabulary(general
|
|
+ self.listTypesForSearch(('concept',),
|
|
('hidden',),))
|
|
#('system', 'hidden',),))
|
|
|
|
def resourceTypesForSearch(self):
|
|
general = [('loops:resource:*', 'Any Resource'),]
|
|
return util.KeywordVocabulary(general
|
|
+ self.listTypesForSearch(('resource',),
|
|
('system', 'hidden',),))
|
|
|
|
def isPartOnlyResource(self, obj):
|
|
if not IResource.providedBy(obj):
|
|
return False
|
|
isPart = False
|
|
for r in obj.getConceptRelations():
|
|
if r.predicate == self.isPartOfPredicate:
|
|
isPart = True
|
|
elif r.predicate != self.typePredicate:
|
|
return False
|
|
return isPart
|
|
|
|
# options/settings
|
|
|
|
@Lazy
|
|
def options(self):
|
|
if ITypeConcept.providedBy(self.adapted):
|
|
return DummyOptions()
|
|
return component.queryAdapter(self.adapted, IOptions) or DummyOptions()
|
|
|
|
@Lazy
|
|
def typeOptions(self):
|
|
if self.typeProvider is None:
|
|
return DummyOptions()
|
|
if getattr(self.adapted, '__is_dummy__', None):
|
|
typeToken = getattr(self, 'typeToken', None)
|
|
if typeToken is not None:
|
|
typeProvider = self.loopsRoot.loopsTraverse(typeToken)
|
|
return IOptions(adapted(typeProvider))
|
|
return IOptions(adapted(self.typeProvider))
|
|
|
|
@Lazy
|
|
def globalOptions(self):
|
|
return IOptions(self.loopsRoot)
|
|
|
|
def getOptions(self, keys):
|
|
for opt in (self.options, self.typeOptions, self.globalOptions):
|
|
if isinstance(opt, DummyOptions):
|
|
continue
|
|
v = opt
|
|
for key in keys.split('.'):
|
|
if isinstance(v, list):
|
|
break
|
|
v = getattr(v, key)
|
|
if not isinstance(v, DummyOptions):
|
|
return v
|
|
|
|
def getPredicateOptions(self, relation):
|
|
return IOptions(adapted(relation.predicate), None) or DummyOptions()
|
|
|
|
# versioning
|
|
|
|
@Lazy
|
|
def versionable(self):
|
|
return IVersionable(self.target, None)
|
|
|
|
@Lazy
|
|
def useVersioning(self):
|
|
if self.globalOptions('useVersioning'):
|
|
return True
|
|
options = getattr(self.controller, 'options', None)
|
|
if options:
|
|
return 'useVersioning' in options.value
|
|
|
|
@Lazy
|
|
def showVersions(self):
|
|
permissions = self.globalOptions('showVersionsPermissions')
|
|
if permissions:
|
|
for p in permissions:
|
|
if checkPermission(p, self.target):
|
|
return True
|
|
else:
|
|
return False
|
|
return True
|
|
|
|
@Lazy
|
|
def versionLevels(self):
|
|
if self.versionable is not None:
|
|
return reversed([dict(token=idx, label=label)
|
|
for idx, label in enumerate(self.versionable.versionLevels)])
|
|
return []
|
|
|
|
@Lazy
|
|
def versionId(self):
|
|
versionable = IVersionable(self.target, None)
|
|
return versionable and versionable.versionId or ''
|
|
|
|
@Lazy
|
|
def currentVersionId(self):
|
|
versionable = IVersionable(self.target, None)
|
|
return versionable and versionable.currentVersion.versionId or ''
|
|
|
|
@Lazy
|
|
def hasVersions(self):
|
|
versionable = IVersionable(self.target, None)
|
|
return versionable and len(versionable.versions) > 1 or False
|
|
|
|
@Lazy
|
|
def versionInfo(self):
|
|
if not self.useVersioning:
|
|
return None
|
|
target = self.target
|
|
if not IResource.providedBy(target):
|
|
# no standard versioning yet for concepts
|
|
return None
|
|
versionable = IVersionable(target, None)
|
|
if versionable is None:
|
|
return ''
|
|
versionId = versionable.versionId
|
|
td = component.getUtility(ITranslationDomain, _._domain)
|
|
current = ((versionable.currentVersion == target)
|
|
and td.translate(_(u'current'), context=self.request)
|
|
or u'')
|
|
released = ((versionable.releasedVersion == target)
|
|
and td.translate(_(u'released'), context=self.request)
|
|
or u'')
|
|
if not current and not released:
|
|
return versionId
|
|
addInfo = u', '.join(e for e in (current, released) if e)
|
|
return u'%s (%s)' % (versionId, addInfo)
|
|
|
|
# states
|
|
|
|
@Lazy
|
|
def viewStatesPermission(self):
|
|
opt = self.globalOptions('organize.show_states')
|
|
return opt and opt[0] or 'zope.ManageContent'
|
|
|
|
@Lazy
|
|
def states(self):
|
|
return self.getStates()
|
|
|
|
@Lazy
|
|
def allStates(self):
|
|
return self.getStates(False)
|
|
|
|
def getStates(self, forDisplay=True):
|
|
result = []
|
|
if forDisplay and not checkPermission(self.viewStatesPermission, self.context):
|
|
# do not display state information
|
|
return result
|
|
if IResource.providedBy(self.target):
|
|
statesDefs = (self.globalOptions('organize.stateful.resource') or [])
|
|
else:
|
|
statesDefs = (self.globalOptions('organize.stateful.concept') or [])
|
|
statesDefs += (self.typeOptions('organize.stateful') or [])
|
|
for std in statesDefs:
|
|
stf = component.getAdapter(self.target, IStateful, name=std)
|
|
result.append(stf)
|
|
return result
|
|
|
|
def checkState(self):
|
|
if checkPermission('loops.ManageSite', self.context):
|
|
return True
|
|
if not self.allStates:
|
|
return True
|
|
for stf in self.allStates:
|
|
option = self.globalOptions(
|
|
'organize.stateful.restrict.' + stf.statesDefinition)
|
|
if option:
|
|
return stf.state in option
|
|
return True
|
|
|
|
# controlling actions and editing
|
|
|
|
@Lazy
|
|
def editable(self):
|
|
return canWriteObject(self.context)
|
|
|
|
def getActions(self, category='object', page=None, target=None):
|
|
""" Return a list of actions that provide the view and edit actions
|
|
available for the context object.
|
|
"""
|
|
acts = []
|
|
optKey = 'action.' + category
|
|
actNames = (self.options(optKey) or []) + (self.typeOptions(optKey) or [])
|
|
if actNames:
|
|
acts = list(actions.get(category, actNames,
|
|
view=self, page=page, target=target))
|
|
if category in self.actions:
|
|
acts.extend(self.actions[category](self, page, target))
|
|
optKey = 'append_action.' + category
|
|
actNames = (self.options(optKey) or []) + (self.typeOptions(optKey) or [])
|
|
if actNames:
|
|
acts.extend(list(actions.get(category, actNames,
|
|
view=self, page=page, target=target)))
|
|
return acts
|
|
|
|
|
|
def getAdditionalActions(self, category='object', page=None, target=None):
|
|
""" Provide additional actions; override by subclass.
|
|
"""
|
|
return []
|
|
|
|
def getAllowedActions(self, category='object', page=None, target=None):
|
|
result = []
|
|
for act in self.getActions(category, page=page, target=target):
|
|
if act.permission is not None:
|
|
ctx = (target is not None and target.context) or self.context
|
|
if not checkPermission(act.permission, ctx):
|
|
continue
|
|
result.append(act)
|
|
return result
|
|
|
|
@Lazy
|
|
def showObjectActions(self):
|
|
principal = self.request.principal
|
|
if IUnauthenticatedPrincipal.providedBy(principal):
|
|
return False
|
|
perms = self.globalOptions('action.object.permissions')
|
|
if perms:
|
|
for p in perms:
|
|
if checkPermission(p, self.context):
|
|
return True
|
|
return False
|
|
return True
|
|
|
|
def checkAction(self, name, category, target):
|
|
if name in ('create_resource',):
|
|
if target is not None and target.options.showCreateResource:
|
|
return True
|
|
return not self.globalOptions('hideCreateResource')
|
|
return True
|
|
|
|
@Lazy
|
|
def canAccessRestricted(self):
|
|
return checkPermission('loops.ViewRestricted', self.context)
|
|
|
|
@Lazy
|
|
def canEditRestricted(self):
|
|
return canEditRestricted(self.context)
|
|
|
|
def openEditWindow(self, viewName='edit.html'):
|
|
if self.editable:
|
|
if checkPermission('loops.ManageSite', self.context):
|
|
return "openEditWindow('%s/@@%s')" % (self.url, viewName)
|
|
return ''
|
|
|
|
@Lazy
|
|
def xeditable(self):
|
|
if self.typeOptions('no_external_edit'):
|
|
return False
|
|
ct = getattr(self.context, 'contentType', '')
|
|
if not ct or ct in ('application/pdf', 'application/x-pdf'):
|
|
return False
|
|
if ct.startswith('text/') and ct != 'text/rtf':
|
|
return checkPermission('loops.ManageSite', self.context)
|
|
return canWriteObject(self.context)
|
|
|
|
@Lazy
|
|
def inlineEditingActive(self):
|
|
# this may depend on system and user settings...
|
|
return True
|
|
|
|
@Lazy
|
|
def conceptMapEditorUrl(self):
|
|
return (checkPermission('loops.xmlrpc.ManageConcepts', self.context)
|
|
and self.rootUrl + '/swf.html'
|
|
or None)
|
|
|
|
inlineEditable = False
|
|
|
|
# work items
|
|
@Lazy
|
|
def workItems(self):
|
|
return []
|
|
|
|
# comments
|
|
|
|
@Lazy
|
|
def comments(self):
|
|
return []
|
|
|
|
# dojo stuff
|
|
|
|
def inlineEdit(self, id):
|
|
self.registerDojo()
|
|
return 'return inlineEdit("%s", "")' % id
|
|
|
|
def registerDojo(self):
|
|
if self.controller is None:
|
|
return
|
|
cm = self.controller.macros
|
|
cm.register('js', 'dojo.js', template=dojoMacroTemplate, name='main',
|
|
position=0,
|
|
djConfig='parseOnLoad: true, usePlainJson: true, '
|
|
#'isDebug: true, '
|
|
'locale: "%s"' % self.languageInfo.language)
|
|
jsCall = ('dojo.require("dojo.parser"); ')
|
|
#'dojo.registerModulePath("jocy", "/@@/cybertools.jocy"); '
|
|
#'dojo.require("jocy.data");')
|
|
cm.register('js-execute', 'dojo_registration', jsCall=jsCall)
|
|
cm.register('css', identifier='Lightbox.css', position=0,
|
|
resourceName='ajax.dojo/dojox/image/resources/Lightbox.css',
|
|
media='all')
|
|
cm.register('css', identifier='tundra.css', position=0,
|
|
resourceName='ajax.dojo/dijit/themes/tundra/tundra.css',
|
|
media='all')
|
|
#cm.register('css', identifier='dojo.css', position=1,
|
|
# resourceName='ajax.dojo/dojo/resources/dojo.css', media='all')
|
|
|
|
def registerDojoDnd(self):
|
|
if self.controller is None:
|
|
return
|
|
self.registerDojo()
|
|
jsCall = 'dojo.require("dojo.dnd.Source")'
|
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
|
|
|
def registerDojoDialog(self):
|
|
self.registerDojo()
|
|
jsCall = 'dojo.require("dijit.Dialog")'
|
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
|
|
|
def registerDojoTooltipDialog(self):
|
|
self.registerDojo()
|
|
jsCall = ('dojo.require("dijit.Dialog");'
|
|
'dojo.require("dijit.form.Button");')
|
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
|
|
|
def registerDojoDateWidget(self):
|
|
self.registerDojo()
|
|
jsCall = ('dojo.require("dijit.form.DateTextBox"); '
|
|
'dojo.require("dijit.form.TimeTextBox");')
|
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
|
|
|
def registerDojoTextWidget(self):
|
|
self.registerDojo()
|
|
jsCall = 'dojo.require("dijit.form.ValidationTextBox");'
|
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
|
|
|
def registerDojoTextarea(self):
|
|
self.registerDojo()
|
|
jsCall = 'dojo.require("dijit.form.SimpleTextarea");'
|
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
|
|
|
def registerDojoNumberWidget(self):
|
|
self.registerDojo()
|
|
jsCall = 'dojo.require("dijit.form.NumberTextBox");'
|
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
|
|
|
def registerDojoEditor(self):
|
|
self.registerDojo()
|
|
jsCall = 'dojo.require("dijit.Editor");'
|
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
|
jsCall = 'dojo.require("dijit._editor.plugins.LinkDialog");'
|
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
|
jsCall = 'dojo.require("dijit._editor.plugins.ViewSource")'
|
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
|
|
|
def registerDojoLightbox(self):
|
|
self.registerDojo()
|
|
jsCall = 'dojo.require("dojox.image.Lightbox");'
|
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
|
|
|
def registerDojoComboBox(self):
|
|
self.registerDojo()
|
|
jsCall = ('dojo.require("dijit.form.ComboBox");')
|
|
self.controller.macros.register('js-execute',
|
|
'dojo.require.ComboBox', jsCall=jsCall)
|
|
|
|
def registerDojoFormAll(self):
|
|
self.registerDojo()
|
|
self.registerDojoEditor()
|
|
cm = self.controller.macros
|
|
jsCall = ('dojo.require("dijit.form.Form"); '
|
|
'dojo.require("dijit.form.DateTextBox"); '
|
|
'dojo.require("dijit.form.TimeTextBox"); '
|
|
'dojo.require("dijit.form.SimpleTextarea"); '
|
|
'dojo.require("dijit.form.FilteringSelect"); '
|
|
'dojo.require("dijit.layout.BorderContainer"); '
|
|
'dojo.require("dijit.layout.ContentPane"); '
|
|
'dojo.require("dojox.data.QueryReadStore"); ')
|
|
cm.register('js-execute', 'dojo.form.all', jsCall=jsCall)
|
|
|
|
def registerDojoFormAllGrid(self):
|
|
self.registerDojoFormAll()
|
|
cm = self.controller.macros
|
|
jsCall = ('dojo.require("dijit.layout.TabContainer"); '
|
|
'dojo.require("dojox.grid.DataGrid"); '
|
|
'dojo.require("dojo.data.ItemFileWriteStore"); ')
|
|
cm.register('js-execute', 'dojo.form.grid', jsCall=jsCall)
|
|
cm.register('css', identifier='dojox.grid.css', position=0,
|
|
resourceName='ajax.dojo/dojox/grid/resources/Grid.css', media='all')
|
|
cm.register('css', identifier='dojox.grid_tundra.css', position=0,
|
|
resourceName='ajax.dojo/dojox/grid/resources/tundraGrid.css',
|
|
media='all')
|
|
|
|
|
|
class LoggedIn(object):
|
|
|
|
messages = dict(success=_(u'You have been logged in.'),
|
|
nosuccess=_(u'Login not successful.'),
|
|
error=_(u'Try again later.'))
|
|
|
|
def __call__(self):
|
|
code = 'success'
|
|
if IUnauthenticatedPrincipal.providedBy(self.request.principal):
|
|
code = 'nosuccess'
|
|
info = self.request.form.get('message')
|
|
if info == 'error' and code == 'nosuccess':
|
|
code = 'error'
|
|
message = self.messages[code]
|
|
return self.request.response.redirect(self.nextUrl(message, code))
|
|
|
|
def nextUrl(self, message, code):
|
|
camefrom = self.request.form.get('camefrom', '').strip('?')
|
|
url = camefrom or self.request.URL[-1]
|
|
params = []
|
|
if '?' in url:
|
|
url, qs = url.split('?', 1)
|
|
params = parse_qsl(qs)
|
|
params = [(k, v) for k, v in params if k != 'loops.messages.top:record']
|
|
params.append(('loops.messages.top:record', message.encode('UTF-8')))
|
|
url = url.encode('utf-8')
|
|
return '%s?%s' % (url, urlencode(params))
|
|
|
|
# vocabulary stuff
|
|
|
|
class SimpleTerms(object):
|
|
""" Provide the ITerms interface, e.g. for usage in selection
|
|
lists.
|
|
"""
|
|
|
|
implements(ITerms)
|
|
|
|
def __init__(self, source, request):
|
|
# the source parameter is a list of tuples (token, title).
|
|
self.source = source
|
|
self.terms = dict(source)
|
|
|
|
def getTerm(self, value):
|
|
token = value[0]
|
|
title = len(value) > 1 and value[1] or token
|
|
return SimpleTerm(token, token, title)
|
|
|
|
def getValue(self, token):
|
|
return (token, self.terms[token])
|
|
|
|
|
|
class LoopsTerms(object):
|
|
""" Provide the ITerms interface, e.g. for usage in selection
|
|
lists.
|
|
"""
|
|
|
|
implements(ITerms)
|
|
|
|
def __init__(self, source, request):
|
|
# the source parameter is a view or adapter of a real context object:
|
|
self.source = source
|
|
self.context = source.context
|
|
self.request = request
|
|
|
|
@Lazy
|
|
def loopsRoot(self):
|
|
return self.context.getLoopsRoot()
|
|
|
|
def getTerm(self, value):
|
|
#if value is None:
|
|
# return SimpleTerm(None, '', u'not assigned')
|
|
title = value.title or getName(value)
|
|
token = self.loopsRoot.getLoopsUri(value)
|
|
return SimpleTerm(value, token, title)
|
|
|
|
def getValue(self, token):
|
|
return self.loopsRoot.loopsTraverse(token)
|
|
|
|
|
|
class InterfaceTerms(object):
|
|
""" Provide the ITerms interface for source list of interfaces.
|
|
"""
|
|
|
|
implements(ITerms)
|
|
|
|
def __init__(self, source, request):
|
|
self.source = source
|
|
self.request = request
|
|
|
|
def getTerm(self, value):
|
|
token = '.'.join((value.__module__, value.__name__))
|
|
return SimpleTerm(value, token, token)
|
|
|
|
def getValue(self, token):
|
|
return resolve(token)
|
|
|
|
|