• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / Products.ZCatalog / 3979545908

pending completion
3979545908

push

github

GitHub
Drop support for Python 2.7, 3.5, 3.6. (#143)

817 of 1057 branches covered (77.29%)

Branch coverage included in aggregate %.

49 of 49 new or added lines in 12 files covered. (100.0%)

3144 of 3538 relevant lines covered (88.86%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

47.79
/src/Products/ZCatalog/ZCatalog.py
1
##############################################################################
2
#
3
# Copyright (c) 2002 Zope Foundation and Contributors.
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE
11
#
12
##############################################################################
13
""" ZCatalog product
1✔
14
"""
15

16
import logging
1✔
17
import operator
1✔
18
import sys
1✔
19
import time
1✔
20
from time import process_time
1✔
21
from urllib.parse import quote
1✔
22

23
import transaction
1✔
24
from AccessControl.class_init import InitializeClass
1✔
25
from AccessControl.Permission import getPermissionIdentifier
1✔
26
from AccessControl.Permissions import manage_zcatalog_entries
1✔
27
from AccessControl.Permissions import manage_zcatalog_indexes
1✔
28
from AccessControl.Permissions import search_zcatalog
1✔
29
from AccessControl.SecurityInfo import ClassSecurityInfo
1✔
30
from Acquisition import Implicit
1✔
31
from Acquisition import aq_base
1✔
32
from Acquisition import aq_parent
1✔
33
from App.special_dtml import DTMLFile
1✔
34
from DateTime.DateTime import DateTime
1✔
35
from DocumentTemplate._DocumentTemplate import InstanceDict
1✔
36
from DocumentTemplate._DocumentTemplate import TemplateDict
1✔
37
from DocumentTemplate.DT_Util import Eval
1✔
38
from DocumentTemplate.security import RestrictedDTML
1✔
39
from OFS.Folder import Folder
1✔
40
from OFS.ObjectManager import ObjectManager
1✔
41
from Persistence import Persistent
1✔
42
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
1✔
43
from zExceptions import BadRequest
1✔
44
from ZODB.POSException import ConflictError
1✔
45
from zope.interface import implementer
1✔
46
from ZTUtils.Lazy import LazyMap
1✔
47

48
from Products.PluginIndexes.interfaces import IPluggableIndex
1✔
49
from Products.ZCatalog.Catalog import Catalog
1✔
50
from Products.ZCatalog.Catalog import CatalogError
1✔
51
from Products.ZCatalog.interfaces import IZCatalog
1✔
52
from Products.ZCatalog.plan import PriorityMap
1✔
53
from Products.ZCatalog.ProgressHandler import ZLogHandler
1✔
54
from Products.ZCatalog.ZCatalogIndexes import ZCatalogIndexes
1✔
55

56

57
_marker = object()
1✔
58
LOG = logging.getLogger('Zope.ZCatalog')
1✔
59

60
manage_addZCatalogForm = DTMLFile('dtml/addZCatalog', globals())
1✔
61

62

63
def manage_addZCatalog(self, id, title, vocab_id=None, REQUEST=None):
1✔
64
    """Add a ZCatalog object. The vocab_id argument is ignored.
65
    """
66
    id = str(id)
1✔
67
    title = str(title)
1✔
68
    c = ZCatalog(id, title, container=self)
1✔
69
    self._setObject(id, c)
1✔
70
    if REQUEST is not None:
1!
71
        return self.manage_main(self, REQUEST, update_menu=1)
×
72

73

74
@implementer(IZCatalog)
1✔
75
class ZCatalog(Folder, Persistent, Implicit):
1✔
76
    """ZCatalog object
77

78
    A ZCatalog contains arbirary index like references to Zope
79
    objects.  ZCatalog's can index either 'Field' values of object, or
80
    'Text' values.
81

82
    ZCatalog does not store references to the objects themselves, but
83
    rather to a unique identifier that defines how to get to the
84
    object.  In Zope, this unique idenfier is the object's relative
85
    path to the ZCatalog (since two Zope object's cannot have the same
86
    URL, this is an excellent unique qualifier in Zope).
87

88
    Most of the dirty work is done in the _catalog object, which is an
89
    instance of the Catalog class.  An interesting feature of this
90
    class is that it is not Zope specific.  You can use it in any
91
    Python program to catalog objects.
92
    """
93

94
    security = ClassSecurityInfo()
1✔
95
    security.setPermissionDefault(manage_zcatalog_entries, ('Manager', ))
1✔
96
    security.setPermissionDefault(manage_zcatalog_indexes, ('Manager', ))
1✔
97
    security.setPermissionDefault(search_zcatalog, ('Anonymous', 'Manager'))
1✔
98
    security.declareProtected(search_zcatalog, 'all_meta_types')
1✔
99

100
    meta_type = 'ZCatalog'
1✔
101
    zmi_icon = 'fa fa-search'
1✔
102

103
    manage_options = (
1✔
104
        {'label': 'Contents', 'action': 'manage_main'},
105
        {'label': 'Catalog', 'action': 'manage_catalogView'},
106
        {'label': 'Indexes', 'action': 'manage_catalogIndexes'},
107
        {'label': 'Metadata', 'action': 'manage_catalogSchema'},
108
        {'label': 'Find Objects', 'action': 'manage_catalogFind'},
109
        {'label': 'Advanced', 'action': 'manage_catalogAdvanced'},
110
        {'label': 'Query Report', 'action': 'manage_catalogReport'},
111
        {'label': 'Query Plan', 'action': 'manage_catalogPlan'},
112
        {'label': 'Security', 'action': 'manage_access'},
113
        {'label': 'Ownership', 'action': 'manage_owner'},
114
    )
115

116
    security.declareProtected(manage_zcatalog_entries, 'manage_main')
1✔
117

118
    security.declareProtected(manage_zcatalog_entries, 'manage_catalogView')
1✔
119
    manage_catalogView = DTMLFile('dtml/catalogView', globals())
1✔
120

121
    security.declareProtected(manage_zcatalog_entries, 'manage_catalogIndexes')
1✔
122
    manage_catalogIndexes = PageTemplateFile('zpt/catalogIndexes', globals())
1✔
123

124
    security.declareProtected(manage_zcatalog_entries, 'manage_catalogSchema')
1✔
125
    manage_catalogSchema = DTMLFile('dtml/catalogSchema', globals())
1✔
126

127
    security.declareProtected(manage_zcatalog_entries, 'manage_catalogFind')
1✔
128
    manage_catalogFind = DTMLFile('dtml/catalogFind', globals())
1✔
129

130
    security.declareProtected(manage_zcatalog_entries,
1✔
131
                              'manage_catalogAdvanced')
132
    manage_catalogAdvanced = DTMLFile('dtml/catalogAdvanced', globals())
1✔
133

134
    security.declareProtected(manage_zcatalog_entries, 'manage_catalogReport')
1✔
135
    manage_catalogReport = DTMLFile('dtml/catalogReport', globals())
1✔
136

137
    security.declareProtected(manage_zcatalog_entries, 'manage_catalogPlan')
1✔
138
    manage_catalogPlan = DTMLFile('dtml/catalogPlan', globals())
1✔
139

140
    security.declareProtected(manage_zcatalog_entries,
1✔
141
                              'manage_objectInformation')
142
    manage_objectInformation = DTMLFile('dtml/catalogObjectInformation',
1✔
143
                                        globals())
144

145
    # this stuff is so the find machinery works
146
    meta_types = ()  # Sub-object types that are specific to this object
1✔
147

148
    Indexes = ZCatalogIndexes()
1✔
149

150
    threshold = 10000
1✔
151
    long_query_time = 0.1
1✔
152

153
    # vocabulary and vocab_id are left for backwards
154
    # compatibility only, they are not used anymore
155
    vocabulary = None
1✔
156
    vocab_id = ''
1✔
157

158
    _v_total = 0
1✔
159
    _v_transaction = None
1✔
160

161
    def __init__(self, id, title='', vocab_id=None, container=None):
1✔
162
        # ZCatalog no longer cares about vocabularies
163
        # so the vocab_id argument is ignored (Casey)
164

165
        if container is not None:
1✔
166
            self = self.__of__(container)
1✔
167
        self.id = id
1✔
168
        self.title = title
1✔
169
        self.threshold = 10000
1✔
170
        self.long_query_time = 0.1  # in seconds
1✔
171
        self._v_total = 0
1✔
172
        self._catalog = Catalog()
1✔
173

174
    def __len__(self):
1✔
175
        return len(self._catalog)
1✔
176

177
    @security.protected(manage_zcatalog_entries)
1✔
178
    def manage_edit(self, RESPONSE, URL1, threshold=1000, REQUEST=None):
1✔
179
        """ edit the catalog """
180
        if not isinstance(threshold, int):
×
181
            threshold = int(threshold)
×
182
        self.threshold = threshold
×
183

184
        RESPONSE.redirect(
×
185
            URL1 + '/manage_main?manage_tabs_message=Catalog%20Changed')
186

187
    @security.protected(manage_zcatalog_entries)
1✔
188
    def manage_subbingToggle(self, REQUEST, RESPONSE, URL1):
1✔
189
        """ toggle subtransactions """
190
        if self.threshold:
×
191
            self.threshold = None
×
192
        else:
193
            self.threshold = 10000
×
194

195
        RESPONSE.redirect(
×
196
            URL1
197
            + '/manage_catalogAdvanced?manage_tabs_message=Catalog%20Changed')
198

199
    @security.protected(manage_zcatalog_entries)
1✔
200
    def manage_catalogObject(self, REQUEST, RESPONSE, URL1, urls=None):
1✔
201
        """ index Zope object(s) that 'urls' point to """
202
        if urls:
1!
203
            if isinstance(urls, str):
1!
204
                urls = (urls, )
×
205

206
            for url in urls:
1✔
207
                obj = self.resolve_path(url)
1✔
208
                if obj is None and hasattr(self, 'REQUEST'):
1!
209
                    obj = self.resolve_url(url, REQUEST)
×
210
                if obj is not None:
1!
211
                    self.catalog_object(obj, url)
1✔
212

213
        RESPONSE.redirect(
1✔
214
            URL1
215
            + '/manage_catalogView?manage_tabs_message=Object%20Cataloged')
216

217
    @security.protected(manage_zcatalog_entries)
1✔
218
    def manage_uncatalogObject(self, REQUEST, RESPONSE, URL1, urls=None):
1✔
219
        """ removes Zope object(s) 'urls' from catalog """
220

221
        if urls:
×
222
            if isinstance(urls, str):
×
223
                urls = (urls, )
×
224

225
            for url in urls:
×
226
                self.uncatalog_object(url)
×
227

228
        RESPONSE.redirect(
×
229
            URL1
230
            + '/manage_catalogView?manage_tabs_message=Object%20Uncataloged')
231

232
    @security.protected(manage_zcatalog_entries)
1✔
233
    def manage_catalogReindex(self, REQUEST, RESPONSE, URL1):
1✔
234
        """ clear the catalog, then re-index everything """
235

236
        elapse = time.time()
×
237
        c_elapse = process_time()
×
238

239
        pgthreshold = self._getProgressThreshold()
×
240
        handler = (pgthreshold > 0) and ZLogHandler(pgthreshold) or None
×
241
        self.refreshCatalog(clear=1, pghandler=handler)
×
242

243
        elapse = time.time() - elapse
×
244
        c_elapse = process_time() - c_elapse
×
245

246
        RESPONSE.redirect(
×
247
            URL1
248
            + '/manage_catalogAdvanced?manage_tabs_message='
249
            + quote('Catalog Updated \n'
250
                    'Total time: %r\n'
251
                    'Total CPU time: %r' % (elapse, c_elapse)))
252

253
    @security.protected(manage_zcatalog_entries)
1✔
254
    def refreshCatalog(self, clear=0, pghandler=None):
1✔
255
        """ re-index everything we can find """
256

257
        cat = self._catalog
1✔
258
        paths = cat.paths.values()
1✔
259
        if clear:
1!
260
            paths = tuple(paths)
×
261
            cat.clear()
×
262

263
        num_objects = len(paths)
1✔
264
        if pghandler:
1!
265
            pghandler.init('Refreshing catalog: %s' % self.absolute_url(1),
×
266
                           num_objects)
267

268
        for i in range(num_objects):
1✔
269
            if pghandler:
1!
270
                pghandler.report(i)
×
271

272
            p = paths[i]
1✔
273
            obj = self.resolve_path(p)
1✔
274
            if obj is None:
1!
275
                obj = self.resolve_url(p, self.REQUEST)
×
276
            if obj is not None:
1!
277
                try:
1✔
278
                    self.catalog_object(obj, p, pghandler=pghandler)
1✔
279
                except ConflictError:
×
280
                    raise
×
281
                except Exception:
×
282
                    LOG.error('Recataloging object at %s failed', p,
×
283
                              exc_info=sys.exc_info())
284

285
        if pghandler:
1!
286
            pghandler.finish()
×
287

288
    @security.protected(manage_zcatalog_entries)
1✔
289
    def manage_catalogClear(self, REQUEST=None, RESPONSE=None, URL1=None):
1✔
290
        """ clears the whole enchilada """
291
        self._catalog.clear()
×
292

293
        if REQUEST and RESPONSE:
×
294
            RESPONSE.redirect(URL1 + (
×
295
                '/manage_catalogAdvanced?'
296
                'manage_tabs_message=Catalog%20Cleared'))
297

298
    @security.protected(manage_zcatalog_entries)
1✔
299
    def manage_catalogFoundItems(self, REQUEST, RESPONSE, URL2, URL1,
1✔
300
                                 obj_metatypes=None,
301
                                 obj_ids=None, obj_searchterm=None,
302
                                 obj_expr=None, obj_mtime=None,
303
                                 obj_mspec=None, obj_roles=None,
304
                                 obj_permission=None):
305
        """ Find object according to search criteria and Catalog them
306
        """
307
        elapse = time.time()
×
308
        c_elapse = process_time()
×
309

310
        obj = REQUEST.PARENTS[1]
×
311
        path = '/'.join(obj.getPhysicalPath())
×
312

313
        self.ZopeFindAndApply(obj,
×
314
                              obj_metatypes=obj_metatypes,
315
                              obj_ids=obj_ids,
316
                              obj_searchterm=obj_searchterm,
317
                              obj_expr=obj_expr,
318
                              obj_mtime=obj_mtime,
319
                              obj_mspec=obj_mspec,
320
                              obj_permission=obj_permission,
321
                              obj_roles=obj_roles,
322
                              search_sub=1,
323
                              REQUEST=REQUEST,
324
                              apply_func=self.catalog_object,
325
                              apply_path=path)
326

327
        elapse = time.time() - elapse
×
328
        c_elapse = process_time() - c_elapse
×
329

330
        RESPONSE.redirect(
×
331
            URL1
332
            + '/manage_catalogView?manage_tabs_message='
333
            + quote('Catalog Updated\n'
334
                    'Total time: %r\n'
335
                    'Total CPU time: %r' % (elapse, c_elapse)))
336

337
    @security.protected(manage_zcatalog_entries)
1✔
338
    def manage_addColumn(self, name, REQUEST=None, RESPONSE=None, URL1=None):
1✔
339
        """ add a column """
340
        self.addColumn(name)
×
341

342
        if REQUEST and RESPONSE:
×
343
            RESPONSE.redirect(
×
344
                URL1
345
                + '/manage_catalogSchema?manage_tabs_message=Column%20Added')
346

347
    @security.protected(manage_zcatalog_entries)
1✔
348
    def manage_delColumn(self, names, REQUEST=None, RESPONSE=None, URL1=None):
1✔
349
        """ delete a column or some columns """
350
        if isinstance(names, str):
×
351
            names = (names, )
×
352

353
        for name in names:
×
354
            self.delColumn(name)
×
355

356
        if REQUEST and RESPONSE:
×
357
            RESPONSE.redirect(
×
358
                URL1
359
                + '/manage_catalogSchema?manage_tabs_message=Column%20Deleted')
360

361
    @security.protected(manage_zcatalog_entries)
1✔
362
    def manage_addIndex(self, name, type, extra=None,
1✔
363
                        REQUEST=None, RESPONSE=None, URL1=None):
364
        """add an index """
365
        self.addIndex(name, type, extra)
1✔
366

367
        if REQUEST and RESPONSE:
1!
368
            RESPONSE.redirect(
×
369
                URL1
370
                + '/manage_catalogIndexes?manage_tabs_message=Index%20Added')
371

372
    @security.protected(manage_zcatalog_entries)
1✔
373
    def manage_delIndex(self, ids=None,
1✔
374
                        REQUEST=None, RESPONSE=None, URL1=None):
375
        """ delete an index or some indexes """
376
        if not ids:
×
377
            raise BadRequest('No items specified')
×
378

379
        if isinstance(ids, str):
×
380
            ids = (ids, )
×
381

382
        for name in ids:
×
383
            self.delIndex(name)
×
384

385
        if REQUEST and RESPONSE:
×
386
            RESPONSE.redirect(
×
387
                URL1
388
                + '/manage_catalogIndexes?manage_tabs_message=Index%20Deleted')
389

390
    @security.protected(manage_zcatalog_entries)
1✔
391
    def manage_clearIndex(self, ids=None,
1✔
392
                          REQUEST=None, RESPONSE=None, URL1=None):
393
        """ clear an index or some indexes """
394
        if not ids:
×
395
            raise BadRequest('No items specified')
×
396

397
        if isinstance(ids, str):
×
398
            ids = (ids, )
×
399

400
        for name in ids:
×
401
            self.clearIndex(name)
×
402

403
        if REQUEST and RESPONSE:
×
404
            RESPONSE.redirect(
×
405
                URL1
406
                + '/manage_catalogIndexes?manage_tabs_message=Index%20Cleared')
407

408
    @security.protected(manage_zcatalog_entries)
1✔
409
    def reindexIndex(self, name, REQUEST, pghandler=None):
1✔
410
        # This method does the actual reindexing of indexes.
411
        # `name` can be the name of an index or a list of names.
412
        idxs = (name, ) if isinstance(name, str) else name
1✔
413
        paths = self._catalog.uids.keys()
1✔
414

415
        i = 0
1✔
416
        if pghandler:
1!
417
            pghandler.init(f'reindexing {idxs}', len(paths))
×
418

419
        for p in paths:
1✔
420
            i += 1
1✔
421
            if pghandler:
1!
422
                pghandler.report(i)
×
423

424
            obj = self.resolve_path(p)
1✔
425
            if obj is None:
1!
426
                obj = self.resolve_url(p, REQUEST)
×
427
            if obj is None:
1!
428
                LOG.error('reindexIndex could not resolve '
×
429
                          'an object from the uid %r.', p)
430
            else:
431
                # don't update metadata when only reindexing a single
432
                # index via the UI
433
                self.catalog_object(obj, p, idxs=idxs,
1✔
434
                                    update_metadata=0, pghandler=pghandler)
435

436
        if pghandler:
1!
437
            pghandler.finish()
×
438

439
    @security.protected(manage_zcatalog_entries)
1✔
440
    def manage_reindexIndex(self, ids=None, REQUEST=None, RESPONSE=None,
1✔
441
                            URL1=None):
442
        """Reindex index(es) from a ZCatalog"""
443
        if not ids:
×
444
            raise BadRequest('No items specified')
×
445

446
        pgthreshold = self._getProgressThreshold()
×
447
        handler = (pgthreshold > 0) and ZLogHandler(pgthreshold) or None
×
448
        self.reindexIndex(ids, REQUEST, handler)
×
449

450
        if REQUEST and RESPONSE:
×
451
            RESPONSE.redirect(
×
452
                URL1
453
                + '/manage_catalogIndexes'
454
                  '?manage_tabs_message=Reindexing%20Performed')
455

456
    @security.private
1✔
457
    def maintain_zodb_cache(self):
1✔
458
        # self.threshold represents the number of times that catalog_object
459
        # needs to be called in order for the catalog to commit
460
        # a subtransaction.
461
        if self.threshold is not None:
1✔
462
            # figure out whether or not to commit a subtransaction.
463
            t = id(transaction.get())
1✔
464
            if t != self._v_transaction:
1✔
465
                self._v_total = 0
1✔
466
            self._v_transaction = t
1✔
467
            self._v_total = self._v_total + 1
1✔
468
            # increment the _v_total counter for this thread only and get a
469
            # reference to the current transaction.  the _v_total counter is
470
            # zeroed if we notice that we're in a different transaction than
471
            # the last one that came by. The semantics here mean that we
472
            # should GC the cache if our threshhold is exceeded within the
473
            # boundaries of the current transaction.
474
            if self._v_total > self.threshold:
1✔
475
                self._p_jar.cacheGC()
1✔
476
                self._v_total = 0
1✔
477
                return True
1✔
478
        return False
1✔
479

480
    @security.protected(manage_zcatalog_entries)
1✔
481
    def catalog_object(self, obj, uid=None, idxs=None, update_metadata=1,
1✔
482
                       pghandler=None):
483
        if uid is None:
1✔
484
            try:
1✔
485
                uid = obj.getPhysicalPath
1✔
486
            except AttributeError:
×
487
                raise CatalogError(
×
488
                    "A cataloged object must support the 'getPhysicalPath' "
489
                    "method if no unique id is provided when cataloging")
490
            else:
491
                uid = '/'.join(uid())
1✔
492
        elif not isinstance(uid, str):
1!
493
            raise CatalogError('The object unique id must be a string.')
×
494

495
        self._catalog.catalogObject(obj, uid, None, idxs,
1✔
496
                                    update_metadata=update_metadata)
497
        # None passed in to catalogObject as third argument indicates
498
        # that we shouldn't try to commit subtransactions within any
499
        # indexing code.  We throw away the result of the call to
500
        # catalogObject (which is a word count), because it's
501
        # worthless to us here.
502

503
        if self.maintain_zodb_cache():
1✔
504
            transaction.savepoint(optimistic=True)
1✔
505
            if pghandler:
1!
506
                pghandler.info('committing subtransaction')
×
507

508
    @security.protected(manage_zcatalog_entries)
1✔
509
    def uncatalog_object(self, uid):
1✔
510
        self._catalog.uncatalogObject(uid)
×
511

512
    @security.protected(search_zcatalog)
1✔
513
    def uniqueValuesFor(self, name):
1✔
514
        # Return the unique values for a given FieldIndex
515
        return self._catalog.uniqueValuesFor(name)
×
516

517
    @security.protected(search_zcatalog)
1✔
518
    def getpath(self, rid):
1✔
519
        # Return the path to a cataloged object given a 'data_record_id_'
520
        return self._catalog.paths[rid]
1✔
521

522
    @security.protected(search_zcatalog)
1✔
523
    def getrid(self, path, default=None):
1✔
524
        # Return 'data_record_id_' the to a cataloged object given a 'path'
525
        return self._catalog.uids.get(path, default)
1✔
526

527
    @security.protected(search_zcatalog)
1✔
528
    def getobject(self, rid, REQUEST=None):
1✔
529
        # Return a cataloged object given a 'data_record_id_'
530
        return aq_parent(self).unrestrictedTraverse(self.getpath(rid))
1✔
531

532
    @security.protected(search_zcatalog)
1✔
533
    def getMetadataForUID(self, uid):
1✔
534
        # return the correct metadata given the uid, usually the path
535
        rid = self._catalog.uids[uid]
1✔
536
        return self._catalog.getMetadataForRID(rid)
1✔
537

538
    @security.protected(search_zcatalog)
1✔
539
    def getIndexDataForUID(self, uid):
1✔
540
        # return the current index contents given the uid, usually the path
541
        rid = self._catalog.uids[uid]
1✔
542
        return self._catalog.getIndexDataForRID(rid)
1✔
543

544
    @security.protected(search_zcatalog)
1✔
545
    def getMetadataForRID(self, rid):
1✔
546
        # return the correct metadata for the cataloged record id
547
        return self._catalog.getMetadataForRID(int(rid))
×
548

549
    @security.protected(search_zcatalog)
1✔
550
    def getIndexDataForRID(self, rid):
1✔
551
        # return the current index contents for the specific rid
552
        return self._catalog.getIndexDataForRID(rid)
×
553

554
    @security.protected(search_zcatalog)
1✔
555
    def getAllBrains(self):
1✔
556
        # return a generator of brains for all cataloged objects
557
        for rid in self._catalog.data:
1✔
558
            yield self._catalog[rid]
1✔
559

560
    @security.protected(search_zcatalog)
1✔
561
    def searchAll(self):
1✔
562
        # the result of a search for all documents
563
        return LazyMap(self._catalog.__getitem__,
1✔
564
                       self._catalog.data.keys(),
565
                       len(self))
566

567
    @security.protected(search_zcatalog)
1✔
568
    def schema(self):
1✔
569
        return self._catalog.schema.keys()
1✔
570

571
    @security.protected(search_zcatalog)
1✔
572
    def indexes(self):
1✔
573
        return self._catalog.indexes.keys()
1✔
574

575
    @security.protected(search_zcatalog)
1✔
576
    def index_objects(self):
1✔
577
        # This method returns unwrapped indexes!
578
        # You should probably use getIndexObjects instead
579
        return self._catalog.indexes.values()
×
580

581
    @security.protected(manage_zcatalog_indexes)
1✔
582
    def getIndexObjects(self):
1✔
583
        # Return a list of wrapped(!) indexes
584
        getIndex = self._catalog.getIndex
×
585
        return [getIndex(name) for name in self.indexes()]
×
586

587
    def _searchable_arguments(self):
1✔
588
        r = {}
×
589
        n = {'optional': 1}
×
590
        for name in self._catalog.indexes.keys():
×
591
            r[name] = n
×
592
        return r
×
593

594
    def _searchable_result_columns(self):
1✔
595
        r = []
×
596
        for name in self._catalog.schema.keys():
×
597
            i = {}
×
598
            i['name'] = name
×
599
            i['type'] = 's'
×
600
            i['parser'] = str
×
601
            i['width'] = 8
×
602
            r.append(i)
×
603
        r.append({'name': 'data_record_id_',
×
604
                  'type': 's',
605
                  'parser': str,
606
                  'width': 8})
607
        return r
×
608

609
    @security.protected(search_zcatalog)
1✔
610
    def searchResults(self, query=None, **kw):
1✔
611
        """Search the catalog.
612

613
        Search terms can be passed as a query or as keyword arguments.
614
        """
615
        return self._catalog.searchResults(query, **kw)
1✔
616

617
    security.declareProtected(search_zcatalog, '__call__')
1✔
618
    __call__ = searchResults
1✔
619

620
    @security.protected(search_zcatalog)
1✔
621
    def search(self, query,
1✔
622
               sort_index=None, reverse=0, limit=None, merge=1):
623
        """Programmatic search interface, use for searching the catalog from
624
        scripts.
625

626
        query:      Dictionary containing catalog query
627
        sort_index: Name of sort index
628
        reverse:    Reverse sort order?
629
        limit:      Limit sorted result count (optimization hint)
630
        merge:      Return merged results (like searchResults) or raw
631
                    results for later merging.
632
        """
633
        if sort_index is not None:
1!
634
            sort_index = self._catalog.indexes[sort_index]
×
635
        return self._catalog.search(
1✔
636
            query, sort_index, reverse, limit, merge)
637

638
    @security.protected(search_zcatalog)
1✔
639
    def valid_roles(self):
1✔
640
        # Return list of valid roles
641
        obj = self
×
642
        roles = set()
×
643
        x = 0
×
644
        while x < 100:
×
645
            if hasattr(obj, '__ac_roles__'):
×
646
                for role in obj.__ac_roles__:
×
647
                    roles.add(role)
×
648
            obj = aq_parent(obj)
×
649
            if obj is None:
×
650
                break
×
651
            x = x + 1
×
652
        roles = sorted(roles)
×
653
        return roles
×
654

655
    @security.protected(manage_zcatalog_entries)
1✔
656
    def ZopeFindAndApply(self, obj, obj_ids=None, obj_metatypes=None,
1✔
657
                         obj_searchterm=None, obj_expr=None,
658
                         obj_mtime=None, obj_mspec=None,
659
                         obj_permission=None, obj_roles=None,
660
                         search_sub=0,
661
                         REQUEST=None, result=None, pre='',
662
                         apply_func=None, apply_path=''):
663
        """Zope Find interface and apply
664

665
        This is a *great* hack.  Zope find just doesn't do what we
666
        need here; the ability to apply a method to all the objects
667
        *as they're found* and the need to pass the object's path into
668
        that method.
669
        """
670

671
        if result is None:
×
672
            result = []
×
673

674
            if obj_metatypes and 'all' in obj_metatypes:
×
675
                obj_metatypes = None
×
676

677
            if obj_mtime and isinstance(obj_mtime, str):
×
678
                obj_mtime = DateTime(obj_mtime).timeTime()
×
679

680
            if obj_permission:
×
681
                obj_permission = getPermissionIdentifier(obj_permission)
×
682

683
            if obj_roles and isinstance(obj_roles, str):
×
684
                obj_roles = [obj_roles]
×
685

686
            if obj_expr:
×
687
                # Setup expr machinations
688
                md = td()
×
689
                obj_expr = (Eval(obj_expr), md, md._push, md._pop)
×
690

691
        base = aq_base(obj)
×
692

693
        if not hasattr(base, 'objectItems'):
×
694
            return result
×
695
        try:
×
696
            items = obj.objectItems()
×
697
        except Exception:
×
698
            return result
×
699

700
        try:
×
701
            add_result = result.append
×
702
        except Exception:
×
703
            raise AttributeError(repr(result))
×
704

705
        for id, ob in items:
×
706
            if pre:
×
707
                p = "{}/{}".format(pre, id)
×
708
            else:
709
                p = id
×
710

711
            dflag = 0
×
712
            if hasattr(ob, '_p_changed') and (ob._p_changed is None):
×
713
                dflag = 1
×
714

715
            bs = aq_base(ob)
×
716

717
            if ((not obj_ids or absattr(bs.id) in obj_ids)
×
718
                    and (not obj_metatypes or (hasattr(bs, 'meta_type')
719
                         and bs.meta_type in obj_metatypes))
720
                    and (not obj_searchterm
721
                         or (hasattr(ob, 'PrincipiaSearchSource')
722
                             and ob.PrincipiaSearchSource().find(obj_searchterm) >= 0))  # noqa: E501
723
                    and (not obj_expr
724
                         or expr_match(ob, obj_expr))
725
                    and (not obj_mtime
726
                         or mtime_match(ob, obj_mtime, obj_mspec))
727
                    and ((not obj_permission or not obj_roles)
728
                         or role_match(ob, obj_permission, obj_roles))):
729
                if apply_func:
×
730
                    apply_func(ob, (apply_path + '/' + p))
×
731
                else:
732
                    add_result((p, ob))
×
733
                    dflag = 0
×
734

735
            if search_sub and hasattr(bs, 'objectItems'):
×
736
                self.ZopeFindAndApply(ob, obj_ids, obj_metatypes,
×
737
                                      obj_searchterm, obj_expr,
738
                                      obj_mtime, obj_mspec,
739
                                      obj_permission, obj_roles,
740
                                      search_sub,
741
                                      REQUEST, result, p,
742
                                      apply_func, apply_path)
743
            if dflag:
×
744
                ob._p_deactivate()
×
745

746
        return result
×
747

748
    @security.protected(search_zcatalog)
1✔
749
    def resolve_url(self, path, REQUEST):
1✔
750
        # Attempt to resolve a url into an object in the Zope
751
        # namespace. The url may be absolute or a catalog path
752
        # style url. If no object is found, None is returned.
753
        # No exceptions are raised.
754
        if REQUEST:
×
755
            script = REQUEST.script
×
756
            if path.find(script) != 0:
×
757
                path = '{}/{}'.format(script, path)
×
758
            try:
×
759
                return REQUEST.resolve_url(path)
×
760
            except Exception:
×
761
                pass
×
762

763
    @security.protected(search_zcatalog)
1✔
764
    def resolve_path(self, path):
1✔
765
        # Attempt to resolve a url into an object in the Zope
766
        # namespace. The url may be absolute or a catalog path
767
        # style url. If no object is found, None is returned.
768
        # No exceptions are raised.
769
        try:
×
770
            return self.unrestrictedTraverse(path)
×
771
        except Exception:
×
772
            pass
×
773

774
    @security.protected(manage_zcatalog_entries)
1✔
775
    def manage_normalize_paths(self, REQUEST):
1✔
776
        """Ensure that all catalog paths are full physical paths
777

778
        This should only be used with ZCatalogs in which all paths can
779
        be resolved with unrestrictedTraverse."""
780

781
        paths = self._catalog.paths
×
782
        uids = self._catalog.uids
×
783
        unchanged = 0
×
784
        fixed = []
×
785
        removed = []
×
786

787
        for path, rid in uids.items():
×
788
            ob = None
×
789
            if path[:1] == '/':
×
790
                ob = self.resolve_url(path[1:], REQUEST)
×
791
            if ob is None:
×
792
                ob = self.resolve_url(path, REQUEST)
×
793
                if ob is None:
×
794
                    removed.append(path)
×
795
                    continue
×
796
            ppath = '/'.join(ob.getPhysicalPath())
×
797
            if path != ppath:
×
798
                fixed.append((path, ppath))
×
799
            else:
800
                unchanged = unchanged + 1
×
801

802
        for path, ppath in fixed:
×
803
            rid = uids[path]
×
804
            del uids[path]
×
805
            paths[rid] = ppath
×
806
            uids[ppath] = rid
×
807
        for path in removed:
×
808
            self.uncatalog_object(path)
×
809

810
    @security.protected(manage_zcatalog_entries)
1✔
811
    def manage_setProgress(self, pgthreshold=0, RESPONSE=None, URL1=None):
1✔
812
        """Set parameter to perform logging of reindexing operations very
813
           'pgthreshold' objects
814
        """
815
        self.pgthreshold = pgthreshold
×
816
        if RESPONSE:
×
817
            RESPONSE.redirect(URL1 + '/manage_catalogAdvanced?'
×
818
                              'manage_tabs_message=Catalog%20Changed')
819

820
    def _getProgressThreshold(self):
1✔
821
        if not hasattr(self, 'pgthreshold'):
×
822
            self.pgthreshold = 0
×
823
        return self.pgthreshold
×
824

825
    # Indexing methods
826

827
    @security.protected(manage_zcatalog_indexes)
1✔
828
    def addIndex(self, name, type, extra=None):
1✔
829
        if IPluggableIndex.providedBy(type):
1✔
830
            self._catalog.addIndex(name, type)
1✔
831
            return
1✔
832

833
        # Convert the type by finding an appropriate product which supports
834
        # this interface by that name.  Bleah
835
        products = ObjectManager.all_meta_types(self,
1✔
836
                                                interfaces=(IPluggableIndex, ))
837

838
        p = None
1✔
839

840
        for prod in products:
1!
841
            if prod['name'] == type:
1✔
842
                p = prod
1✔
843
                break
1✔
844

845
        if p is None:
1!
846
            raise ValueError("Index of type %s not found" % type)
×
847

848
        base = p['instance']
1✔
849

850
        if base is None:
1!
851
            raise ValueError("Index type %s does not support addIndex" % type)
×
852

853
        # This code is *really* lame but every index type has its own
854
        # function signature *sigh* and there is no common way to pass
855
        # additional parameters to the constructor. The suggested way
856
        # for new index types is to use an "extra" record.
857

858
        if 'extra' in base.__init__.__code__.co_varnames:
1!
859
            index = base(name, extra=extra, caller=self)
1✔
860
        elif 'caller' in base.__init__.__code__.co_varnames:
×
861
            index = base(name, caller=self)
×
862
        else:
863
            index = base(name)
×
864

865
        self._catalog.addIndex(name, index)
1✔
866

867
    @security.protected(manage_zcatalog_indexes)
1✔
868
    def availableIndexes(self):
1✔
869
        """Return a sorted list of indexes.
870

871
        Only indexes get returned for which the user has adequate
872
        permission to add them.
873
        """
874
        return sorted(
1✔
875
            self.Indexes.filtered_meta_types(),
876
            key=lambda meta_types: meta_types['name'])
877

878
    @security.protected(manage_zcatalog_indexes)
1✔
879
    def delIndex(self, name):
1✔
880
        self._catalog.delIndex(name)
1✔
881

882
    @security.protected(manage_zcatalog_indexes)
1✔
883
    def clearIndex(self, name):
1✔
884
        self._catalog.getIndex(name).clear()
1✔
885

886
    @security.protected(manage_zcatalog_indexes)
1✔
887
    def addColumn(self, name, default_value=None):
1✔
888
        return self._catalog.addColumn(name, default_value,
1✔
889
                                       threshold=self.threshold)
890

891
    @security.protected(manage_zcatalog_indexes)
1✔
892
    def delColumn(self, name):
1✔
893
        return self._catalog.delColumn(name, threshold=self.threshold)
1✔
894

895
    # Catalog plan methods
896

897
    @security.protected(manage_zcatalog_entries)
1✔
898
    def getCatalogPlan(self):
1✔
899
        """Get a string representation of a query plan"""
900
        pmap = PriorityMap.get_value()
1✔
901
        output = []
1✔
902
        output.append('# query plan dumped at %r\n' % time.asctime())
1✔
903
        output.append('queryplan = {')
1✔
904
        for cid, plan in sorted(pmap.items()):
1✔
905
            output.append('  %s: {' % repr(cid))
1✔
906
            for querykey, details in plan.items():
1✔
907
                if isinstance(details, (frozenset, set)):
1✔
908
                    output.append('    {!r}: {!r},'.format(querykey, details))
1✔
909
                else:
910
                    output.append('    %s: {' % repr(querykey))
1✔
911
                    for indexname, bench in sorted(details.items()):
1✔
912
                        tuplebench = (round(bench[0], 4), ) + bench[1:]
1✔
913
                        output.append('      {!r}:\n      {!r},'.format(
1✔
914
                            indexname, tuplebench))
915
                    output.append('    },')
1✔
916
            output.append('  },')
1✔
917
        output.append('}')
1✔
918
        return '\n'.join(output)
1✔
919

920
    @security.protected(manage_zcatalog_entries)
1✔
921
    def getCatalogReport(self):
1✔
922
        """Query time reporting."""
923
        rval = self._catalog.getCatalogPlan().report()
1✔
924
        rval.sort(key=operator.itemgetter('duration'), reverse=True)
1✔
925
        return rval
1✔
926

927
    @security.protected(manage_zcatalog_entries)
1✔
928
    def manage_resetCatalogReport(self, REQUEST=None):
1✔
929
        """Resets the catalog report."""
930
        self._catalog.getCatalogPlan().reset()
1✔
931

932
        if REQUEST is not None:
1!
933
            REQUEST.response.redirect(REQUEST.URL1 + (
×
934
                '/manage_catalogReport?manage_tabs_message=Report%20cleared'))
935

936
    @security.protected(manage_zcatalog_entries)
1✔
937
    def manage_editCatalogReport(self, long_query_time=0.1, REQUEST=None):
1✔
938
        """Edit the long query time."""
939
        if not isinstance(long_query_time, float):
×
940
            long_query_time = float(long_query_time)
×
941
        self.long_query_time = long_query_time
×
942

943
        if REQUEST is not None:
×
944
            REQUEST.response.redirect(REQUEST.URL1 + (
×
945
                '/manage_catalogReport?manage_tabs_message='
946
                'Long%20query%20time%20changed'))
947

948

949
InitializeClass(ZCatalog)
1✔
950

951

952
def absattr(attr):
1✔
953
    if callable(attr):
×
954
        return attr()
×
955
    return attr
×
956

957

958
class td(RestrictedDTML, TemplateDict):  # NOQA
1✔
959
    pass
1✔
960

961

962
def expr_match(ob, ed):
1✔
963
    e, md, push, pop = ed
×
964
    push(InstanceDict(ob, md))
×
965
    r = 0
×
966
    try:
×
967
        r = e.eval(md)
×
968
    finally:
969
        pop()
×
970
        return r
×
971

972

973
def mtime_match(ob, t, q):
1✔
974
    mtime = getattr(ob, '_p_mtime', _marker)
×
975
    if mtime is _marker():
×
976
        return False
×
977
    return q == '<' and (mtime < t) or (mtime > t)
×
978

979

980
def role_match(ob, permission, roles):
1✔
981
    pr = []
×
982
    while True:
983
        p = getattr(ob, permission, _marker)
×
984
        if p is not _marker:
×
985
            if isinstance(p, list):
×
986
                pr.append(p)
×
987
                ob = aq_parent(ob)
×
988
                if ob is not None:
×
989
                    continue
×
990
                break
×
991
            if isinstance(p, tuple):
×
992
                pr.append(p)
×
993
                break
×
994
            if p is None:
×
995
                pr.append(('Manager', 'Anonymous'))
×
996
                break
×
997

998
        ob = aq_parent(ob)
×
999
        if ob is not None:
×
1000
            continue
×
1001
        break
×
1002

1003
    for role in roles:
×
1004
        if role not in pr:
×
1005
            return False
×
1006
    return True
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc