Added backup/restore scripts (wrappers around repozo). The backup script has the possibility to execute a tool method on a Appy application.
This commit is contained in:
parent
500637eb53
commit
db8ad18c5f
340
bin/backup.py
Normal file
340
bin/backup.py
Normal file
|
@ -0,0 +1,340 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import sys, time, os, os.path, smtplib, socket, popen2, shutil
|
||||
from optparse import OptionParser
|
||||
import ZODB.FileStorage
|
||||
import ZODB.serialize
|
||||
from DateTime import DateTime
|
||||
from StringIO import StringIO
|
||||
folderName = os.path.dirname(__file__)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class BackupError(Exception): pass
|
||||
ERROR_CODE = 1
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class ZodbBackuper:
|
||||
'''This backuper will run every night (after 00.00). Every night excepted
|
||||
Sunday, it will perform an incremental backup. Every Sunday, the script
|
||||
will pack the ZODB, perform a full backup, and, if successful, remove all
|
||||
previous (full and incremental) backups.'''
|
||||
fullBackupExts = ('.fs', '.fsz')
|
||||
toRemoveExts = ('.doc', '.pdf', '.rtf', '.odt')
|
||||
def __init__(self, storageLocation, backupFolder, options):
|
||||
self.storageLocation = storageLocation
|
||||
self.backupFolder = backupFolder
|
||||
self.options = options
|
||||
# Unwrap some options directly on self.
|
||||
self.repozo = options.repozo or './repozo.py'
|
||||
self.zopectl = options.zopectl or './zopectl'
|
||||
self.logFile = file(options.logFile, 'a')
|
||||
self.logMem = StringIO() # We keep a log of the last script execution,
|
||||
# so we can send this info by email.
|
||||
self.emails = options.emails
|
||||
self.tempFolder = options.tempFolder
|
||||
self.logsBackupFolder = options.logsBackupFolder
|
||||
self.zopeUser = options.zopeUser
|
||||
self.keepSeconds = int(options.keepSeconds)
|
||||
|
||||
def log(self, msg):
|
||||
for logPlace in (self.logFile, self.logMem):
|
||||
logPlace.write(msg)
|
||||
logPlace.write('\n')
|
||||
|
||||
def executeCommand(self, cmd):
|
||||
'''Executes command p_cmd.'''
|
||||
w = self.log
|
||||
w('Executing "%s"...' % cmd)
|
||||
outstream, instream = popen2.popen4(cmd)
|
||||
outTxt = outstream.readlines()
|
||||
instream.close()
|
||||
outstream.close()
|
||||
for line in outTxt:
|
||||
w(line[:-1])
|
||||
w('Done.')
|
||||
|
||||
def packZodb(self):
|
||||
'''Packs the ZODB and keeps one week history.'''
|
||||
storage = ZODB.FileStorage.FileStorage(self.storageLocation)
|
||||
#storage.pack(time.time()-(7*24*60*60), ZODB.serialize.referencesf)
|
||||
storage.pack(time.time()-self.keepSeconds, ZODB.serialize.referencesf)
|
||||
for fileSuffix in ('', '.index'):
|
||||
fileName = self.storageLocation + fileSuffix
|
||||
os.system('chown %s %s' % (self.zopeUser, fileName))
|
||||
|
||||
folderCreateError = 'Could not create backup folder. Backup of log ' \
|
||||
'files will not take place. %s'
|
||||
def backupLogs(self):
|
||||
w = self.log
|
||||
if not os.path.exists(self.logsBackupFolder):
|
||||
# Try to create the folder when to store backups of the log files
|
||||
try:
|
||||
w('Try to create backup folder for logs "%s"...' % \
|
||||
self.logsBackupFolder)
|
||||
os.mkdir(self.logsBackupFolder)
|
||||
except IOError, ioe:
|
||||
w(folderCreateError % str(ioe))
|
||||
except OSError, oe:
|
||||
w(folderCreateError % str(oe))
|
||||
if os.path.exists(self.logsBackupFolder):
|
||||
# Ok, we can make the backup of the log files.
|
||||
# Get the folder where logs lie
|
||||
d = os.path.dirname
|
||||
j = os.path.join
|
||||
logsFolder = j(d(d(self.storageLocation)), 'log')
|
||||
for logFileName in os.listdir(logsFolder):
|
||||
if logFileName.endswith('.log'):
|
||||
backupTime = DateTime().strftime('%Y_%m_%d_%H_%M')
|
||||
parts = os.path.splitext(logFileName)
|
||||
copyFileName = '%s.%s%s' % (parts[0], backupTime, parts[1])
|
||||
absCopyFileName = j(self.logsBackupFolder, copyFileName)
|
||||
absLogFileName = j(logsFolder, logFileName)
|
||||
w('Moving "%s" to "%s"...' % (absLogFileName,
|
||||
absCopyFileName))
|
||||
shutil.copyfile(absLogFileName, absCopyFileName)
|
||||
os.remove(absLogFileName)
|
||||
# I do a "copy" + a "remove" instead of a "rename" because
|
||||
# a "rename" fails if the source and dest files are on
|
||||
# different physical devices.
|
||||
|
||||
def getDate(self, dateString):
|
||||
'''Returns a DateTime instance from p_dateString, which has the form
|
||||
YYYY-MM-DD-HH-MM-SS.'''
|
||||
return DateTime('%s/%s/%s %s:%s:%s' % tuple(dateString.split('-')))
|
||||
|
||||
def removeOldBackups(self):
|
||||
'''This method removes all files (full & incremental backups) that are
|
||||
older than the last full backup.'''
|
||||
w = self.log
|
||||
# Determine date of the oldest full backup
|
||||
oldestFullBackupDate = eighties = DateTime('1980/01/01')
|
||||
for backupFile in os.listdir(self.backupFolder):
|
||||
fileDate, ext = os.path.splitext(backupFile)
|
||||
if ext in self.fullBackupExts:
|
||||
# I have found a full backup
|
||||
fileDate = self.getDate(fileDate)
|
||||
if fileDate > oldestFullBackupDate:
|
||||
oldestFullBackupDate = fileDate
|
||||
# Remove all backup files older that oldestFullBackupDate
|
||||
if oldestFullBackupDate != eighties:
|
||||
w('Last full backup date: %s' % str(oldestFullBackupDate))
|
||||
for backupFile in os.listdir(self.backupFolder):
|
||||
fileDate, ext = os.path.splitext(backupFile)
|
||||
if self.getDate(fileDate) < oldestFullBackupDate:
|
||||
fullFileName = '%s/%s' % (self.backupFolder, backupFile)
|
||||
w('Removing old backup file %s...' % fullFileName)
|
||||
os.remove(fullFileName)
|
||||
|
||||
def sendEmails(self):
|
||||
'''Send content of self.logMem to self.emails.'''
|
||||
w = self.log
|
||||
subject = 'Backup notification.'
|
||||
msg = 'From: %s\nSubject: %s\n\n%s' % (self.options.fromAddress,
|
||||
subject, self.logMem.getvalue())
|
||||
try:
|
||||
w('> Sending mail notifications to %s...' % self.emails)
|
||||
server, port = self.options.smtpServer.split(':')
|
||||
smtpServer = smtplib.SMTP(server, port=int(port))
|
||||
res = smtpServer.sendmail(self.options.fromAddress,
|
||||
self.emails.split(','), msg)
|
||||
if res:
|
||||
w('Could not send mail to some recipients. %s' % str(res))
|
||||
w('Done.')
|
||||
except socket.error, se:
|
||||
w('Could not connect to SMTP server %s (%s).' % \
|
||||
(self.options.smtpServer, str(se)))
|
||||
|
||||
def removeTempFiles(self):
|
||||
'''For EGW, OO produces temp files that EGW tries do delete at the time
|
||||
they are produced. But in some cases EGW can't do it (ie Zope runs
|
||||
with a given user and OO runs with root and produces files that can't
|
||||
be deleted by the user running Zope). This is why in this script we
|
||||
remove the temp files that could not be removed by Zope.'''
|
||||
w = self.log
|
||||
w('Removing temp files in "%s"...' % self.tempFolder)
|
||||
pdfCount = 0
|
||||
docCount = 0
|
||||
for fileName in os.listdir(self.tempFolder):
|
||||
ext = os.path.splitext(fileName)[1]
|
||||
if ext in self.toRemoveExts:
|
||||
exec '%sCount += 1' % ext[1:]
|
||||
fullFileName = os.path.join(self.tempFolder, fileName)
|
||||
#w('Removing "%s"...' % fullFileName)
|
||||
try:
|
||||
os.remove(fullFileName)
|
||||
except OSError, oe:
|
||||
w('Could not remove "%s" (%s).' % (fullFileName, str(oe)))
|
||||
w('%d PDF document(s) removed.' % pdfCount)
|
||||
w('%d Word document(s) removed.' % docCount)
|
||||
|
||||
def run(self):
|
||||
w = self.log
|
||||
startTime = time.time()
|
||||
w('\n****** Backup launched at %s ******' % str(time.asctime()))
|
||||
# Shutdown the Zope instance
|
||||
w('> Shutting down Zope instance...')
|
||||
self.executeCommand('%s stop' % self.zopectl)
|
||||
# If we are on the "full backup day", let's pack the ZODB first
|
||||
if time.asctime().startswith(self.options.dayFullBackup):
|
||||
w('> Day is "%s", packing the ZODB...' % self.options.dayFullBackup)
|
||||
self.packZodb()
|
||||
w('> Make a backup of log files...')
|
||||
self.backupLogs()
|
||||
w('Done.')
|
||||
# Do the backup with repozo
|
||||
w('> Performing backup...')
|
||||
self.executeCommand('%s %s -BvzQ -r %s -f %s' % (self.options.python,
|
||||
self.repozo, self.backupFolder, self.storageLocation))
|
||||
# Remove previous full backups.
|
||||
self.removeOldBackups()
|
||||
# If a command is specified, run Zope to execute this command
|
||||
if self.options.command:
|
||||
w('> Executing command "%s"...' % self.options.command)
|
||||
jobScript = '%s/job.py' % folderName
|
||||
cmd = '%s run %s %s' % (self.zopectl, jobScript,
|
||||
self.options.command)
|
||||
self.executeCommand(cmd)
|
||||
# Start the instance again, in normal mode.
|
||||
w('> Restarting Zope instance...')
|
||||
self.executeCommand('%s start' % self.zopectl)
|
||||
self.removeTempFiles()
|
||||
stopTime = time.time()
|
||||
w('Done in %d minute(s).' % ((stopTime-startTime)/60))
|
||||
if self.emails:
|
||||
self.sendEmails()
|
||||
self.logFile.close()
|
||||
print self.logMem.getvalue()
|
||||
self.logMem.close()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class ZodbBackupScript:
|
||||
'''usage: python backup.py storageLocation backupFolder [options]
|
||||
storageLocation is the path to a ZODB database (file storage) (ie
|
||||
/opt/ZopeInstance/var/Data.fs);
|
||||
backupFolder is a folder exclusively dedicated for storing backups
|
||||
of the mentioned storage (ie /data/zodbbackups).'''
|
||||
|
||||
weekDays = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')
|
||||
def checkArgs(self, options, args):
|
||||
'''Check that the scripts arguments are correct.'''
|
||||
# Do I have the correct number of args?
|
||||
if len(args) != 2:
|
||||
raise BackupError('Wrong number of arguments.')
|
||||
# Check storageLocation
|
||||
if not os.path.exists(args[0]) or not os.path.isfile(args[0]):
|
||||
raise BackupError('"%s" does not exist or is not a file.' % args[0])
|
||||
# Check backupFolder
|
||||
if not os.path.isdir(args[1]):
|
||||
raise BackupError('"%s" does not exist or is not a folder.'%args[1])
|
||||
# Try to create a file in this folder to check if we have write
|
||||
# access in it.
|
||||
fileName = '%s/%s.tmp' % (args[1], str(time.time()))
|
||||
try:
|
||||
f = file(fileName, 'w')
|
||||
f.write('Hello.')
|
||||
f.close()
|
||||
os.remove(fileName)
|
||||
except OSError, oe:
|
||||
raise BackupError('I do not have the right to write in ' \
|
||||
'folder "%s".' % args[1])
|
||||
# Check temp folder
|
||||
if not os.path.isdir(options.tempFolder):
|
||||
raise BackupError('Temp folder "%s" does not exist or is not ' \
|
||||
'a folder.' % options.tempFolder)
|
||||
# Check day of week
|
||||
if options.dayFullBackup not in self.weekDays:
|
||||
raise BackupError(
|
||||
'Day of week must be one of %s' % str(self.weekDays))
|
||||
# Check command format
|
||||
if options.command:
|
||||
parts = options.command.split(':')
|
||||
if len(parts) not in (3,4):
|
||||
raise BackupError('Command format must be ' \
|
||||
'<PloneInstancePath>:<ApplicationName>:<ToolMethodName>' \
|
||||
'[:<args>]')
|
||||
|
||||
def run(self):
|
||||
optParser = OptionParser(usage=ZodbBackupScript.__doc__)
|
||||
optParser.add_option("-p", "--python", dest="python",
|
||||
help="The path to the Python interpreter running "\
|
||||
"Zope",
|
||||
default='python2.4',metavar="REPOZO",type='string')
|
||||
optParser.add_option("-r", "--repozo", dest="repozo",
|
||||
help="The path to repozo.py",
|
||||
default='', metavar="REPOZO", type='string')
|
||||
optParser.add_option("-z", "--zopectl", dest="zopectl",
|
||||
help="The path to Zope instance's zopectl script",
|
||||
default='', metavar="ZOPECTL", type='string')
|
||||
optParser.add_option("-l", "--logfile", dest="logFile",
|
||||
help="Log file where this script will append " \
|
||||
"output (defaults to ./backup.log)",
|
||||
default='./backup.log', metavar="LOGFILE",
|
||||
type='string')
|
||||
optParser.add_option("-d", "--day-full-backup", dest="dayFullBackup",
|
||||
help="Day of the week where the full backup " \
|
||||
"must be performed (defaults to 'Sun'). " \
|
||||
"Must be one of %s" % str(self.weekDays),
|
||||
default='Sun', metavar="DAYFULLBACKUP",
|
||||
type='string')
|
||||
optParser.add_option("-e", "--emails", dest="emails",
|
||||
help="Comma-separated list of emails that will " \
|
||||
"receive the log of this script.",
|
||||
default='', metavar="EMAILS", type='string')
|
||||
optParser.add_option("-f", "--from-address", dest="fromAddress",
|
||||
help="From address for the sent mails",
|
||||
default='', metavar="FROMADDRESS", type='string')
|
||||
optParser.add_option("-s", "--smtp-server", dest="smtpServer",
|
||||
help="SMTP server and port (ie: localhost:25) " \
|
||||
"for sending mails", default='localhost:25',
|
||||
metavar="SMTPSERVER", type='string')
|
||||
optParser.add_option("-t", "--tempFolder", dest="tempFolder",
|
||||
help="Folder used by OO for producing temp " \
|
||||
"files. Defaults to /tmp.",
|
||||
default='/tmp', metavar="TEMP", type='string')
|
||||
optParser.add_option("-b", "--logsBackupFolder",dest="logsBackupFolder",
|
||||
help="Folder where backups of log files " \
|
||||
"(event.log and Z2.log) will be stored.",
|
||||
default='./logsbackup', metavar="LOGSBACKUPFOLDER",
|
||||
type='string')
|
||||
optParser.add_option("-u", "--user", dest="zopeUser",
|
||||
help="User and group that must own Data.fs. " \
|
||||
"Defaults to zope:www-data. If " \
|
||||
"this script is launched by root, for " \
|
||||
"example, when packing the ZODB this script "\
|
||||
"may produce a new Data.fs that the user " \
|
||||
"running Zope may not be able to read " \
|
||||
"anymore. After packing, this script makes " \
|
||||
"a 'chmod' on Data.fs.",
|
||||
default='zope:www-data', metavar="USER",
|
||||
type='string')
|
||||
optParser.add_option("-k", "--keep-seconds", dest="keepSeconds",
|
||||
help="Number of seconds to leave in the ZODB " \
|
||||
"history when the ZODB is packed.",
|
||||
default='86400', metavar="KEEPSECONDS",
|
||||
type='string')
|
||||
optParser.add_option("-c", "--command", dest="command",
|
||||
help="Command to execute while Zope is running. It must have the " \
|
||||
"following format: <PloneInstancePath>:<ApplicationName>:" \
|
||||
"<ToolMethodName>[:<args>]. <PloneInstancePath> is the path, " \
|
||||
"within Zope, to the Plone Site object (if not at the root of " \
|
||||
"the Zope hierarchy, use '/' as folder separator); " \
|
||||
"<ApplicationName> is the name of the Appy application; " \
|
||||
"<ToolMethodName> is the name of the method to call on the tool " \
|
||||
"in this Appy application; (optional) <args> are the arguments " \
|
||||
"to give to this method (only strings are supported). Several " \
|
||||
"arguments must be separated by '*'.", default='',
|
||||
metavar="COMMAND", type='string')
|
||||
(options, args) = optParser.parse_args()
|
||||
try:
|
||||
self.checkArgs(options, args)
|
||||
backuper = ZodbBackuper(args[0], args[1], options)
|
||||
backuper.run()
|
||||
except BackupError, be:
|
||||
sys.stderr.write(str(be))
|
||||
sys.stderr.write('\n')
|
||||
optParser.print_help()
|
||||
sys.exit(ERROR_CODE)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
ZodbBackupScript().run()
|
||||
# ------------------------------------------------------------------------------
|
51
bin/job.py
Normal file
51
bin/job.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
'''job.py must be executed by a "zopectl run" command and, as single arg,
|
||||
must get a string with the following format:
|
||||
|
||||
<PloneInstancePath>:<ApplicationName>:<ToolMethodName>[:<args>].
|
||||
|
||||
<PloneInstancePath> is the path, within Zope, to the Plone Site object (if
|
||||
not at the root of the Zope hierarchy, use '/' as
|
||||
folder separator);
|
||||
|
||||
<ApplicationName> is the name of the Appy application;
|
||||
|
||||
<ToolMethodName> is the name of the method to call on the tool in this
|
||||
Appy application;
|
||||
|
||||
<args> (optional) are the arguments to give to this method (only strings
|
||||
are supported). Several arguments must be separated by '*'.'''
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
import sys
|
||||
# Check that job.py is called with the right parameters.
|
||||
if len(sys.argv) != 2:
|
||||
print 'job.py was called with wrong args.'
|
||||
print __doc__
|
||||
else:
|
||||
command = sys.argv[1]
|
||||
parts = command.split(':')
|
||||
if len(parts) not in (3,4):
|
||||
print 'job.py was called with wrong args.'
|
||||
print __doc__
|
||||
else:
|
||||
# Unwrap parameters
|
||||
if len(parts) == 3:
|
||||
plonePath, appName, toolMethod = parts
|
||||
args = ()
|
||||
else:
|
||||
plonePath, appName, toolMethod, args = parts
|
||||
# Zope was initialized in a minimal way. Complete Zope and Plone
|
||||
# installation.
|
||||
from Testing import makerequest
|
||||
app = makerequest.makerequest(app)
|
||||
# Get the Plone site
|
||||
ploneSite = app # Initialised with the Zope root object.
|
||||
for elem in plonePath.split('/'):
|
||||
ploneSite = getattr(ploneSite, elem)
|
||||
# Get the tool corresponding to the Appy application
|
||||
toolName = 'portal_%s' % appName.lower()
|
||||
tool = getattr(ploneSite, toolName).appy()
|
||||
# Execute the method on the tool
|
||||
if args: args = args.split('*')
|
||||
exec 'tool.%s(*args)' % toolMethod
|
||||
# ------------------------------------------------------------------------------
|
93
bin/restore.py
Normal file
93
bin/restore.py
Normal file
|
@ -0,0 +1,93 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import sys, time, os, os.path
|
||||
from optparse import OptionParser
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class RestoreError(Exception): pass
|
||||
ERROR_CODE = 1
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class ZodbRestorer:
|
||||
def __init__(self, storageLocation, backupFolder, options):
|
||||
self.storageLocation = storageLocation
|
||||
self.backupFolder = backupFolder
|
||||
self.repozo = options.repozo or 'repozo.py'
|
||||
self.restoreDate = options.date
|
||||
self.python = options.python
|
||||
def run(self):
|
||||
startTime = time.time()
|
||||
datePart = ''
|
||||
if self.restoreDate:
|
||||
datePart = '-D %s' % self.restoreDate
|
||||
repozoCmd = '%s %s -Rv -r %s %s -o %s' % (self.python,
|
||||
self.repozo, self.backupFolder, datePart, self.storageLocation)
|
||||
print 'Executing %s...' % repozoCmd
|
||||
os.system(repozoCmd)
|
||||
stopTime = time.time()
|
||||
print 'Done in %d minutes.' % ((stopTime-startTime)/60)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class ZodbRestoreScript:
|
||||
'''usage: python restore.py storageLocation backupFolder [options]
|
||||
storageLocation is the storage that will be created at the end of the
|
||||
restore process (ie /tmp/Data.hurrah.fs);
|
||||
backupFolder is the folder used for storing storage backups
|
||||
(ie /data/zodbbackups).'''
|
||||
|
||||
def checkArgs(self, options, args):
|
||||
'''Check that the scripts arguments are correct.'''
|
||||
# Do I have the correct number of args?
|
||||
if len(args) != 2:
|
||||
raise RestoreError('Wrong number of arguments.')
|
||||
# Check that storageLocation does not exist.
|
||||
if os.path.exists(args[0]):
|
||||
raise RestoreError('"%s" exists. Please specify the name of a ' \
|
||||
'new file (in a temp folder for example); you ' \
|
||||
'will move this at the right place in a second '\
|
||||
'step.' % args[0])
|
||||
# Check backupFolder
|
||||
if not os.path.isdir(args[1]):
|
||||
raise RestoreError('"%s" does not exist or is not a folder.' % \
|
||||
args[1])
|
||||
# Try to create storageLocation to check if we have write
|
||||
# access in it.
|
||||
try:
|
||||
f = file(args[0], 'w')
|
||||
f.write('Hello.')
|
||||
f.close()
|
||||
os.remove(args[0])
|
||||
except OSError, oe:
|
||||
raise RestoreError('I do not have the right to write file ' \
|
||||
'"%s".' % args[0])
|
||||
|
||||
def run(self):
|
||||
optParser = OptionParser(usage=ZodbRestoreScript.__doc__)
|
||||
optParser.add_option("-p", "--python", dest="python",
|
||||
help="The path to the Python interpreter running "\
|
||||
"Zope",
|
||||
default='python2.4',metavar="REPOZO",type='string')
|
||||
optParser.add_option("-r", "--repozo", dest="repozo",
|
||||
help="The path to repozo.py",
|
||||
default='', metavar="REPOZO", type='string')
|
||||
optParser.add_option("-d", "--date", dest="date",
|
||||
help="Date of the image to restore (format=" \
|
||||
"YYYY-MM-DD-HH-MM-SS). It is UTC time, " \
|
||||
"not local time. If you don't specify this " \
|
||||
"option, it defaults to now. If specified, " \
|
||||
"hour, minute, and second parts are optional",
|
||||
default='', metavar="DATE", type='string')
|
||||
(options, args) = optParser.parse_args()
|
||||
try:
|
||||
self.checkArgs(options, args)
|
||||
backuper = ZodbRestorer(args[0], args[1], options)
|
||||
backuper.run()
|
||||
except RestoreError, be:
|
||||
sys.stderr.write(str(be))
|
||||
sys.stderr.write('\n')
|
||||
optParser.print_help()
|
||||
sys.exit(ERROR_CODE)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
ZodbRestoreScript().run()
|
||||
# ------------------------------------------------------------------------------
|
|
@ -108,7 +108,7 @@ class ToolMixin(AbstractMixin):
|
|||
_sortFields = {'title': 'sortable_title'}
|
||||
def executeQuery(self, contentType, flavourNumber=1, searchName=None,
|
||||
startNumber=0, search=None, remember=False,
|
||||
brainsOnly=False, maxResults=None):
|
||||
brainsOnly=False, maxResults=None, noSecurity=False):
|
||||
'''Executes a query on a given p_contentType (or several, separated
|
||||
with commas) in Plone's portal_catalog. Portal types are from the
|
||||
flavour numbered p_flavourNumber. If p_searchName is specified, it
|
||||
|
@ -134,7 +134,10 @@ class ToolMixin(AbstractMixin):
|
|||
specified, the method returns maximum
|
||||
self.getNumberOfResultsPerPage(). The method returns all objects if
|
||||
p_maxResults equals string "NO_LIMIT". p_maxResults is ignored if
|
||||
p_brainsOnly is True.'''
|
||||
p_brainsOnly is True.
|
||||
|
||||
If p_noSecurity is True, it gets all the objects, even those that the
|
||||
currently logged user can't see.'''
|
||||
# Is there one or several content types ?
|
||||
if contentType.find(',') != -1:
|
||||
# Several content types are specified
|
||||
|
@ -194,11 +197,14 @@ class ToolMixin(AbstractMixin):
|
|||
# (for searchability) and can't be used for sorting.
|
||||
if self._sortFields.has_key(sb): sb = self._sortFields[sb]
|
||||
params['sort_on'] = sb
|
||||
brains = self.portal_catalog.searchResults(**params)
|
||||
# Determine what method to call on the portal catalog
|
||||
if noSecurity: catalogMethod = 'unrestrictedSearchResults'
|
||||
else: catalogMethod = 'searchResults'
|
||||
exec 'brains = self.portal_catalog.%s(**params)' % catalogMethod
|
||||
if brainsOnly: return brains
|
||||
if not maxResults: maxResults = self.getNumberOfResultsPerPage()
|
||||
elif maxResults == 'NO_LIMIT': maxResults = None
|
||||
res = SomeObjects(brains, maxResults, startNumber)
|
||||
res = SomeObjects(brains, maxResults, startNumber,noSecurity=noSecurity)
|
||||
res.brainsToObjects()
|
||||
# In some cases (p_remember=True), we need to keep some information
|
||||
# about the query results in the current user's session, allowing him
|
||||
|
|
|
@ -262,12 +262,14 @@ class AbstractWrapper:
|
|||
replaced with normal chars.'''
|
||||
return unicodedata.normalize('NFKD', s).encode("ascii","ignore")
|
||||
|
||||
def search(self, klass, sortBy='', maxResults=None, **fields):
|
||||
def search(self, klass, sortBy='', maxResults=None,
|
||||
noSecurity=False, **fields):
|
||||
'''Searches objects of p_klass. p_sortBy must be the name of an indexed
|
||||
field (declared with indexed=True); every param in p_fields must
|
||||
take the name of an indexed field and take a possible value of this
|
||||
field. You can optionally specify a maximum number of results in
|
||||
p_maxResults.'''
|
||||
p_maxResults. If p_noSecurity is specified, you get all objects,
|
||||
even if the logged user does not have the permission to view it.'''
|
||||
# Find the content type corresponding to p_klass
|
||||
flavour = self.flavour
|
||||
contentType = flavour.o.getPortalType(klass)
|
||||
|
@ -278,7 +280,7 @@ class AbstractWrapper:
|
|||
# If I let maxResults=None, only a subset of the results will be
|
||||
# returned by method executeResult.
|
||||
res = self.tool.o.executeQuery(contentType,flavour.number,search=search,
|
||||
maxResults=maxResults)
|
||||
maxResults=maxResults, noSecurity=noSecurity)
|
||||
return [o.appy() for o in res['objects']]
|
||||
|
||||
def count(self, klass, **fields):
|
||||
|
|
12
gen/utils.py
12
gen/utils.py
|
@ -175,7 +175,8 @@ class AppyRequest:
|
|||
class SomeObjects:
|
||||
'''Represents a bunch of objects retrieved from a reference or a query in
|
||||
portal_catalog.'''
|
||||
def __init__(self, objects=None, batchSize=None, startNumber=0):
|
||||
def __init__(self, objects=None, batchSize=None, startNumber=0,
|
||||
noSecurity=False):
|
||||
self.objects = objects or [] # The objects
|
||||
self.totalNumber = len(self.objects) # self.objects may only represent a
|
||||
# part of all available objects.
|
||||
|
@ -183,11 +184,16 @@ class SomeObjects:
|
|||
# self.objects.
|
||||
self.startNumber = startNumber # The index of first object in
|
||||
# self.objects in the whole list.
|
||||
self.noSecurity = noSecurity
|
||||
def brainsToObjects(self):
|
||||
'''self.objects has been populated from brains from the portal_catalog,
|
||||
not from True objects. This method turns them (or some of them
|
||||
depending on batchSize and startNumber) into real objects.'''
|
||||
depending on batchSize and startNumber) into real objects.
|
||||
If self.noSecurity is True, it gets the objects even if the logged
|
||||
user does not have the right to get them.'''
|
||||
start = self.startNumber
|
||||
brains = self.objects[start:start + self.batchSize]
|
||||
self.objects = [b.getObject() for b in brains]
|
||||
if self.noSecurity: getMethod = '_unrestrictedGetObject'
|
||||
else: getMethod = 'getObject'
|
||||
self.objects = [getattr(b, getMethod)() for b in brains]
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in a new issue