1 | import copy |
---|
2 | import logging |
---|
3 | |
---|
4 | from zope.interface import classProvides, implements |
---|
5 | from zope.app.annotation.interfaces import IAnnotations |
---|
6 | |
---|
7 | from collective.transmogrifier.interfaces import ISection, ISectionBlueprint |
---|
8 | |
---|
9 | from Products.CMFCore import utils |
---|
10 | |
---|
11 | from quintagroup.transmogrifier.logger import VALIDATIONKEY |
---|
12 | |
---|
13 | logger = logging.getLogger("CatalogSourceSection") |
---|
14 | |
---|
15 | class CatalogSourceSection(object): |
---|
16 | classProvides(ISectionBlueprint) |
---|
17 | implements(ISection) |
---|
18 | |
---|
19 | def __init__(self, transmogrifier, name, options, previous): |
---|
20 | self.previous = previous |
---|
21 | self.context = transmogrifier.context |
---|
22 | |
---|
23 | # next is for communication with 'logger' section |
---|
24 | self.anno = IAnnotations(transmogrifier) |
---|
25 | self.storage = self.anno.setdefault(VALIDATIONKEY, []) |
---|
26 | |
---|
27 | self.pathkey = options.pop('path-key', '_path') |
---|
28 | self.entrieskey = options.pop('entries-key', '_entries') |
---|
29 | |
---|
30 | # handle exclude-contained parameter |
---|
31 | if "exclude-contained" in options.keys(): |
---|
32 | self.exclude_contained = options.pop('exclude-contained') |
---|
33 | self.exclude_contained = self.exclude_contained == "true" |
---|
34 | else: |
---|
35 | self.exclude_contained = False |
---|
36 | |
---|
37 | # remove 'blueprint' option - it cannot be a query |
---|
38 | options.pop('blueprint') |
---|
39 | |
---|
40 | self.query = {} |
---|
41 | for k, v in options.items(): |
---|
42 | for p in v.split(';'): |
---|
43 | params = p.split('=', 1) |
---|
44 | if len(params) == 1: |
---|
45 | self.query[k] = p.strip() |
---|
46 | else : |
---|
47 | q = self.query.setdefault(k, {}) |
---|
48 | q[params[0].strip()] = params[1].strip() |
---|
49 | |
---|
50 | self.catalog = utils.getToolByName(self.context, 'portal_catalog') |
---|
51 | |
---|
52 | def __iter__(self): |
---|
53 | for item in self.previous: |
---|
54 | yield item |
---|
55 | |
---|
56 | exported = [] |
---|
57 | exported_parents = [] |
---|
58 | |
---|
59 | results = list(self.catalog(**self.query)) |
---|
60 | results.sort(key=lambda x: x.getPath()) |
---|
61 | |
---|
62 | for brain in results: |
---|
63 | # discussion items are indexed and they must be replaced to |
---|
64 | # content objects to which they correspond |
---|
65 | # we need to skip them |
---|
66 | if brain.portal_type == 'Discussion Item': |
---|
67 | path = '/'.join(brain.getPath().split('/')[:-2]) |
---|
68 | cp, id_ = path.rsplit('/', 1) |
---|
69 | brain = self.catalog(path=cp, id=id_)[0] |
---|
70 | else: |
---|
71 | path = brain.getPath() |
---|
72 | |
---|
73 | # folderish objects are tried to export twice: |
---|
74 | # when their contained items are exported and when they are |
---|
75 | # returned in catalog search results |
---|
76 | if path in exported: |
---|
77 | continue |
---|
78 | exported.append(path) |
---|
79 | |
---|
80 | # export also all parents of current object |
---|
81 | containers = [] |
---|
82 | container_path = path.rsplit('/', 1)[0] |
---|
83 | while container_path: |
---|
84 | |
---|
85 | if container_path in exported: |
---|
86 | container_path = container_path.rsplit('/', 1)[0] |
---|
87 | continue |
---|
88 | |
---|
89 | exported_parents.append(container_path) |
---|
90 | |
---|
91 | contained = self.getContained(container_path, results, exported_parents) |
---|
92 | |
---|
93 | if contained: |
---|
94 | exported.append(container_path) |
---|
95 | containers.append({ |
---|
96 | self.pathkey: '/'.join(container_path.split('/')[2:]), |
---|
97 | self.entrieskey: contained, |
---|
98 | }) |
---|
99 | container_path = container_path.rsplit('/', 1)[0] |
---|
100 | |
---|
101 | containers.reverse() |
---|
102 | # order metter for us |
---|
103 | for i in containers: |
---|
104 | self.storage.append(i[self.pathkey]) |
---|
105 | yield i |
---|
106 | |
---|
107 | item = { |
---|
108 | self.pathkey: '/'.join(path.split('/')[2:]), |
---|
109 | } |
---|
110 | if brain.is_folderish: |
---|
111 | contained = self.getContained(path, results, exported_parents) |
---|
112 | if contained: |
---|
113 | item[self.entrieskey] = contained |
---|
114 | |
---|
115 | self.storage.append(item[self.pathkey]) |
---|
116 | yield item |
---|
117 | |
---|
118 | # cleanup |
---|
119 | if VALIDATIONKEY in self.anno: |
---|
120 | del self.anno[VALIDATIONKEY] |
---|
121 | |
---|
122 | def getContained(self, path, orignal_results, parents): |
---|
123 | """ Return list of (object_id, portal_type) for objects that are returned by catalog |
---|
124 | and contained in folder with given 'path'. |
---|
125 | """ |
---|
126 | results = [] |
---|
127 | seen = [] |
---|
128 | |
---|
129 | |
---|
130 | # Remove the orignal path element from the query if there was one |
---|
131 | query = copy.deepcopy(self.query) |
---|
132 | if "path" in query: |
---|
133 | del query["path"] |
---|
134 | |
---|
135 | raw_results = self.catalog(path=path, **query) |
---|
136 | |
---|
137 | for brain in raw_results: |
---|
138 | current = brain.getPath() |
---|
139 | relative = current[len(path):] |
---|
140 | relative = relative.strip('/') |
---|
141 | if not relative: |
---|
142 | # it's object with path that was given in catalog query |
---|
143 | continue |
---|
144 | elif '/' in relative: |
---|
145 | # object stored in subfolders, we need append to results their parent folder |
---|
146 | parent_path = '/'.join([path, relative.split('/', 1)[0]]) |
---|
147 | if parent_path not in seen: |
---|
148 | res = self.catalog(path=path) #, meta_type='Folder') |
---|
149 | for i in res: |
---|
150 | if i.getPath() == parent_path: |
---|
151 | results.append(i) |
---|
152 | seen.append(parent_path) |
---|
153 | break |
---|
154 | elif current not in seen: |
---|
155 | # object is directly stored in folder, that has path given in query |
---|
156 | seen.append(current) |
---|
157 | results.append(brain) |
---|
158 | |
---|
159 | def filter(r): |
---|
160 | |
---|
161 | # Parent objects must be allowed always |
---|
162 | for parent in parents: |
---|
163 | if r.getPath() == parent: |
---|
164 | return True |
---|
165 | |
---|
166 | if r["UID"] in allowed_uids: |
---|
167 | return True |
---|
168 | else: |
---|
169 | logger.info("Excluded contained item as it did not match the orignal catalog query:" + str(r.getPath())) |
---|
170 | |
---|
171 | if self.exclude_contained and orignal_results is not None: |
---|
172 | # Filter contained results against our query, so that |
---|
173 | # we do not export results from parent objects which did not match |
---|
174 | # Build list of allowed object UIDs - |
---|
175 | allowed_uids = [ r["UID"] for r in orignal_results ] |
---|
176 | |
---|
177 | # All parents must be allowed always |
---|
178 | filtered_results = [ r for r in results if filter(r) == True ] |
---|
179 | else: |
---|
180 | # Don't filter child items |
---|
181 | filtered_results = results |
---|
182 | |
---|
183 | contained = [(i.getId, str(i.portal_type)) for i in filtered_results ] |
---|
184 | |
---|
185 | return tuple(contained) |
---|