2009-06-29 07:06:01 -05:00
|
|
|
# ------------------------------------------------------------------------------
|
2011-12-05 11:15:45 -06:00
|
|
|
import os, os.path, sys, re, time, random, types, base64, urllib
|
[gen] Added param Search.default allowing to define a default Search. The default search, if present, will be triggered when clicking on the main link for a class, instead of the query that collects all instances of this class; appy.gen.Type: removed 3 obsolete params: 'index', 'editDefault' and 'optional'. For achieving the same result than using 'editDefault', one may define 'by hand' an attribute on the Tool for storing the editable default value, and define, on the appropriate field in param 'default', a method that returns the value of the tool attribute; Added Type.defaultForSearch, allowing, for some sub-types, to define a default value when displaying the corresponding widget on the search screen; added a default 'state' field allowing to include workflow state among search criteria in the search screens; removed obsolete test applications.
2012-10-31 07:20:25 -05:00
|
|
|
from appy import Object
|
2010-09-02 09:16:08 -05:00
|
|
|
import appy.gen
|
2012-11-14 04:36:48 -06:00
|
|
|
from appy.gen import Type, Search, Selection, String, Page
|
2012-11-14 10:40:52 -06:00
|
|
|
from appy.gen.utils import SomeObjects, getClassName, GroupDescr, SearchDescr
|
2011-12-05 08:11:29 -06:00
|
|
|
from appy.gen.mixins import BaseMixin
|
|
|
|
from appy.gen.wrappers import AbstractWrapper
|
|
|
|
from appy.gen.descriptors import ClassDescriptor
|
2012-07-09 08:47:38 -05:00
|
|
|
from appy.gen.mail import sendMail
|
[gen] Added param Search.default allowing to define a default Search. The default search, if present, will be triggered when clicking on the main link for a class, instead of the query that collects all instances of this class; appy.gen.Type: removed 3 obsolete params: 'index', 'editDefault' and 'optional'. For achieving the same result than using 'editDefault', one may define 'by hand' an attribute on the Tool for storing the editable default value, and define, on the appropriate field in param 'default', a method that returns the value of the tool attribute; Added Type.defaultForSearch, allowing, for some sub-types, to define a default value when displaying the corresponding widget on the search screen; added a default 'state' field allowing to include workflow state among search criteria in the search screens; removed obsolete test applications.
2012-10-31 07:20:25 -05:00
|
|
|
from appy.shared import mimeTypes
|
|
|
|
from appy.shared.utils import getOsTempFolder, sequenceTypes
|
|
|
|
from appy.shared.data import languages
|
2011-11-25 11:01:20 -06:00
|
|
|
try:
|
|
|
|
from AccessControl.ZopeSecurityPolicy import _noroles
|
|
|
|
except ImportError:
|
|
|
|
_noroles = []
|
2010-08-05 11:23:17 -05:00
|
|
|
|
2010-10-14 07:43:56 -05:00
|
|
|
# Errors -----------------------------------------------------------------------
|
2012-10-08 03:08:54 -05:00
|
|
|
jsMessages = ('no_elem_selected', 'delete_confirm', 'unlink_confirm')
|
2009-06-29 07:06:01 -05:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
2010-10-14 07:43:56 -05:00
|
|
|
class ToolMixin(BaseMixin):
|
2010-08-05 11:23:17 -05:00
|
|
|
_appy_meta_type = 'Tool'
|
2010-09-02 09:16:08 -05:00
|
|
|
def getPortalType(self, metaTypeOrAppyClass):
|
|
|
|
'''Returns the name of the portal_type that is based on
|
2010-10-14 07:43:56 -05:00
|
|
|
p_metaTypeOrAppyType.'''
|
2010-09-02 09:16:08 -05:00
|
|
|
appName = self.getProductConfig().PROJECTNAME
|
2011-02-01 04:09:54 -06:00
|
|
|
res = metaTypeOrAppyClass
|
2010-09-02 09:16:08 -05:00
|
|
|
if not isinstance(metaTypeOrAppyClass, basestring):
|
|
|
|
res = getClassName(metaTypeOrAppyClass, appName)
|
2011-12-05 03:52:18 -06:00
|
|
|
if res.find('_wrappers') != -1:
|
2010-09-02 09:16:08 -05:00
|
|
|
elems = res.split('_')
|
|
|
|
res = '%s%s' % (elems[1], elems[4])
|
2011-11-28 15:50:01 -06:00
|
|
|
if res in ('User', 'Group', 'Translation'): res = appName + res
|
2010-09-02 09:16:08 -05:00
|
|
|
return res
|
|
|
|
|
2012-02-18 12:48:00 -06:00
|
|
|
def getHomePage(self):
|
|
|
|
'''Return the home page when a user hits the app.'''
|
|
|
|
# If the app defines a method "getHomePage", call it.
|
|
|
|
appyTool = self.appy()
|
|
|
|
try:
|
|
|
|
url = appyTool.getHomePage()
|
|
|
|
except AttributeError:
|
|
|
|
# Bring Managers to the config, lead others to home.pt.
|
|
|
|
user = self.getUser()
|
|
|
|
if user.has_role('Manager'):
|
|
|
|
url = self.goto(self.absolute_url())
|
|
|
|
else:
|
|
|
|
url = self.goto(self.getApp().ui.home.absolute_url())
|
|
|
|
return url
|
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def getCatalog(self):
|
|
|
|
'''Returns the catalog object.'''
|
|
|
|
return self.getParentNode().catalog
|
|
|
|
|
2011-09-18 08:00:05 -05:00
|
|
|
def getApp(self):
|
2011-11-25 11:01:20 -06:00
|
|
|
'''Returns the root Zope object.'''
|
|
|
|
return self.getPhysicalRoot()
|
2011-09-18 08:00:05 -05:00
|
|
|
|
2010-11-22 02:36:14 -06:00
|
|
|
def getSiteUrl(self):
|
|
|
|
'''Returns the absolute URL of this site.'''
|
2011-11-25 11:01:20 -06:00
|
|
|
return self.getApp().absolute_url()
|
2010-11-22 02:36:14 -06:00
|
|
|
|
2011-02-16 06:43:58 -06:00
|
|
|
def getPodInfo(self, obj, name):
|
|
|
|
'''Gets the available POD formats for Pod field named p_name on
|
|
|
|
p_obj.'''
|
|
|
|
podField = self.getAppyType(name, className=obj.meta_type)
|
|
|
|
return podField.getToolInfo(obj.appy())
|
|
|
|
|
2010-10-14 07:43:56 -05:00
|
|
|
def generateDocument(self):
|
|
|
|
'''Generates the document from field-related info. UID of object that
|
|
|
|
is the template target is given in the request.'''
|
|
|
|
rq = self.REQUEST
|
2011-02-16 06:43:58 -06:00
|
|
|
# Get the object on which a document must be generated.
|
|
|
|
obj = self.getObject(rq.get('objectUid'), appy=True)
|
2010-10-14 07:43:56 -05:00
|
|
|
fieldName = rq.get('fieldName')
|
2011-02-16 06:43:58 -06:00
|
|
|
res = getattr(obj, fieldName)
|
|
|
|
if isinstance(res, basestring):
|
|
|
|
# An error has occurred, and p_res contains the error message
|
|
|
|
obj.say(res)
|
|
|
|
return self.goto(rq.get('HTTP_REFERER'))
|
|
|
|
# res contains a FileWrapper instance.
|
|
|
|
response = rq.RESPONSE
|
|
|
|
response.setHeader('Content-Type', res.mimeType)
|
|
|
|
response.setHeader('Content-Disposition',
|
|
|
|
'inline;filename="%s"' % res.name)
|
|
|
|
return res.content
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2010-10-14 07:43:56 -05:00
|
|
|
def getAttr(self, name):
|
2011-11-25 11:01:20 -06:00
|
|
|
'''Gets attribute named p_name.'''
|
2010-10-14 07:43:56 -05:00
|
|
|
return getattr(self.appy(), name, None)
|
|
|
|
|
2009-10-25 15:42:08 -05:00
|
|
|
def getAppName(self):
|
2011-11-25 11:01:20 -06:00
|
|
|
'''Returns the name of the application.'''
|
2009-10-25 15:42:08 -05:00
|
|
|
return self.getProductConfig().PROJECTNAME
|
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def getPath(self, path):
|
|
|
|
'''Returns the folder or object whose absolute path p_path.'''
|
|
|
|
res = self.getPhysicalRoot()
|
|
|
|
if path == '/': return res
|
|
|
|
path = path[1:]
|
|
|
|
if '/' not in path: return res._getOb(path) # For performance
|
|
|
|
for elem in path.split('/'): res = res._getOb(elem)
|
|
|
|
return res
|
|
|
|
|
2012-02-21 05:09:42 -06:00
|
|
|
def showLanguageSelector(self):
|
|
|
|
'''We must show the language selector if the app config requires it and
|
|
|
|
it there is more than 2 supported languages. Moreover, on some pages,
|
|
|
|
switching the language is not allowed.'''
|
|
|
|
cfg = self.getProductConfig()
|
|
|
|
if not cfg.languageSelector: return
|
|
|
|
if len(cfg.languages) < 2: return
|
|
|
|
page = self.REQUEST.get('ACTUAL_URL').split('/')[-1]
|
2012-06-27 06:27:24 -05:00
|
|
|
return page not in ('edit', 'query', 'search', 'do')
|
2012-02-21 05:09:42 -06:00
|
|
|
|
2012-07-27 04:01:35 -05:00
|
|
|
def showForgotPassword(self):
|
|
|
|
'''We must show link "forgot password?" when the app requires it.'''
|
|
|
|
return self.getProductConfig().activateForgotPassword
|
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def getLanguages(self):
|
|
|
|
'''Returns the supported languages. First one is the default.'''
|
|
|
|
return self.getProductConfig().languages
|
|
|
|
|
|
|
|
def getLanguageName(self, code):
|
|
|
|
'''Gets the language name (in this language) from a 2-chars language
|
|
|
|
p_code.'''
|
|
|
|
return languages.get(code)[2]
|
|
|
|
|
2012-05-29 13:50:18 -05:00
|
|
|
def changeLanguage(self):
|
|
|
|
'''Sets the language cookie with the new desired language code that is
|
|
|
|
in request["language"].'''
|
|
|
|
rq = self.REQUEST
|
|
|
|
rq.RESPONSE.setCookie('_ZopeLg', rq['language'], path='/')
|
|
|
|
return self.goto(rq['HTTP_REFERER'])
|
|
|
|
|
2012-06-27 06:27:24 -05:00
|
|
|
def flipLanguageDirection(self, align, dir):
|
|
|
|
'''According to language direction p_dir ('ltr' or 'rtl'), this method
|
|
|
|
turns p_align from 'left' to 'right' (or the inverse) when
|
|
|
|
required.'''
|
|
|
|
if dir == 'ltr': return align
|
|
|
|
if align == 'left': return 'right'
|
|
|
|
if align == 'right': return 'left'
|
|
|
|
return align
|
|
|
|
|
2012-03-01 10:35:23 -06:00
|
|
|
def getGlobalCssJs(self):
|
2012-02-21 05:09:42 -06:00
|
|
|
'''Returns the list of CSS and JS files to include in the main template.
|
|
|
|
The method ensures that appy.css and appy.js come first.'''
|
|
|
|
names = self.getPhysicalRoot().ui.objectIds('File')
|
|
|
|
names.remove('appy.js'); names.insert(0, 'appy.js')
|
2012-06-27 06:27:24 -05:00
|
|
|
names.remove('appyrtl.css'); names.insert(0, 'appyrtl.css')
|
2012-02-21 05:09:42 -06:00
|
|
|
names.remove('appy.css'); names.insert(0, 'appy.css')
|
|
|
|
return names
|
|
|
|
|
2011-12-05 11:15:45 -06:00
|
|
|
def consumeMessages(self):
|
|
|
|
'''Returns the list of messages to show to a web page and clean it in
|
|
|
|
the session.'''
|
|
|
|
rq = self.REQUEST
|
|
|
|
res = rq.SESSION.get('messages', '')
|
|
|
|
if res:
|
|
|
|
del rq.SESSION['messages']
|
|
|
|
res = ' '.join([m[1] for m in res])
|
2011-11-25 11:01:20 -06:00
|
|
|
return res
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2009-10-18 07:52:27 -05:00
|
|
|
def getRootClasses(self):
|
|
|
|
'''Returns the list of root classes for this application.'''
|
|
|
|
return self.getProductConfig().rootClasses
|
|
|
|
|
2010-10-14 07:43:56 -05:00
|
|
|
def _appy_getAllFields(self, contentType):
|
|
|
|
'''Returns the (translated) names of fields of p_contentType.'''
|
|
|
|
res = []
|
2011-09-14 14:01:58 -05:00
|
|
|
for appyType in self.getAllAppyTypes(className=contentType):
|
|
|
|
res.append((appyType.name, self.translate(appyType.labelId)))
|
2010-10-14 07:43:56 -05:00
|
|
|
# Add object state
|
2011-09-20 12:21:48 -05:00
|
|
|
res.append(('state', self.translate('workflow_state')))
|
2010-10-14 07:43:56 -05:00
|
|
|
return res
|
|
|
|
|
|
|
|
def _appy_getSearchableFields(self, contentType):
|
|
|
|
'''Returns the (translated) names of fields that may be searched on
|
|
|
|
objects of type p_contentType (=indexed fields).'''
|
|
|
|
res = []
|
2011-09-14 14:01:58 -05:00
|
|
|
for appyType in self.getAllAppyTypes(className=contentType):
|
2010-10-14 07:43:56 -05:00
|
|
|
if appyType.indexed:
|
|
|
|
res.append((appyType.name, self.translate(appyType.labelId)))
|
|
|
|
return res
|
|
|
|
|
2010-12-06 04:11:40 -06:00
|
|
|
def getSearchInfo(self, contentType, refInfo=None):
|
2010-12-17 07:46:55 -06:00
|
|
|
'''Returns, as a dict:
|
|
|
|
- the list of searchable fields (= some fields among all indexed
|
|
|
|
fields);
|
|
|
|
- the number of columns for layouting those fields.'''
|
2010-12-06 04:11:40 -06:00
|
|
|
fields = []
|
2010-12-17 07:46:55 -06:00
|
|
|
fieldDicts = []
|
2010-12-06 04:11:40 -06:00
|
|
|
if refInfo:
|
|
|
|
# The search is triggered from a Ref field.
|
2011-09-20 12:21:48 -05:00
|
|
|
refObject, fieldName = self.getRefInfo(refInfo)
|
|
|
|
refField = refObject.getAppyType(fieldName)
|
2010-12-06 04:11:40 -06:00
|
|
|
fieldNames = refField.queryFields or ()
|
|
|
|
nbOfColumns = refField.queryNbCols
|
|
|
|
else:
|
|
|
|
# The search is triggered from an app-wide search.
|
|
|
|
at = self.appy()
|
|
|
|
fieldNames = getattr(at, 'searchFieldsFor%s' % contentType,())
|
|
|
|
nbOfColumns = getattr(at, 'numberOfSearchColumnsFor%s' %contentType)
|
2010-10-14 07:43:56 -05:00
|
|
|
for name in fieldNames:
|
2010-12-17 07:46:55 -06:00
|
|
|
appyType = self.getAppyType(name,asDict=False,className=contentType)
|
|
|
|
appyDict = self.getAppyType(name, asDict=True,className=contentType)
|
2010-12-06 04:11:40 -06:00
|
|
|
fields.append(appyType)
|
2010-12-17 07:46:55 -06:00
|
|
|
fieldDicts.append(appyDict)
|
|
|
|
return {'fields': fields, 'nbOfColumns': nbOfColumns,
|
|
|
|
'fieldDicts': fieldDicts}
|
2010-10-14 07:43:56 -05:00
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
queryParamNames = ('className', 'search', 'sortKey', 'sortOrder',
|
2011-01-28 07:36:30 -06:00
|
|
|
'filterKey', 'filterValue')
|
|
|
|
def getQueryInfo(self):
|
|
|
|
'''If we are showing search results, this method encodes in a string all
|
|
|
|
the params in the request that are required for re-triggering the
|
|
|
|
search.'''
|
|
|
|
rq = self.REQUEST
|
|
|
|
res = ''
|
|
|
|
if rq.has_key('search'):
|
|
|
|
res = ';'.join([rq.get(key,'').replace(';','') \
|
|
|
|
for key in self.queryParamNames])
|
|
|
|
return res
|
|
|
|
|
2010-10-14 07:43:56 -05:00
|
|
|
def getImportElements(self, contentType):
|
|
|
|
'''Returns the list of elements that can be imported from p_path for
|
|
|
|
p_contentType.'''
|
|
|
|
appyClass = self.getAppyClass(contentType)
|
|
|
|
importParams = self.getCreateMeans(appyClass)['import']
|
|
|
|
onElement = importParams['onElement'].__get__('')
|
|
|
|
sortMethod = importParams['sort']
|
|
|
|
if sortMethod: sortMethod = sortMethod.__get__('')
|
|
|
|
elems = []
|
2010-11-10 08:15:00 -06:00
|
|
|
importType = self.getAppyType('importPathFor%s' % contentType)
|
|
|
|
importPath = importType.getValue(self)
|
2010-10-14 07:43:56 -05:00
|
|
|
for elem in os.listdir(importPath):
|
|
|
|
elemFullPath = os.path.join(importPath, elem)
|
|
|
|
elemInfo = onElement(elemFullPath)
|
|
|
|
if elemInfo:
|
|
|
|
elemInfo.insert(0, elemFullPath) # To the result, I add the full
|
|
|
|
# path of the elem, which will not be shown.
|
|
|
|
elems.append(elemInfo)
|
|
|
|
if sortMethod:
|
|
|
|
elems = sortMethod(elems)
|
|
|
|
return [importParams['headers'], elems]
|
|
|
|
|
2012-11-29 13:45:21 -06:00
|
|
|
def showPortlet(self, context, layoutType):
|
|
|
|
'''When must the portlet be shown?'''
|
|
|
|
# Not on 'edit' pages.
|
|
|
|
if layoutType == 'edit': return
|
2011-11-25 11:01:20 -06:00
|
|
|
if context.id == 'ui': context = context.getParentNode()
|
2010-08-12 04:56:42 -05:00
|
|
|
res = True
|
2012-03-26 12:09:45 -05:00
|
|
|
if hasattr(context.aq_base, 'appy'):
|
|
|
|
appyObj = context.appy()
|
|
|
|
try:
|
|
|
|
res = appyObj.showPortlet()
|
|
|
|
except AttributeError:
|
|
|
|
res = True
|
2012-05-29 13:50:18 -05:00
|
|
|
else:
|
|
|
|
appyObj = self.appy()
|
|
|
|
try:
|
|
|
|
res = appyObj.showPortletAt(context)
|
|
|
|
except AttributeError:
|
|
|
|
res = True
|
2010-08-12 04:56:42 -05:00
|
|
|
return res
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def getObject(self, uid, appy=False, brain=False):
|
2009-11-17 03:05:19 -06:00
|
|
|
'''Allows to retrieve an object from its p_uid.'''
|
2011-11-25 11:01:20 -06:00
|
|
|
res = self.getPhysicalRoot().catalog(UID=uid)
|
|
|
|
if not res: return
|
|
|
|
res = res[0]
|
|
|
|
if brain: return res
|
2012-02-09 09:36:50 -06:00
|
|
|
res = res._unrestrictedGetObject()
|
2011-11-25 11:01:20 -06:00
|
|
|
if not appy: return res
|
|
|
|
return res.appy()
|
|
|
|
|
2011-11-28 15:50:01 -06:00
|
|
|
def getAllowedValue(self):
|
|
|
|
'''Gets, for the currently logged user, the value for index
|
|
|
|
"Allowed".'''
|
|
|
|
user = self.getUser()
|
|
|
|
res = ['user:%s' % user.getId(), 'Anonymous'] + user.getRoles()
|
|
|
|
try:
|
|
|
|
res += ['user:%s' % g for g in user.groups.keys()]
|
|
|
|
except AttributeError, ae:
|
|
|
|
pass # The Zope admin does not have this attribute.
|
|
|
|
return res
|
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def executeQuery(self, className, searchName=None, startNumber=0,
|
2010-10-14 07:43:56 -05:00
|
|
|
search=None, remember=False, brainsOnly=False,
|
|
|
|
maxResults=None, noSecurity=False, sortBy=None,
|
2010-12-06 04:11:40 -06:00
|
|
|
sortOrder='asc', filterKey=None, filterValue=None,
|
2011-09-20 12:21:48 -05:00
|
|
|
refObject=None, refField=None):
|
2012-11-14 10:40:52 -06:00
|
|
|
'''Executes a query on instances of a given p_className in the catalog.
|
|
|
|
If p_searchName is specified, it corresponds to:
|
2011-11-25 11:01:20 -06:00
|
|
|
1) a search defined on p_className: additional search criteria
|
2009-12-30 10:12:18 -06:00
|
|
|
will be added to the query, or;
|
2012-12-12 10:26:01 -06:00
|
|
|
2) "customSearch": in this case, additional search criteria will
|
|
|
|
also be added to the query, but those criteria come from the
|
|
|
|
session (in key "searchCriteria") and were created from
|
|
|
|
search.pt.
|
2009-12-30 10:12:18 -06:00
|
|
|
|
|
|
|
We will retrieve objects from p_startNumber. If p_search is defined,
|
|
|
|
it corresponds to a custom Search instance (instead of a predefined
|
|
|
|
named search like in p_searchName). If both p_searchName and p_search
|
|
|
|
are given, p_search is ignored.
|
|
|
|
|
|
|
|
This method returns a list of objects in the form of the
|
2009-11-24 15:41:42 -06:00
|
|
|
__dict__ attribute of an instance of SomeObjects (see in
|
|
|
|
appy.gen.utils). We return the __dict__ attribute instead of real
|
|
|
|
instance: that way, it can be used in ZPTs without security problems.
|
|
|
|
If p_brainsOnly is True, it returns a list of brains instead (can be
|
|
|
|
useful for some usages like knowing the number of objects without
|
|
|
|
needing to get information about them). If no p_maxResults is
|
|
|
|
specified, the method returns maximum
|
2010-08-05 11:23:17 -05:00
|
|
|
self.numberOfResultsPerPage. The method returns all objects if
|
2010-02-22 08:28:20 -06:00
|
|
|
p_maxResults equals string "NO_LIMIT".
|
2010-01-12 14:15:14 -06:00
|
|
|
|
|
|
|
If p_noSecurity is True, it gets all the objects, even those that the
|
2010-04-30 05:05:29 -05:00
|
|
|
currently logged user can't see.
|
|
|
|
|
|
|
|
The result is sorted according to the potential sort key defined in
|
2012-05-25 07:27:53 -05:00
|
|
|
the Search instance (Search.sortBy, together with Search.sortOrder).
|
|
|
|
But if parameter p_sortBy is given, it defines or overrides the sort.
|
|
|
|
In this case, p_sortOrder gives the order (*asc*ending or
|
|
|
|
*desc*ending).
|
2010-04-30 05:05:29 -05:00
|
|
|
|
|
|
|
If p_filterKey is given, it represents an additional search parameter
|
|
|
|
to take into account: the corresponding search value is in
|
2010-12-06 04:11:40 -06:00
|
|
|
p_filterValue.
|
|
|
|
|
2011-09-20 12:21:48 -05:00
|
|
|
If p_refObject and p_refField are given, the query is limited to the
|
|
|
|
objects that are referenced from p_refObject through p_refField.'''
|
2012-11-14 10:40:52 -06:00
|
|
|
params = {'ClassName': className}
|
2009-11-24 15:41:42 -06:00
|
|
|
if not brainsOnly: params['batch'] = True
|
2009-11-03 08:02:18 -06:00
|
|
|
# Manage additional criteria from a search when relevant
|
2012-11-14 10:40:52 -06:00
|
|
|
if searchName: search = self.getSearch(className, searchName)
|
2009-11-06 04:33:56 -06:00
|
|
|
if search:
|
|
|
|
# Add additional search criteria
|
2009-11-03 08:02:18 -06:00
|
|
|
for fieldName, fieldValue in search.fields.iteritems():
|
2010-12-06 04:11:40 -06:00
|
|
|
# Management of searches restricted to objects linked through a
|
|
|
|
# Ref field: not implemented yet.
|
|
|
|
if fieldName == '_ref': continue
|
2010-01-07 13:25:18 -06:00
|
|
|
# Make the correspondance between the name of the field and the
|
|
|
|
# name of the corresponding index.
|
2010-04-30 05:05:29 -05:00
|
|
|
attrName = Search.getIndexName(fieldName)
|
2010-01-07 13:25:18 -06:00
|
|
|
# Express the field value in the way needed by the index
|
2010-04-30 05:05:29 -05:00
|
|
|
params[attrName] = Search.getSearchValue(fieldName, fieldValue)
|
2009-11-06 04:33:56 -06:00
|
|
|
# Add a sort order if specified
|
2010-04-30 05:05:29 -05:00
|
|
|
sortKey = search.sortBy
|
|
|
|
if sortKey:
|
|
|
|
params['sort_on'] = Search.getIndexName(sortKey, usage='sort')
|
2012-05-25 07:27:53 -05:00
|
|
|
if search.sortOrder == 'desc': params['sort_order'] = 'reverse'
|
|
|
|
else: params['sort_order'] = None
|
2010-04-30 05:05:29 -05:00
|
|
|
# Determine or override sort if specified.
|
|
|
|
if sortBy:
|
|
|
|
params['sort_on'] = Search.getIndexName(sortBy, usage='sort')
|
|
|
|
if sortOrder == 'desc': params['sort_order'] = 'reverse'
|
|
|
|
else: params['sort_order'] = None
|
|
|
|
# If defined, add the filter among search parameters.
|
|
|
|
if filterKey:
|
|
|
|
filterKey = Search.getIndexName(filterKey)
|
|
|
|
filterValue = Search.getSearchValue(filterKey, filterValue)
|
|
|
|
params[filterKey] = filterValue
|
|
|
|
# TODO This value needs to be merged with an existing one if already
|
|
|
|
# in params, or, in a first step, we should avoid to display the
|
|
|
|
# corresponding filter widget on the screen.
|
2011-09-20 12:21:48 -05:00
|
|
|
if refObject:
|
|
|
|
refField = refObject.getAppyType(refField)
|
2011-09-26 14:19:34 -05:00
|
|
|
params['UID'] = getattr(refObject, refField.name).data
|
2011-11-28 15:50:01 -06:00
|
|
|
# Use index "Allowed" if noSecurity is False
|
|
|
|
if not noSecurity: params['Allowed'] = self.getAllowedValue()
|
|
|
|
brains = self.getPath("/catalog")(**params)
|
2010-02-22 08:28:20 -06:00
|
|
|
if brainsOnly:
|
|
|
|
# Return brains only.
|
|
|
|
if not maxResults: return brains
|
|
|
|
else: return brains[:maxResults]
|
2010-12-06 04:11:40 -06:00
|
|
|
if not maxResults:
|
|
|
|
if refField: maxResults = refField.maxPerPage
|
|
|
|
else: maxResults = self.appy().numberOfResultsPerPage
|
2009-12-21 13:45:29 -06:00
|
|
|
elif maxResults == 'NO_LIMIT': maxResults = None
|
2010-01-12 14:15:14 -06:00
|
|
|
res = SomeObjects(brains, maxResults, startNumber,noSecurity=noSecurity)
|
2009-11-03 08:02:18 -06:00
|
|
|
res.brainsToObjects()
|
2009-11-17 03:05:19 -06:00
|
|
|
# In some cases (p_remember=True), we need to keep some information
|
|
|
|
# about the query results in the current user's session, allowing him
|
|
|
|
# to navigate within elements without re-triggering the query every
|
|
|
|
# time a page for an element is consulted.
|
|
|
|
if remember:
|
|
|
|
if not searchName:
|
2012-12-12 10:26:01 -06:00
|
|
|
if not search or (search.name == 'allSearch'):
|
|
|
|
searchName = className
|
|
|
|
else:
|
|
|
|
searchName = search.name
|
2009-11-17 03:05:19 -06:00
|
|
|
uids = {}
|
|
|
|
i = -1
|
|
|
|
for obj in res.objects:
|
|
|
|
i += 1
|
|
|
|
uids[startNumber+i] = obj.UID()
|
2010-12-17 07:46:55 -06:00
|
|
|
self.REQUEST.SESSION['search_%s' % searchName] = uids
|
2009-11-03 08:02:18 -06:00
|
|
|
return res.__dict__
|
2009-06-29 07:06:01 -05:00
|
|
|
|
2012-11-02 16:27:54 -05:00
|
|
|
def getResultColumnsLayouts(self, contentType, refInfo):
|
|
|
|
'''Returns the column layouts for displaying objects of
|
|
|
|
p_contentType.'''
|
|
|
|
if refInfo[0]:
|
|
|
|
res = refInfo[0].getAppyType(refInfo[1]).shownInfo
|
|
|
|
else:
|
|
|
|
toolFieldName = 'resultColumnsFor%s' % contentType
|
|
|
|
res = getattr(self.appy(), toolFieldName)
|
2009-06-29 07:06:01 -05:00
|
|
|
return res
|
|
|
|
|
2010-08-05 11:23:17 -05:00
|
|
|
def truncateValue(self, value, appyType):
|
|
|
|
'''Truncates the p_value according to p_appyType width.'''
|
|
|
|
maxWidth = appyType['width']
|
2011-12-01 13:53:13 -06:00
|
|
|
if isinstance(value, str): value = value.decode('utf-8')
|
2010-08-05 11:23:17 -05:00
|
|
|
if len(value) > maxWidth:
|
2011-12-08 09:01:57 -06:00
|
|
|
return value[:maxWidth].encode('utf-8') + '...'
|
|
|
|
return value.encode('utf-8')
|
2010-08-05 11:23:17 -05:00
|
|
|
|
2010-09-20 04:33:54 -05:00
|
|
|
def truncateText(self, text, width=15):
|
|
|
|
'''Truncates p_text to max p_width chars. If the text is longer than
|
|
|
|
p_width, the truncated part is put in a "acronym" html tag.'''
|
2011-12-08 09:01:57 -06:00
|
|
|
# p_text has to be unicode-encoded for being truncated (else, one char
|
|
|
|
# may be spread on 2 chars). But this method must return an encoded
|
|
|
|
# string, else, ZPT crashes. The same remark holds for m_truncateValue
|
|
|
|
# above.
|
|
|
|
uText = text # uText will store the unicode version
|
|
|
|
if isinstance(text, str): uText = text.decode('utf-8')
|
|
|
|
if len(uText) <= width: return text
|
|
|
|
return '<acronym title="%s">%s</acronym>' % \
|
|
|
|
(text, uText[:width].encode('utf-8') + '...')
|
2010-09-20 04:33:54 -05:00
|
|
|
|
2012-11-29 13:45:21 -06:00
|
|
|
def getLayoutType(self):
|
|
|
|
'''Guess the current layout type, according to actual URL.'''
|
|
|
|
actualUrl = self.REQUEST['ACTUAL_URL']
|
|
|
|
res = ''
|
|
|
|
if actualUrl.endswith('/ui/view'):
|
|
|
|
res = 'view'
|
|
|
|
elif actualUrl.endswith('/ui/edit') or actualUrl.endswith('/do'):
|
|
|
|
res = 'edit'
|
|
|
|
return res
|
|
|
|
|
|
|
|
def getPublishedObject(self, layoutType):
|
2010-08-05 11:23:17 -05:00
|
|
|
'''Gets the currently published object, if its meta_class is among
|
2010-09-17 08:32:48 -05:00
|
|
|
application classes.'''
|
2012-11-29 13:45:21 -06:00
|
|
|
# In some situations (ie, we are querying objects), the published object
|
|
|
|
# according to Zope is the tool, but we don't want to consider it that
|
|
|
|
# way.
|
|
|
|
if layoutType not in ('edit', 'view'): return
|
2011-09-14 14:01:58 -05:00
|
|
|
obj = self.REQUEST['PUBLISHED']
|
2012-11-29 13:45:21 -06:00
|
|
|
# If URL is a /do, published object is the "do" method.
|
|
|
|
if type(obj) == types.MethodType: obj = obj.im_self
|
|
|
|
else:
|
|
|
|
parent = obj.getParentNode()
|
|
|
|
if parent.id == 'ui': obj = parent.getParentNode()
|
2011-09-14 14:01:58 -05:00
|
|
|
if obj.meta_type in self.getProductConfig().attributes: return obj
|
2009-10-20 09:57:00 -05:00
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def getZopeClass(self, name):
|
|
|
|
'''Returns the Zope class whose name is p_name.'''
|
|
|
|
exec 'from Products.%s.%s import %s as C'% (self.getAppName(),name,name)
|
|
|
|
return C
|
|
|
|
|
|
|
|
def getAppyClass(self, zopeName, wrapper=False):
|
|
|
|
'''Gets the Appy class corresponding to the Zope class named p_name.
|
|
|
|
If p_wrapper is True, it returns the Appy wrapper. Else, it returns
|
|
|
|
the user-defined class.'''
|
|
|
|
zopeClass = self.getZopeClass(zopeName)
|
|
|
|
if wrapper: return zopeClass.wrapperClass
|
|
|
|
else: return zopeClass.wrapperClass.__bases__[-1]
|
2010-04-26 11:19:34 -05:00
|
|
|
|
2009-10-20 09:57:00 -05:00
|
|
|
def getCreateMeans(self, contentTypeOrAppyClass):
|
|
|
|
'''Gets the different ways objects of p_contentTypeOrAppyClass (which
|
2011-12-05 08:11:29 -06:00
|
|
|
can be a Zope content type or a Appy class) can be created
|
2009-10-20 09:57:00 -05:00
|
|
|
(via a web form, by importing external data, etc). Result is a
|
|
|
|
dict whose keys are strings (ie "form", "import"...) and whose
|
2012-09-19 04:29:29 -05:00
|
|
|
values are additional data about the particular mean.'''
|
2009-10-20 09:57:00 -05:00
|
|
|
pythonClass = contentTypeOrAppyClass
|
|
|
|
if isinstance(contentTypeOrAppyClass, basestring):
|
|
|
|
pythonClass = self.getAppyClass(pythonClass)
|
|
|
|
res = {}
|
|
|
|
if not pythonClass.__dict__.has_key('create'):
|
|
|
|
res['form'] = None
|
|
|
|
# No additional data for this means, which is the default one.
|
|
|
|
else:
|
|
|
|
means = pythonClass.create
|
|
|
|
if means:
|
|
|
|
if isinstance(means, basestring): res[means] = None
|
|
|
|
elif isinstance(means, list) or isinstance(means, tuple):
|
|
|
|
for mean in means:
|
|
|
|
if isinstance(mean, basestring):
|
|
|
|
res[mean] = None
|
|
|
|
else:
|
|
|
|
res[mean.id] = mean.__dict__
|
|
|
|
else:
|
|
|
|
res[means.id] = means.__dict__
|
|
|
|
return res
|
|
|
|
|
2010-09-20 04:33:54 -05:00
|
|
|
def userMaySearch(self, rootClass):
|
|
|
|
'''This method checks if the currently logged user can trigger searches
|
|
|
|
on a given p_rootClass. This is done by calling method "maySearch"
|
|
|
|
on the class. If no such method exists, we return True.'''
|
2011-09-14 14:01:58 -05:00
|
|
|
# When editign a form, one should avoid annoying the user with this.
|
2011-09-28 14:17:15 -05:00
|
|
|
url = self.REQUEST['ACTUAL_URL']
|
|
|
|
if url.endswith('/edit') or url.endswith('/do'): return
|
2010-08-05 11:23:17 -05:00
|
|
|
pythonClass = self.getAppyClass(rootClass)
|
2010-09-20 04:33:54 -05:00
|
|
|
if 'maySearch' in pythonClass.__dict__:
|
|
|
|
return pythonClass.maySearch(self.appy())
|
2010-08-05 11:23:17 -05:00
|
|
|
return True
|
|
|
|
|
2009-10-20 09:57:00 -05:00
|
|
|
def onImportObjects(self):
|
|
|
|
'''This method is called when the user wants to create objects from
|
|
|
|
external data.'''
|
|
|
|
rq = self.REQUEST
|
2011-11-25 11:01:20 -06:00
|
|
|
appyClass = self.getAppyClass(rq.get('className'))
|
2009-10-20 09:57:00 -05:00
|
|
|
importPaths = rq.get('importPath').split('|')
|
2011-11-25 11:01:20 -06:00
|
|
|
appFolder = self.getPath('/data')
|
2009-10-20 09:57:00 -05:00
|
|
|
for importPath in importPaths:
|
|
|
|
if not importPath: continue
|
|
|
|
objectId = os.path.basename(importPath)
|
2009-10-25 15:42:08 -05:00
|
|
|
self.appy().create(appyClass, id=objectId, _data=importPath)
|
2011-02-12 10:09:11 -06:00
|
|
|
self.say(self.translate('import_done'))
|
2009-10-25 15:42:08 -05:00
|
|
|
return self.goto(rq['HTTP_REFERER'])
|
2009-10-20 09:57:00 -05:00
|
|
|
|
|
|
|
def isAlreadyImported(self, contentType, importPath):
|
2011-11-25 11:01:20 -06:00
|
|
|
data = self.getPath('/data')
|
2009-10-20 09:57:00 -05:00
|
|
|
objectId = os.path.basename(importPath)
|
2011-11-25 11:01:20 -06:00
|
|
|
if hasattr(data.aq_base, objectId):
|
2009-10-20 09:57:00 -05:00
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2010-09-20 04:33:54 -05:00
|
|
|
def isSortable(self, name, className, usage):
|
2010-12-06 04:11:40 -06:00
|
|
|
'''Is field p_name defined on p_className sortable for p_usage purposes
|
2010-09-20 04:33:54 -05:00
|
|
|
(p_usage can be "ref" or "search")?'''
|
2011-09-20 12:21:48 -05:00
|
|
|
if (',' in className) or (name == 'state'): return False
|
2010-09-20 04:33:54 -05:00
|
|
|
appyType = self.getAppyType(name, className=className)
|
2011-01-19 13:51:43 -06:00
|
|
|
if appyType: return appyType.isSortable(usage=usage)
|
2010-09-20 04:33:54 -05:00
|
|
|
|
2012-10-31 15:17:31 -05:00
|
|
|
def subTitleIsUsed(self, className):
|
|
|
|
'''Does class named p_className define a method "getSubTitle"?'''
|
|
|
|
klass = self.getAppyClass(className)
|
|
|
|
return hasattr(klass, 'getSubTitle')
|
|
|
|
|
2010-01-07 13:25:18 -06:00
|
|
|
def _searchValueIsEmpty(self, key):
|
|
|
|
'''Returns True if request value in key p_key can be considered as
|
|
|
|
empty.'''
|
|
|
|
rq = self.REQUEST.form
|
2010-01-14 10:54:18 -06:00
|
|
|
if key.endswith('*int') or key.endswith('*float'):
|
2010-01-07 13:25:18 -06:00
|
|
|
# We return True if "from" AND "to" values are empty.
|
2010-01-14 10:54:18 -06:00
|
|
|
toKey = '%s_to' % key[2:key.find('*')]
|
2010-01-07 13:25:18 -06:00
|
|
|
return not rq[key].strip() and not rq[toKey].strip()
|
|
|
|
elif key.endswith('*date'):
|
|
|
|
# We return True if "from" AND "to" values are empty. A value is
|
|
|
|
# considered as not empty if at least the year is specified.
|
|
|
|
toKey = '%s_to_year' % key[2:-5]
|
|
|
|
return not rq[key] and not rq[toKey]
|
|
|
|
else:
|
|
|
|
return not rq[key]
|
|
|
|
|
|
|
|
def _getDateTime(self, year, month, day, setMin):
|
|
|
|
'''Gets a valid DateTime instance from date information coming from the
|
|
|
|
request as strings in p_year, p_month and p_day. Returns None if
|
|
|
|
p_year is empty. If p_setMin is True, when some
|
|
|
|
information is missing (month or day), we will replace it with the
|
|
|
|
minimum value (=1). Else, we will replace it with the maximum value
|
|
|
|
(=12, =31).'''
|
|
|
|
if not year: return None
|
|
|
|
if not month:
|
|
|
|
if setMin: month = 1
|
|
|
|
else: month = 12
|
|
|
|
if not day:
|
|
|
|
if setMin: day = 1
|
|
|
|
else: day = 31
|
|
|
|
DateTime = self.getProductConfig().DateTime
|
2011-02-15 06:27:36 -06:00
|
|
|
# Set the hour
|
|
|
|
if setMin: hour = '00:00'
|
|
|
|
else: hour = '23:59'
|
2010-01-07 13:25:18 -06:00
|
|
|
# We loop until we find a valid date. For example, we could loop from
|
|
|
|
# 2009/02/31 to 2009/02/28.
|
|
|
|
dateIsWrong = True
|
|
|
|
while dateIsWrong:
|
|
|
|
try:
|
2011-02-15 06:27:36 -06:00
|
|
|
res = DateTime('%s/%s/%s %s' % (year, month, day, hour))
|
2010-01-07 13:25:18 -06:00
|
|
|
dateIsWrong = False
|
|
|
|
except:
|
|
|
|
day = int(day)-1
|
|
|
|
return res
|
|
|
|
|
2010-04-16 10:07:34 -05:00
|
|
|
transformMethods = {'uppercase': 'upper', 'lowercase': 'lower',
|
|
|
|
'capitalize': 'capitalize'}
|
2009-12-30 10:12:18 -06:00
|
|
|
def onSearchObjects(self):
|
|
|
|
'''This method is called when the user triggers a search from
|
|
|
|
search.pt.'''
|
|
|
|
rq = self.REQUEST
|
2010-01-06 11:36:16 -06:00
|
|
|
# Store the search criteria in the session
|
|
|
|
criteria = {}
|
|
|
|
for attrName in rq.form.keys():
|
2010-01-07 13:25:18 -06:00
|
|
|
if attrName.startswith('w_') and \
|
|
|
|
not self._searchValueIsEmpty(attrName):
|
|
|
|
# We have a(n interval of) value(s) that is not empty for a
|
|
|
|
# given field.
|
2010-01-06 11:36:16 -06:00
|
|
|
attrValue = rq.form[attrName]
|
2010-01-07 13:25:18 -06:00
|
|
|
if attrName.find('*') != -1:
|
2011-02-12 10:09:11 -06:00
|
|
|
attrValue = attrValue.strip()
|
2010-03-19 07:13:36 -05:00
|
|
|
# The type of the value is encoded after char "*".
|
2010-01-07 13:25:18 -06:00
|
|
|
attrName, attrType = attrName.split('*')
|
|
|
|
if attrType == 'bool':
|
|
|
|
exec 'attrValue = %s' % attrValue
|
2010-01-14 10:54:18 -06:00
|
|
|
elif attrType in ('int', 'float'):
|
2010-01-07 13:25:18 -06:00
|
|
|
# Get the "from" value
|
2011-02-12 10:09:11 -06:00
|
|
|
if not attrValue: attrValue = None
|
2010-01-14 10:54:18 -06:00
|
|
|
else:
|
|
|
|
exec 'attrValue = %s(attrValue)' % attrType
|
2010-01-07 13:25:18 -06:00
|
|
|
# Get the "to" value
|
|
|
|
toValue = rq.form['%s_to' % attrName[2:]].strip()
|
2010-01-14 10:54:18 -06:00
|
|
|
if not toValue: toValue = None
|
|
|
|
else:
|
|
|
|
exec 'toValue = %s(toValue)' % attrType
|
2010-01-07 13:25:18 -06:00
|
|
|
attrValue = (attrValue, toValue)
|
|
|
|
elif attrType == 'date':
|
|
|
|
prefix = attrName[2:]
|
|
|
|
# Get the "from" value
|
|
|
|
year = attrValue
|
|
|
|
month = rq.form['%s_from_month' % prefix]
|
|
|
|
day = rq.form['%s_from_day' % prefix]
|
|
|
|
fromDate = self._getDateTime(year, month, day, True)
|
|
|
|
# Get the "to" value"
|
|
|
|
year = rq.form['%s_to_year' % prefix]
|
|
|
|
month = rq.form['%s_to_month' % prefix]
|
|
|
|
day = rq.form['%s_to_day' % prefix]
|
|
|
|
toDate = self._getDateTime(year, month, day, False)
|
|
|
|
attrValue = (fromDate, toDate)
|
2010-04-16 10:07:34 -05:00
|
|
|
elif attrType.startswith('string'):
|
|
|
|
# In the case of a string, it could be necessary to
|
|
|
|
# apply some text transform.
|
|
|
|
if len(attrType) > 6:
|
|
|
|
transform = attrType.split('-')[1]
|
|
|
|
if (transform != 'none') and attrValue:
|
|
|
|
exec 'attrValue = attrValue.%s()' % \
|
|
|
|
self.transformMethods[transform]
|
2010-01-07 13:25:18 -06:00
|
|
|
if isinstance(attrValue, list):
|
2010-03-19 07:13:36 -05:00
|
|
|
# It is a list of values. Check if we have an operator for
|
|
|
|
# the field, to see if we make an "and" or "or" for all
|
|
|
|
# those values. "or" will be the default.
|
|
|
|
operKey = 'o_%s' % attrName[2:]
|
|
|
|
oper = ' %s ' % rq.form.get(operKey, 'or').upper()
|
|
|
|
attrValue = oper.join(attrValue)
|
2010-01-07 13:25:18 -06:00
|
|
|
criteria[attrName[2:]] = attrValue
|
2010-12-06 04:11:40 -06:00
|
|
|
# Complete criteria with Ref info if the search is restricted to
|
|
|
|
# referenced objects of a Ref field.
|
|
|
|
refInfo = rq.get('ref', None)
|
|
|
|
if refInfo: criteria['_ref'] = refInfo
|
2010-01-06 11:36:16 -06:00
|
|
|
rq.SESSION['searchCriteria'] = criteria
|
2010-10-14 07:43:56 -05:00
|
|
|
# Go to the screen that displays search results
|
2012-12-12 10:26:01 -06:00
|
|
|
backUrl = '%s/ui/query?className=%s&&search=customSearch' % \
|
2011-11-25 11:01:20 -06:00
|
|
|
(self.absolute_url(), rq['className'])
|
2009-12-30 10:12:18 -06:00
|
|
|
return self.goto(backUrl)
|
|
|
|
|
2009-10-20 09:57:00 -05:00
|
|
|
def getJavascriptMessages(self):
|
|
|
|
'''Returns the translated version of messages that must be shown in
|
|
|
|
Javascript popups.'''
|
|
|
|
res = ''
|
|
|
|
for msg in jsMessages:
|
|
|
|
res += 'var %s = "%s";\n' % (msg, self.translate(msg))
|
|
|
|
return res
|
2009-11-03 08:02:18 -06:00
|
|
|
|
2010-12-06 04:11:40 -06:00
|
|
|
def getRefInfo(self, refInfo=None):
|
|
|
|
'''When a search is restricted to objects referenced through a Ref
|
|
|
|
field, this method returns information about this reference: the
|
|
|
|
source content type and the Ref field (Appy type). If p_refInfo is
|
|
|
|
not given, we search it among search criteria in the session.'''
|
2012-12-12 10:26:01 -06:00
|
|
|
if not refInfo and (self.REQUEST.get('search', None) == 'customSearch'):
|
2010-12-06 04:11:40 -06:00
|
|
|
criteria = self.REQUEST.SESSION.get('searchCriteria', None)
|
|
|
|
if criteria and criteria.has_key('_ref'): refInfo = criteria['_ref']
|
2011-09-20 12:21:48 -05:00
|
|
|
if not refInfo: return (None, None)
|
|
|
|
objectUid, fieldName = refInfo.split(':')
|
|
|
|
obj = self.getObject(objectUid)
|
|
|
|
return obj, fieldName
|
2010-12-06 04:11:40 -06:00
|
|
|
|
2012-11-14 10:40:52 -06:00
|
|
|
def getGroupedSearches(self, className):
|
[gen] Added param Search.default allowing to define a default Search. The default search, if present, will be triggered when clicking on the main link for a class, instead of the query that collects all instances of this class; appy.gen.Type: removed 3 obsolete params: 'index', 'editDefault' and 'optional'. For achieving the same result than using 'editDefault', one may define 'by hand' an attribute on the Tool for storing the editable default value, and define, on the appropriate field in param 'default', a method that returns the value of the tool attribute; Added Type.defaultForSearch, allowing, for some sub-types, to define a default value when displaying the corresponding widget on the search screen; added a default 'state' field allowing to include workflow state among search criteria in the search screens; removed obsolete test applications.
2012-10-31 07:20:25 -05:00
|
|
|
'''Returns an object with 2 attributes:
|
2012-11-14 10:40:52 -06:00
|
|
|
* "searches" stores the searches that are defined for p_className;
|
2012-11-06 04:32:39 -06:00
|
|
|
* "default" stores the search defined as the default one.
|
[gen] Added param Search.default allowing to define a default Search. The default search, if present, will be triggered when clicking on the main link for a class, instead of the query that collects all instances of this class; appy.gen.Type: removed 3 obsolete params: 'index', 'editDefault' and 'optional'. For achieving the same result than using 'editDefault', one may define 'by hand' an attribute on the Tool for storing the editable default value, and define, on the appropriate field in param 'default', a method that returns the value of the tool attribute; Added Type.defaultForSearch, allowing, for some sub-types, to define a default value when displaying the corresponding widget on the search screen; added a default 'state' field allowing to include workflow state among search criteria in the search screens; removed obsolete test applications.
2012-10-31 07:20:25 -05:00
|
|
|
Every item representing a search is a dict containing info about a
|
|
|
|
search or about a group of searches.
|
|
|
|
'''
|
2012-11-14 10:40:52 -06:00
|
|
|
appyClass = self.getAppyClass(className)
|
2012-11-14 04:36:48 -06:00
|
|
|
res = []
|
[gen] Added param Search.default allowing to define a default Search. The default search, if present, will be triggered when clicking on the main link for a class, instead of the query that collects all instances of this class; appy.gen.Type: removed 3 obsolete params: 'index', 'editDefault' and 'optional'. For achieving the same result than using 'editDefault', one may define 'by hand' an attribute on the Tool for storing the editable default value, and define, on the appropriate field in param 'default', a method that returns the value of the tool attribute; Added Type.defaultForSearch, allowing, for some sub-types, to define a default value when displaying the corresponding widget on the search screen; added a default 'state' field allowing to include workflow state among search criteria in the search screens; removed obsolete test applications.
2012-10-31 07:20:25 -05:00
|
|
|
default = None # Also retrieve the default one here.
|
2012-11-14 04:36:48 -06:00
|
|
|
groups = {} # The already encountered groups
|
|
|
|
page = Page('main') # A dummy page required by class GroupDescr
|
2012-11-14 10:40:52 -06:00
|
|
|
searches = ClassDescriptor.getSearches(appyClass)
|
|
|
|
# Add dynamic searches if defined on p_appyClass
|
|
|
|
if hasattr(appyClass, 'getDynamicSearches'):
|
|
|
|
searches += appyClass.getDynamicSearches(self.appy())
|
|
|
|
for search in searches:
|
|
|
|
# Create the search descriptor
|
|
|
|
searchDescr = SearchDescr(search, className, self).get()
|
2012-11-14 04:36:48 -06:00
|
|
|
if not search.group:
|
|
|
|
# Insert the search at the highest level, not in any group.
|
2012-11-14 10:40:52 -06:00
|
|
|
res.append(searchDescr)
|
2009-11-06 04:33:56 -06:00
|
|
|
else:
|
2012-11-14 04:36:48 -06:00
|
|
|
groupDescr = search.group.insertInto(res, groups, page,
|
2012-11-14 10:40:52 -06:00
|
|
|
className, forSearch=True)
|
|
|
|
GroupDescr.addWidget(groupDescr, searchDescr)
|
2012-11-14 04:36:48 -06:00
|
|
|
# Is this search the default search?
|
2012-11-14 10:40:52 -06:00
|
|
|
if search.default: default = searchDescr
|
2012-11-14 04:36:48 -06:00
|
|
|
return Object(searches=res, default=default).__dict__
|
2009-11-06 04:33:56 -06:00
|
|
|
|
2012-11-14 10:40:52 -06:00
|
|
|
def getSearch(self, className, name, descr=False):
|
|
|
|
'''Gets the Search instance (or a SearchDescr instance if p_descr is
|
|
|
|
True) corresponding to the search named p_name, on class
|
|
|
|
p_className.'''
|
2012-12-12 10:26:01 -06:00
|
|
|
if name == 'customSearch':
|
|
|
|
# It is a custom search whose parameters are in the session.
|
2012-11-14 10:40:52 -06:00
|
|
|
fields = self.REQUEST.SESSION['searchCriteria']
|
|
|
|
res = Search('customSearch', **fields)
|
|
|
|
elif name:
|
|
|
|
appyClass = self.getAppyClass(className)
|
|
|
|
# Search among static searches
|
|
|
|
res = ClassDescriptor.getSearch(appyClass, name)
|
|
|
|
if not res and hasattr(appyClass, 'getDynamicSearches'):
|
|
|
|
# Search among dynamic searches
|
|
|
|
for search in appyClass.getDynamicSearches(self.appy()):
|
|
|
|
if search.name == name:
|
|
|
|
res = search
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
# It is the search for every instance of p_className
|
|
|
|
res = Search('allSearch')
|
|
|
|
# Return a SearchDescr if required.
|
|
|
|
if descr: res = SearchDescr(res, className, self).get()
|
|
|
|
return res
|
|
|
|
|
2010-10-14 07:43:56 -05:00
|
|
|
def getQueryUrl(self, contentType, searchName, startNumber=None):
|
2010-04-30 05:05:29 -05:00
|
|
|
'''This method creates the URL that allows to perform a (non-Ajax)
|
2009-11-03 08:02:18 -06:00
|
|
|
request for getting queried objects from a search named p_searchName
|
2010-10-14 07:43:56 -05:00
|
|
|
on p_contentType.'''
|
2011-11-25 11:01:20 -06:00
|
|
|
baseUrl = self.absolute_url() + '/ui'
|
|
|
|
baseParams = 'className=%s' % contentType
|
2009-11-17 03:05:19 -06:00
|
|
|
rq = self.REQUEST
|
2010-12-06 04:11:40 -06:00
|
|
|
if rq.get('ref'): baseParams += '&ref=%s' % rq.get('ref')
|
|
|
|
# Manage start number
|
2009-11-17 03:05:19 -06:00
|
|
|
if startNumber != None:
|
|
|
|
baseParams += '&startNumber=%s' % startNumber
|
|
|
|
elif rq.has_key('startNumber'):
|
|
|
|
baseParams += '&startNumber=%s' % rq['startNumber']
|
|
|
|
# Manage search name
|
2010-04-30 05:05:29 -05:00
|
|
|
if searchName: baseParams += '&search=%s' % searchName
|
|
|
|
return '%s/query?%s' % (baseUrl, baseParams)
|
2009-11-17 03:05:19 -06:00
|
|
|
|
|
|
|
def computeStartNumberFrom(self, currentNumber, totalNumber, batchSize):
|
|
|
|
'''Returns the number (start at 0) of the first element in a list
|
|
|
|
containing p_currentNumber (starts at 0) whose total number is
|
|
|
|
p_totalNumber and whose batch size is p_batchSize.'''
|
|
|
|
startNumber = 0
|
|
|
|
res = startNumber
|
|
|
|
while (startNumber < totalNumber):
|
|
|
|
if (currentNumber < startNumber + batchSize):
|
|
|
|
return startNumber
|
|
|
|
else:
|
|
|
|
startNumber += batchSize
|
|
|
|
return startNumber
|
|
|
|
|
|
|
|
def getNavigationInfo(self):
|
|
|
|
'''Extracts navigation information from request/nav and returns a dict
|
|
|
|
with the info that a page can use for displaying object
|
|
|
|
navigation.'''
|
|
|
|
res = {}
|
|
|
|
t,d1,d2,currentNumber,totalNumber = self.REQUEST.get('nav').split('.')
|
|
|
|
res['currentNumber'] = int(currentNumber)
|
|
|
|
res['totalNumber'] = int(totalNumber)
|
2010-01-15 06:46:54 -06:00
|
|
|
# Compute the label of the search, or ref field
|
|
|
|
if t == 'search':
|
|
|
|
searchName = d2
|
2010-02-22 08:28:20 -06:00
|
|
|
if not searchName:
|
|
|
|
# We search all objects of a given type.
|
|
|
|
label = '%s_plural' % d1.split(':')[0]
|
2012-12-12 10:26:01 -06:00
|
|
|
elif searchName == 'customSearch':
|
2010-02-22 08:28:20 -06:00
|
|
|
# This is an advanced, custom search.
|
|
|
|
label = 'search_results'
|
2010-01-15 06:46:54 -06:00
|
|
|
else:
|
2010-02-22 08:28:20 -06:00
|
|
|
# This is a named, predefined search.
|
2010-01-15 06:46:54 -06:00
|
|
|
label = '%s_search_%s' % (d1.split(':')[0], searchName)
|
|
|
|
res['backText'] = self.translate(label)
|
2012-11-14 10:40:52 -06:00
|
|
|
# If it is a dynamic search this label does not exist.
|
|
|
|
if ('_' in res['backText']): res['backText'] = ''
|
2010-01-15 06:46:54 -06:00
|
|
|
else:
|
2010-09-17 08:32:48 -05:00
|
|
|
fieldName, pageName = d2.split(':')
|
2011-11-25 11:01:20 -06:00
|
|
|
sourceObj = self.getObject(d1)
|
2010-09-17 08:32:48 -05:00
|
|
|
label = '%s_%s' % (sourceObj.meta_type, fieldName)
|
2012-11-14 10:40:52 -06:00
|
|
|
res['backText'] = '%s - %s' % (sourceObj.Title(),
|
2011-01-14 02:06:25 -06:00
|
|
|
self.translate(label))
|
2009-11-17 03:05:19 -06:00
|
|
|
newNav = '%s.%s.%s.%%d.%s' % (t, d1, d2, totalNumber)
|
|
|
|
# Among, first, previous, next and last, which one do I need?
|
|
|
|
previousNeeded = False # Previous ?
|
|
|
|
previousIndex = res['currentNumber'] - 2
|
|
|
|
if (previousIndex > -1) and (res['totalNumber'] > previousIndex):
|
|
|
|
previousNeeded = True
|
|
|
|
nextNeeded = False # Next ?
|
|
|
|
nextIndex = res['currentNumber']
|
|
|
|
if nextIndex < res['totalNumber']: nextNeeded = True
|
|
|
|
firstNeeded = False # First ?
|
|
|
|
firstIndex = 0
|
|
|
|
if previousIndex > 0: firstNeeded = True
|
|
|
|
lastNeeded = False # Last ?
|
|
|
|
lastIndex = res['totalNumber'] - 1
|
|
|
|
if (nextIndex < lastIndex): lastNeeded = True
|
|
|
|
# Get the list of available UIDs surrounding the current object
|
|
|
|
if t == 'ref': # Manage navigation from a reference
|
2010-10-14 07:43:56 -05:00
|
|
|
# In the case of a reference, we retrieve ALL surrounding objects.
|
2009-11-17 03:05:19 -06:00
|
|
|
masterObj = self.getObject(d1)
|
2010-08-05 11:23:17 -05:00
|
|
|
batchSize = masterObj.getAppyType(fieldName).maxPerPage
|
2011-09-26 14:19:34 -05:00
|
|
|
uids = getattr(masterObj, fieldName)
|
2009-11-17 03:05:19 -06:00
|
|
|
# Display the reference widget at the page where the current object
|
|
|
|
# lies.
|
|
|
|
startNumberKey = '%s%s_startNumber' % (masterObj.UID(), fieldName)
|
|
|
|
startNumber = self.computeStartNumberFrom(res['currentNumber']-1,
|
|
|
|
res['totalNumber'], batchSize)
|
2010-09-17 08:32:48 -05:00
|
|
|
res['sourceUrl'] = masterObj.getUrl(**{startNumberKey:startNumber,
|
|
|
|
'page':pageName, 'nav':''})
|
|
|
|
else: # Manage navigation from a search
|
2010-10-14 07:43:56 -05:00
|
|
|
contentType = d1
|
2009-11-17 03:05:19 -06:00
|
|
|
searchName = keySuffix = d2
|
2010-08-12 04:56:42 -05:00
|
|
|
batchSize = self.appy().numberOfResultsPerPage
|
2009-11-17 03:05:19 -06:00
|
|
|
if not searchName: keySuffix = contentType
|
|
|
|
s = self.REQUEST.SESSION
|
2010-10-14 07:43:56 -05:00
|
|
|
searchKey = 'search_%s' % keySuffix
|
2009-11-17 03:05:19 -06:00
|
|
|
if s.has_key(searchKey): uids = s[searchKey]
|
|
|
|
else: uids = {}
|
|
|
|
# In the case of a search, we retrieve only a part of all
|
|
|
|
# surrounding objects, those that are stored in the session.
|
|
|
|
if (previousNeeded and not uids.has_key(previousIndex)) or \
|
|
|
|
(nextNeeded and not uids.has_key(nextIndex)):
|
|
|
|
# I do not have this UID in session. I will need to
|
|
|
|
# retrigger the query by querying all objects surrounding
|
|
|
|
# this one.
|
|
|
|
newStartNumber = (res['currentNumber']-1) - (batchSize / 2)
|
|
|
|
if newStartNumber < 0: newStartNumber = 0
|
2010-10-14 07:43:56 -05:00
|
|
|
self.executeQuery(contentType, searchName=searchName,
|
|
|
|
startNumber=newStartNumber, remember=True)
|
2009-11-17 03:05:19 -06:00
|
|
|
uids = s[searchKey]
|
|
|
|
# For the moment, for first and last, we get them only if we have
|
|
|
|
# them in session.
|
|
|
|
if not uids.has_key(0): firstNeeded = False
|
|
|
|
if not uids.has_key(lastIndex): lastNeeded = False
|
|
|
|
# Compute URL of source object
|
|
|
|
startNumber = self.computeStartNumberFrom(res['currentNumber']-1,
|
2010-10-14 07:43:56 -05:00
|
|
|
res['totalNumber'], batchSize)
|
|
|
|
res['sourceUrl'] = self.getQueryUrl(contentType, searchName,
|
|
|
|
startNumber=startNumber)
|
2009-11-17 03:05:19 -06:00
|
|
|
# Compute URLs
|
|
|
|
for urlType in ('previous', 'next', 'first', 'last'):
|
|
|
|
exec 'needIt = %sNeeded' % urlType
|
|
|
|
urlKey = '%sUrl' % urlType
|
|
|
|
res[urlKey] = None
|
|
|
|
if needIt:
|
|
|
|
exec 'index = %sIndex' % urlType
|
2010-01-20 14:51:17 -06:00
|
|
|
uid = None
|
|
|
|
try:
|
|
|
|
uid = uids[index]
|
|
|
|
# uids can be a list (ref) or a dict (search)
|
|
|
|
except KeyError: pass
|
|
|
|
except IndexError: pass
|
|
|
|
if uid:
|
2011-11-25 11:01:20 -06:00
|
|
|
brain = self.getObject(uid, brain=True)
|
2010-01-20 14:51:17 -06:00
|
|
|
if brain:
|
2011-11-25 11:01:20 -06:00
|
|
|
sibling = brain.getObject()
|
2010-09-17 08:32:48 -05:00
|
|
|
res[urlKey] = sibling.getUrl(nav=newNav % (index + 1),
|
2011-12-15 15:56:53 -06:00
|
|
|
page=self.REQUEST.get('page', 'main'))
|
2009-11-17 03:05:19 -06:00
|
|
|
return res
|
2010-01-06 11:36:16 -06:00
|
|
|
|
2012-12-14 02:23:33 -06:00
|
|
|
def getGroupedSearchFields(self, searchInfo):
|
|
|
|
'''This method transforms p_searchInfo['fieldDicts'], which is a "flat"
|
|
|
|
list of fields, into a list of lists, where every sub-list having
|
|
|
|
length p_searchInfo['nbOfColumns']. For every field, scolspan
|
|
|
|
(=colspan "for search") is taken into account.'''
|
2010-08-05 11:23:17 -05:00
|
|
|
res = []
|
|
|
|
row = []
|
2012-12-14 02:23:33 -06:00
|
|
|
rowLength = 0
|
|
|
|
for field in searchInfo['fieldDicts']:
|
|
|
|
# Can I insert this field in the current row?
|
|
|
|
remaining = searchInfo['nbOfColumns'] - rowLength
|
|
|
|
if field['scolspan'] <= remaining:
|
|
|
|
# Yes.
|
|
|
|
row.append(field)
|
|
|
|
rowLength += field['scolspan']
|
|
|
|
else:
|
|
|
|
# We must put the field on a new line. Complete the current one
|
|
|
|
# if not complete.
|
|
|
|
while rowLength < searchInfo['nbOfColumns']:
|
|
|
|
row.append(None)
|
|
|
|
rowLength += 1
|
2010-01-06 11:36:16 -06:00
|
|
|
res.append(row)
|
2012-12-14 02:23:33 -06:00
|
|
|
row = [field]
|
|
|
|
rowLength = field['scolspan']
|
2010-08-05 11:23:17 -05:00
|
|
|
# Complete the last unfinished line if required.
|
|
|
|
if row:
|
2012-12-14 02:23:33 -06:00
|
|
|
while rowLength < searchInfo['nbOfColumns']:
|
|
|
|
row.append(None)
|
|
|
|
rowLength += 1
|
2010-08-05 11:23:17 -05:00
|
|
|
res.append(row)
|
|
|
|
return res
|
2010-01-06 11:36:16 -06:00
|
|
|
|
|
|
|
def truncate(self, value, numberOfChars):
|
|
|
|
'''Truncates string p_value to p_numberOfChars.'''
|
|
|
|
if len(value) > numberOfChars: return value[:numberOfChars] + '...'
|
|
|
|
return value
|
2010-01-07 13:25:18 -06:00
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
# --------------------------------------------------------------------------
|
|
|
|
# Authentication-related methods
|
|
|
|
# --------------------------------------------------------------------------
|
2012-02-21 05:09:42 -06:00
|
|
|
def _updateCookie(self, login, password):
|
|
|
|
cookieValue = base64.encodestring('%s:%s' % (login, password)).rstrip()
|
|
|
|
cookieValue = urllib.quote(cookieValue)
|
|
|
|
self.REQUEST.RESPONSE.setCookie('__ac', cookieValue, path='/')
|
|
|
|
|
2012-03-06 10:02:41 -06:00
|
|
|
def _encryptPassword(self, password):
|
|
|
|
'''Returns the encrypted version of clear p_password.'''
|
|
|
|
return self.acl_users._encryptPassword(password)
|
|
|
|
|
2011-02-15 08:59:55 -06:00
|
|
|
def performLogin(self):
|
|
|
|
'''Logs the user in.'''
|
|
|
|
rq = self.REQUEST
|
|
|
|
jsEnabled = rq.get('js_enabled', False) in ('1', 1)
|
|
|
|
cookiesEnabled = rq.get('cookies_enabled', False) in ('1', 1)
|
|
|
|
urlBack = rq['HTTP_REFERER']
|
|
|
|
|
|
|
|
if jsEnabled and not cookiesEnabled:
|
2012-06-03 11:34:56 -05:00
|
|
|
msg = self.translate('enable_cookies')
|
2011-12-08 09:01:57 -06:00
|
|
|
return self.goto(urlBack, msg)
|
2011-02-15 08:59:55 -06:00
|
|
|
# Perform the Zope-level authentication
|
2011-11-25 11:01:20 -06:00
|
|
|
login = rq.get('__ac_name', '')
|
2012-02-21 05:09:42 -06:00
|
|
|
self._updateCookie(login, rq.get('__ac_password', ''))
|
2011-11-25 11:01:20 -06:00
|
|
|
user = self.acl_users.validate(rq)
|
|
|
|
if self.userIsAnon():
|
2011-02-15 08:59:55 -06:00
|
|
|
rq.RESPONSE.expireCookie('__ac', path='/')
|
2012-06-03 11:34:56 -05:00
|
|
|
msg = self.translate('login_ko')
|
2012-02-18 12:48:00 -06:00
|
|
|
logMsg = 'Authentication failed (tried with login "%s").' % login
|
2011-02-15 08:59:55 -06:00
|
|
|
else:
|
2012-06-03 11:34:56 -05:00
|
|
|
msg = self.translate('login_ok')
|
|
|
|
logMsg = 'User "%s" logged in.' % login
|
2011-02-15 08:59:55 -06:00
|
|
|
self.log(logMsg)
|
2012-02-18 12:48:00 -06:00
|
|
|
return self.goto(self.getApp().absolute_url(), msg)
|
2011-02-15 08:59:55 -06:00
|
|
|
|
|
|
|
def performLogout(self):
|
2010-01-17 15:01:14 -06:00
|
|
|
'''Logs out the current user when he clicks on "disconnect".'''
|
|
|
|
rq = self.REQUEST
|
2011-11-25 11:01:20 -06:00
|
|
|
userId = self.getUser().getId()
|
2010-01-17 15:01:14 -06:00
|
|
|
# Perform the logout in acl_users
|
2011-11-25 11:01:20 -06:00
|
|
|
rq.RESPONSE.expireCookie('__ac', path='/')
|
2012-06-02 07:36:49 -05:00
|
|
|
# Invalidate session.
|
|
|
|
try:
|
|
|
|
sdm = self.session_data_manager
|
|
|
|
except AttributeError, ae:
|
|
|
|
# When ran in test mode, session_data_manager is not there.
|
|
|
|
sdm = None
|
|
|
|
if sdm:
|
|
|
|
session = sdm.getSessionData(create=0)
|
|
|
|
if session is not None:
|
|
|
|
session.invalidate()
|
2011-09-15 08:52:21 -05:00
|
|
|
self.log('User "%s" has been logged out.' % userId)
|
2010-01-17 15:01:14 -06:00
|
|
|
# Remove user from variable "loggedUsers"
|
2011-12-05 08:11:29 -06:00
|
|
|
from appy.gen.installer import loggedUsers
|
2010-01-17 15:01:14 -06:00
|
|
|
if loggedUsers.has_key(userId): del loggedUsers[userId]
|
2011-11-25 11:01:20 -06:00
|
|
|
return self.goto(self.getApp().absolute_url())
|
|
|
|
|
|
|
|
def validate(self, request, auth='', roles=_noroles):
|
|
|
|
'''This method performs authentication and authorization. It is used as
|
|
|
|
a replacement for Zope's AccessControl.User.BasicUserFolder.validate,
|
|
|
|
that allows to manage cookie-based authentication.'''
|
|
|
|
v = request['PUBLISHED'] # The published object
|
|
|
|
# v is the object (value) we're validating access to
|
|
|
|
# n is the name used to access the object
|
|
|
|
# a is the object the object was accessed through
|
|
|
|
# c is the physical container of the object
|
|
|
|
a, c, n, v = self._getobcontext(v, request)
|
|
|
|
# Try to get user name and password from basic authentication
|
|
|
|
login, password = self.identify(auth)
|
|
|
|
if not login:
|
|
|
|
# Try to get them from a cookie
|
|
|
|
cookie = request.get('__ac', None)
|
|
|
|
login = request.get('__ac_name', None)
|
|
|
|
if login and request.form.has_key('__ac_password'):
|
|
|
|
# The user just entered his credentials. The cookie has not been
|
|
|
|
# set yet (it will come in the upcoming HTTP response when the
|
|
|
|
# current request will be served).
|
|
|
|
login = request.get('__ac_name', '')
|
|
|
|
password = request.get('__ac_password', '')
|
|
|
|
elif cookie and (cookie != 'deleted'):
|
|
|
|
cookieValue = base64.decodestring(urllib.unquote(cookie))
|
2012-05-25 07:27:53 -05:00
|
|
|
if ':' in cookieValue:
|
|
|
|
login, password = cookieValue.split(':')
|
2011-11-25 11:01:20 -06:00
|
|
|
# Try to authenticate this user
|
|
|
|
user = self.authenticate(login, password, request)
|
|
|
|
emergency = self._emergency_user
|
|
|
|
if emergency and user is emergency:
|
|
|
|
# It is the emergency user.
|
|
|
|
return emergency.__of__(self)
|
|
|
|
elif user is None:
|
|
|
|
# Login and/or password incorrect. Try to authorize and return the
|
|
|
|
# anonymous user.
|
|
|
|
if self.authorize(self._nobody, a, c, n, v, roles):
|
|
|
|
return self._nobody.__of__(self)
|
|
|
|
else:
|
|
|
|
return # Anonymous can't acces this object
|
|
|
|
else:
|
|
|
|
# We found a user and his password was correct. Try to authorize him
|
|
|
|
# against the published object.
|
|
|
|
if self.authorize(user, a, c, n, v, roles):
|
|
|
|
return user.__of__(self)
|
|
|
|
# That didn't work. Try to authorize the anonymous user.
|
|
|
|
elif self.authorize(self._nobody, a, c, n, v, roles):
|
|
|
|
return self._nobody.__of__(self)
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
|
|
|
|
# Patch BasicUserFolder with our version of m_validate above.
|
|
|
|
from AccessControl.User import BasicUserFolder
|
|
|
|
BasicUserFolder.validate = validate
|
2010-08-05 11:23:17 -05:00
|
|
|
|
|
|
|
def tempFile(self):
|
|
|
|
'''A temp file has been created in a temp folder. This method returns
|
|
|
|
this file to the browser.'''
|
|
|
|
rq = self.REQUEST
|
|
|
|
baseFolder = os.path.join(getOsTempFolder(), self.getAppName())
|
|
|
|
baseFolder = os.path.join(baseFolder, rq.SESSION.id)
|
|
|
|
fileName = os.path.join(baseFolder, rq.get('name', ''))
|
|
|
|
if os.path.exists(fileName):
|
|
|
|
f = file(fileName)
|
|
|
|
content = f.read()
|
|
|
|
f.close()
|
|
|
|
# Remove the temp file
|
|
|
|
os.remove(fileName)
|
|
|
|
return content
|
|
|
|
return 'File does not exist'
|
2011-01-28 07:36:30 -06:00
|
|
|
|
|
|
|
def getResultPodFields(self, contentType):
|
|
|
|
'''Finds, among fields defined on p_contentType, which ones are Pod
|
|
|
|
fields that need to be shown on a page displaying query results.'''
|
2011-02-14 09:04:30 -06:00
|
|
|
# Skip this if we are searching multiple content types.
|
|
|
|
if ',' in contentType: return ()
|
2011-01-28 07:36:30 -06:00
|
|
|
return [f.__dict__ for f in self.getAllAppyTypes(contentType) \
|
|
|
|
if (f.type == 'Pod') and (f.show == 'result')]
|
2011-09-20 12:21:48 -05:00
|
|
|
|
2012-02-16 11:13:51 -06:00
|
|
|
def getUserLine(self):
|
|
|
|
'''Returns a info about the currently logged user as a 2-tuple: first
|
|
|
|
elem is the one-line user info as shown on every page; second line is
|
|
|
|
the URL to edit user info.'''
|
|
|
|
appyUser = self.appy().appyUser
|
2012-06-01 08:57:19 -05:00
|
|
|
info = [appyUser.title]
|
2012-02-16 11:13:51 -06:00
|
|
|
rolesToShow = [r for r in appyUser.roles \
|
2011-09-20 12:21:48 -05:00
|
|
|
if r not in ('Authenticated', 'Member')]
|
|
|
|
if rolesToShow:
|
2012-06-01 08:57:19 -05:00
|
|
|
info.append(', '.join([self.translate('role_%s'%r) \
|
|
|
|
for r in rolesToShow]))
|
|
|
|
# Edit URL for the appy user.
|
|
|
|
url = None
|
|
|
|
if appyUser.o.mayEdit():
|
|
|
|
url = appyUser.o.getUrl(mode='edit', page='main', nav='')
|
|
|
|
return (' | '.join(info), url)
|
2011-11-25 11:01:20 -06:00
|
|
|
|
2012-07-26 10:22:22 -05:00
|
|
|
def getUserName(self, login=None):
|
|
|
|
'''Gets the user name corresponding to p_login (or the currently logged
|
|
|
|
login if None), or the p_login itself if the user does not exist
|
|
|
|
anymore.'''
|
|
|
|
tool = self.appy()
|
|
|
|
if not login: login = tool.user.getId()
|
|
|
|
user = tool.search1('User', noSecurity=True, login=login)
|
2012-07-18 14:58:11 -05:00
|
|
|
if not user: return login
|
|
|
|
firstName = user.firstName
|
|
|
|
name = user.name
|
|
|
|
res = ''
|
|
|
|
if firstName: res += firstName
|
|
|
|
if name:
|
|
|
|
if res: res += ' ' + name
|
|
|
|
else: res = name
|
|
|
|
if not res: res = login
|
|
|
|
return res
|
|
|
|
|
|
|
|
def formatDate(self, aDate, withHour=True):
|
|
|
|
'''Returns aDate formatted as specified by tool.dateFormat.
|
|
|
|
If p_withHour is True, hour is appended, with a format specified
|
|
|
|
in tool.hourFormat.'''
|
|
|
|
tool = self.appy()
|
|
|
|
res = aDate.strftime(tool.dateFormat)
|
|
|
|
if withHour: res += ' (%s)' % aDate.strftime(tool.hourFormat)
|
|
|
|
return res
|
|
|
|
|
2011-11-25 11:01:20 -06:00
|
|
|
def generateUid(self, className):
|
|
|
|
'''Generates a UID for an instance of p_className.'''
|
2012-04-24 09:22:12 -05:00
|
|
|
name = className.split('_')[-1]
|
2011-11-25 11:01:20 -06:00
|
|
|
randomNumber = str(random.random()).split('.')[1]
|
|
|
|
timestamp = ('%f' % time.time()).replace('.', '')
|
|
|
|
return '%s%s%s' % (name, timestamp, randomNumber)
|
2011-12-05 11:15:45 -06:00
|
|
|
|
|
|
|
def manageError(self, error):
|
|
|
|
'''Manages an error.'''
|
|
|
|
tb = sys.exc_info()
|
|
|
|
from zExceptions.ExceptionFormatter import format_exception
|
|
|
|
htmlMessage = format_exception(tb[0], tb[1], tb[2], as_html=1)
|
|
|
|
textMessage = format_exception(tb[0], tb[1], tb[2], as_html=0)
|
|
|
|
self.log(''.join(textMessage).strip(), type='error')
|
|
|
|
return '<table class="main" align="center" cellpadding="0"><tr>' \
|
|
|
|
'<td style="padding: 1em 1em 1em 1em">An error occurred. %s' \
|
|
|
|
'</td></tr></table>' % '\n'.join(htmlMessage)
|
2012-03-26 12:09:45 -05:00
|
|
|
|
|
|
|
def getMainPages(self):
|
|
|
|
'''Returns the main pages.'''
|
|
|
|
if hasattr(self.o.aq_base, 'pages') and self.o.pages:
|
|
|
|
return [self.getObject(uid) for uid in self.o.pages ]
|
|
|
|
return ()
|
2012-07-09 08:47:38 -05:00
|
|
|
|
|
|
|
def askPasswordReinit(self):
|
|
|
|
'''A user (anonymmous) does not remember its password. Here we will
|
|
|
|
send him a mail containing a link that will trigger password
|
|
|
|
re-initialisation.'''
|
|
|
|
login = self.REQUEST.get('login').strip()
|
|
|
|
appyTool = self.appy()
|
|
|
|
user = appyTool.search1('User', login=login, noSecurity=True)
|
|
|
|
msg = self.translate('reinit_mail_sent')
|
|
|
|
backUrl = self.REQUEST['HTTP_REFERER']
|
|
|
|
if not user:
|
|
|
|
# Return the message nevertheless. This way, malicious users can't
|
|
|
|
# deduce information about existing users.
|
|
|
|
return self.goto(backUrl, msg)
|
|
|
|
# If login is an email, use it. Else, use user.email instead.
|
|
|
|
email = user.login
|
|
|
|
if not String.EMAIL.match(email):
|
|
|
|
email = user.email
|
|
|
|
if not email:
|
|
|
|
# Impossible to re-initialise the password.
|
|
|
|
return self.goto(backUrl, msg)
|
|
|
|
# Create a temporary file whose name is the user login and whose
|
|
|
|
# content is a generated token.
|
|
|
|
f = file(os.path.join(getOsTempFolder(), login), 'w')
|
|
|
|
token = String().generatePassword()
|
|
|
|
f.write(token)
|
|
|
|
f.close()
|
|
|
|
# Send an email
|
|
|
|
initUrl = '%s/doPasswordReinit?login=%s&token=%s' % \
|
|
|
|
(self.absolute_url(), login, token)
|
|
|
|
subject = self.translate('reinit_password')
|
|
|
|
map = {'url':initUrl, 'siteUrl':self.getSiteUrl()}
|
|
|
|
body= self.translate('reinit_password_body', mapping=map, format='text')
|
|
|
|
sendMail(appyTool, email, subject, body)
|
|
|
|
return self.goto(backUrl, msg)
|
|
|
|
|
|
|
|
def doPasswordReinit(self):
|
|
|
|
'''Performs the password re-initialisation.'''
|
|
|
|
rq = self.REQUEST
|
|
|
|
login = rq['login']
|
|
|
|
token = rq['token']
|
|
|
|
# Check if such token exists in temp folder
|
2012-09-19 10:48:49 -05:00
|
|
|
res = None
|
|
|
|
siteUrl = self.getSiteUrl()
|
2012-07-09 08:47:38 -05:00
|
|
|
tokenFile = os.path.join(getOsTempFolder(), login)
|
|
|
|
if os.path.exists(tokenFile):
|
|
|
|
f = file(tokenFile)
|
|
|
|
storedToken = f.read()
|
|
|
|
f.close()
|
|
|
|
if storedToken == token:
|
|
|
|
# Generate a new password for this user
|
|
|
|
appyTool = self.appy()
|
|
|
|
user = appyTool.search1('User', login=login, noSecurity=True)
|
|
|
|
newPassword = user.setPassword()
|
|
|
|
# Send the new password by email
|
|
|
|
email = login
|
|
|
|
if not String.EMAIL.match(email):
|
|
|
|
email = user.email
|
|
|
|
subject = self.translate('new_password')
|
|
|
|
map = {'password': newPassword, 'siteUrl': siteUrl}
|
|
|
|
body = self.translate('new_password_body', mapping=map,
|
|
|
|
format='text')
|
|
|
|
sendMail(appyTool, email, subject, body)
|
|
|
|
os.remove(tokenFile)
|
2012-09-19 10:48:49 -05:00
|
|
|
res = self.goto(siteUrl, self.translate('new_password_sent'))
|
|
|
|
if not res:
|
|
|
|
res = self.goto(siteUrl, self.translate('wrong_password_reinit'))
|
|
|
|
return res
|
2012-09-17 14:11:54 -05:00
|
|
|
|
|
|
|
def getSearchValues(self, name, className):
|
|
|
|
'''Gets the possible values for selecting a value for searching field
|
|
|
|
p_name belonging to class p_className.'''
|
|
|
|
klass = self.getAppyClass(className, wrapper=True)
|
|
|
|
method = getattr(klass, name).searchSelect
|
|
|
|
tool = self.appy()
|
2012-12-14 02:23:33 -06:00
|
|
|
if method.__class__.__name__ == 'function':
|
|
|
|
objects = method(tool)
|
|
|
|
else:
|
|
|
|
objects = method.__get__(tool)(tool)
|
2012-09-17 14:11:54 -05:00
|
|
|
return [(o.uid, o) for o in objects]
|
2009-06-29 07:06:01 -05:00
|
|
|
# ------------------------------------------------------------------------------
|