53 from logilab.mtconverter import xml_escape |
53 from logilab.mtconverter import xml_escape |
54 from logilab.common.graph import has_path |
54 from logilab.common.graph import has_path |
55 from logilab.common.decorators import cached |
55 from logilab.common.decorators import cached |
56 from logilab.common.date import datetime2ticks, ustrftime, ticks2datetime |
56 from logilab.common.date import datetime2ticks, ustrftime, ticks2datetime |
57 from logilab.common.compat import all |
57 from logilab.common.compat import all |
|
58 from logilab.common.deprecation import deprecated |
58 |
59 |
59 from rql import parse, nodes, utils |
60 from rql import parse, nodes, utils |
60 |
61 |
61 from cubicweb import Unauthorized, typed_eid |
62 from cubicweb import Unauthorized, typed_eid |
62 from cubicweb.schema import display_name |
63 from cubicweb.schema import display_name |
71 if len(ptypes) == 1: |
72 if len(ptypes) == 1: |
72 return display_name(facet._cw, facet.rtype, form=facet.role, |
73 return display_name(facet._cw, facet.rtype, form=facet.role, |
73 context=iter(ptypes).next()) |
74 context=iter(ptypes).next()) |
74 return display_name(facet._cw, facet.rtype, form=facet.role) |
75 return display_name(facet._cw, facet.rtype, form=facet.role) |
75 |
76 |
|
77 def filtered_variable(rqlst): |
|
78 vref = rqlst.selection[0].iget_nodes(nodes.VariableRef).next() |
|
79 return vref.variable |
|
80 |
|
81 def get_facet(req, facetid, rqlst, mainvar): |
|
82 return req.vreg['facets'].object_by_id(facetid, req, rqlst=rqlst, |
|
83 filtered_variable=mainvar) |
|
84 |
|
85 @deprecated('[3.13] filter_hiddens moved to cubicweb.web.views.facets with ' |
|
86 'slightly modified prototype') |
|
87 def filter_hiddens(w, **kwargs): |
|
88 from cubicweb.web.views.facets import filter_hiddens |
|
89 return filter_hiddens(w, wdgs=kwargs.pop('facets')) |
|
90 |
|
91 |
76 ## rqlst manipulation functions used by facets ################################ |
92 ## rqlst manipulation functions used by facets ################################ |
77 |
93 |
78 def prepare_facets_rqlst(rqlst, args=None): |
94 def prepare_facets_rqlst(rqlst, args=None): |
79 """prepare a syntax tree to generate facet filters |
95 """prepare a syntax tree to generate facet filters |
80 |
96 |
102 select.undefine_variable(dvar) |
117 select.undefine_variable(dvar) |
103 # global tree config: DISTINCT, LIMIT, OFFSET |
118 # global tree config: DISTINCT, LIMIT, OFFSET |
104 select.set_distinct(True) |
119 select.set_distinct(True) |
105 return mainvar, baserql |
120 return mainvar, baserql |
106 |
121 |
107 def filtered_variable(rqlst): |
122 |
108 vref = rqlst.selection[0].iget_nodes(nodes.VariableRef).next() |
123 def prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role, |
109 return vref.variable |
|
110 |
|
111 |
|
112 def get_facet(req, facetid, rqlst, mainvar): |
|
113 return req.vreg['facets'].object_by_id(facetid, req, rqlst=rqlst, |
|
114 filtered_variable=mainvar) |
|
115 |
|
116 |
|
117 def filter_hiddens(w, **kwargs): |
|
118 for key, val in kwargs.items(): |
|
119 w(u'<input type="hidden" name="%s" value="%s" />' % ( |
|
120 key, xml_escape(val))) |
|
121 |
|
122 |
|
123 def _may_be_removed(rel, schema, mainvar): |
|
124 """if the given relation may be removed from the tree, return the variable |
|
125 on the other side of `mainvar`, else return None |
|
126 Conditions: |
|
127 * the relation is an attribute selection of the main variable |
|
128 * the relation is optional relation linked to the main variable |
|
129 * the relation is a mandatory relation linked to the main variable |
|
130 without any restriction on the other variable |
|
131 """ |
|
132 lhs, rhs = rel.get_variable_parts() |
|
133 rschema = schema.rschema(rel.r_type) |
|
134 if lhs.variable is mainvar: |
|
135 try: |
|
136 ovar = rhs.variable |
|
137 except AttributeError: |
|
138 # constant restriction |
|
139 # XXX: X title LOWER(T) if it makes sense? |
|
140 return None |
|
141 if rschema.final: |
|
142 if len(ovar.stinfo['relations']) == 1: |
|
143 # attribute selection |
|
144 return ovar |
|
145 return None |
|
146 opt = 'right' |
|
147 cardidx = 0 |
|
148 elif getattr(rhs, 'variable', None) is mainvar: |
|
149 ovar = lhs.variable |
|
150 opt = 'left' |
|
151 cardidx = 1 |
|
152 else: |
|
153 # not directly linked to the main variable |
|
154 return None |
|
155 if rel.optional in (opt, 'both'): |
|
156 # optional relation |
|
157 return ovar |
|
158 if all(rdef.cardinality[cardidx] in '1+' |
|
159 for rdef in rschema.rdefs.values()): |
|
160 # mandatory relation without any restriction on the other variable |
|
161 for orel in ovar.stinfo['relations']: |
|
162 if rel is orel: |
|
163 continue |
|
164 if _may_be_removed(orel, schema, ovar) is None: |
|
165 return None |
|
166 return ovar |
|
167 return None |
|
168 |
|
169 def _make_relation(rqlst, mainvar, rtype, role): |
|
170 newvar = rqlst.make_variable() |
|
171 if role == 'object': |
|
172 rel = nodes.make_relation(newvar, rtype, (mainvar,), nodes.VariableRef) |
|
173 else: |
|
174 rel = nodes.make_relation(mainvar, rtype, (newvar,), nodes.VariableRef) |
|
175 return newvar, rel |
|
176 |
|
177 def _add_rtype_relation(rqlst, mainvar, rtype, role): |
|
178 """add a relation relying `mainvar` to entities linked by the `rtype` |
|
179 relation (where `mainvar` has `role`) |
|
180 |
|
181 return the inserted variable for linked entities. |
|
182 """ |
|
183 newvar, newrel = _make_relation(rqlst, mainvar, rtype, role) |
|
184 rqlst.add_restriction(newrel) |
|
185 return newvar, newrel |
|
186 |
|
187 def _add_eid_restr(rel, restrvar, value): |
|
188 rrel = nodes.make_constant_restriction(restrvar, 'eid', value, 'Int') |
|
189 rel.parent.replace(rel, nodes.And(rel, rrel)) |
|
190 |
|
191 def _prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role, |
|
192 select_target_entity=True): |
124 select_target_entity=True): |
193 """prepare a syntax tree to generate a filter vocabulary rql using the given |
125 """prepare a syntax tree to generate a filter vocabulary rql using the given |
194 relation: |
126 relation: |
195 * create a variable to filter on this relation |
127 * create a variable to filter on this relation |
196 * add the relation |
128 * add the relation |
206 if mainvar.stinfo['typerel'] is None: |
138 if mainvar.stinfo['typerel'] is None: |
207 etypes = frozenset(sol[mainvar.name] for sol in rqlst.solutions) |
139 etypes = frozenset(sol[mainvar.name] for sol in rqlst.solutions) |
208 rqlst.add_type_restriction(mainvar, etypes) |
140 rqlst.add_type_restriction(mainvar, etypes) |
209 return newvar |
141 return newvar |
210 |
142 |
211 def _remove_relation(rqlst, rel, var): |
|
212 """remove a constraint relation from the syntax tree""" |
|
213 # remove the relation |
|
214 rqlst.remove_node(rel) |
|
215 # remove relations where the filtered variable appears on the |
|
216 # lhs and rhs is a constant restriction |
|
217 extra = [] |
|
218 for vrel in var.stinfo['relations']: |
|
219 if vrel is rel: |
|
220 continue |
|
221 if vrel.children[0].variable is var: |
|
222 if not vrel.children[1].get_nodes(nodes.Constant): |
|
223 extra.append(vrel) |
|
224 rqlst.remove_node(vrel) |
|
225 return extra |
|
226 |
|
227 def _set_orderby(rqlst, newvar, sortasc, sortfuncname): |
|
228 if sortfuncname is None: |
|
229 rqlst.add_sort_var(newvar, sortasc) |
|
230 else: |
|
231 vref = nodes.variable_ref(newvar) |
|
232 vref.register_reference() |
|
233 sortfunc = nodes.Function(sortfuncname) |
|
234 sortfunc.append(vref) |
|
235 term = nodes.SortTerm(sortfunc, sortasc) |
|
236 rqlst.add_sort_term(term) |
|
237 |
143 |
238 def insert_attr_select_relation(rqlst, mainvar, rtype, role, attrname, |
144 def insert_attr_select_relation(rqlst, mainvar, rtype, role, attrname, |
239 sortfuncname=None, sortasc=True, |
145 sortfuncname=None, sortasc=True, |
240 select_target_entity=True): |
146 select_target_entity=True): |
241 """modify a syntax tree to : |
147 """modify a syntax tree to : |
245 Sorting: |
151 Sorting: |
246 * on `attrname` ascendant (`sortasc`=True) or descendant (`sortasc`=False) |
152 * on `attrname` ascendant (`sortasc`=True) or descendant (`sortasc`=False) |
247 * on `sortfuncname`(`attrname`) if `sortfuncname` is specified |
153 * on `sortfuncname`(`attrname`) if `sortfuncname` is specified |
248 * no sort if `sortasc` is None |
154 * no sort if `sortasc` is None |
249 """ |
155 """ |
250 _cleanup_rqlst(rqlst, mainvar) |
156 cleanup_rqlst(rqlst, mainvar) |
251 var = _prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role, |
157 var = prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role, |
252 select_target_entity) |
158 select_target_entity) |
253 attrvar = rqlst.make_variable() |
159 attrvar = rqlst.make_variable() |
254 rqlst.add_relation(var, attrname, attrvar) |
160 rqlst.add_relation(var, attrname, attrvar) |
255 # if query is grouped, we have to add the attribute variable |
161 # if query is grouped, we have to add the attribute variable |
256 if rqlst.groupby: |
162 if rqlst.groupby: |
257 if not attrvar in rqlst.groupby: |
163 if not attrvar in rqlst.groupby: |
260 _set_orderby(rqlst, attrvar, sortasc, sortfuncname) |
166 _set_orderby(rqlst, attrvar, sortasc, sortfuncname) |
261 # add attribute variable to selection |
167 # add attribute variable to selection |
262 rqlst.add_selected(attrvar) |
168 rqlst.add_selected(attrvar) |
263 return var |
169 return var |
264 |
170 |
265 def _cleanup_rqlst(rqlst, mainvar): |
171 |
266 """cleanup tree from unnecessary restriction: |
172 def cleanup_rqlst(rqlst, mainvar): |
|
173 """cleanup tree from unnecessary restrictions: |
267 * attribute selection |
174 * attribute selection |
268 * optional relations linked to the main variable |
175 * optional relations linked to the main variable |
269 * mandatory relations linked to the main variable |
176 * mandatory relations linked to the main variable |
270 """ |
177 """ |
271 if rqlst.where is None: |
178 if rqlst.where is None: |
308 continue |
215 continue |
309 if not has_path(vargraph, ovarname, mainvar.name): |
216 if not has_path(vargraph, ovarname, mainvar.name): |
310 toremove.add(rqlst.defined_vars[ovarname]) |
217 toremove.add(rqlst.defined_vars[ovarname]) |
311 |
218 |
312 |
219 |
|
220 def _may_be_removed(rel, schema, mainvar): |
|
221 """if the given relation may be removed from the tree, return the variable |
|
222 on the other side of `mainvar`, else return None |
|
223 Conditions: |
|
224 * the relation is an attribute selection of the main variable |
|
225 * the relation is optional relation linked to the main variable |
|
226 * the relation is a mandatory relation linked to the main variable |
|
227 without any restriction on the other variable |
|
228 """ |
|
229 lhs, rhs = rel.get_variable_parts() |
|
230 rschema = schema.rschema(rel.r_type) |
|
231 if lhs.variable is mainvar: |
|
232 try: |
|
233 ovar = rhs.variable |
|
234 except AttributeError: |
|
235 # constant restriction |
|
236 # XXX: X title LOWER(T) if it makes sense? |
|
237 return None |
|
238 if rschema.final: |
|
239 if len(ovar.stinfo['relations']) == 1: |
|
240 # attribute selection |
|
241 return ovar |
|
242 return None |
|
243 opt = 'right' |
|
244 cardidx = 0 |
|
245 elif getattr(rhs, 'variable', None) is mainvar: |
|
246 ovar = lhs.variable |
|
247 opt = 'left' |
|
248 cardidx = 1 |
|
249 else: |
|
250 # not directly linked to the main variable |
|
251 return None |
|
252 if rel.optional in (opt, 'both'): |
|
253 # optional relation |
|
254 return ovar |
|
255 if all(rdef.cardinality[cardidx] in '1+' |
|
256 for rdef in rschema.rdefs.values()): |
|
257 # mandatory relation without any restriction on the other variable |
|
258 for orel in ovar.stinfo['relations']: |
|
259 if rel is orel: |
|
260 continue |
|
261 if _may_be_removed(orel, schema, ovar) is None: |
|
262 return None |
|
263 return ovar |
|
264 return None |
|
265 |
|
266 def _make_relation(rqlst, mainvar, rtype, role): |
|
267 newvar = rqlst.make_variable() |
|
268 if role == 'object': |
|
269 rel = nodes.make_relation(newvar, rtype, (mainvar,), nodes.VariableRef) |
|
270 else: |
|
271 rel = nodes.make_relation(mainvar, rtype, (newvar,), nodes.VariableRef) |
|
272 return newvar, rel |
|
273 |
|
274 def _add_rtype_relation(rqlst, mainvar, rtype, role): |
|
275 """add a relation relying `mainvar` to entities linked by the `rtype` |
|
276 relation (where `mainvar` has `role`) |
|
277 |
|
278 return the inserted variable for linked entities. |
|
279 """ |
|
280 newvar, newrel = _make_relation(rqlst, mainvar, rtype, role) |
|
281 rqlst.add_restriction(newrel) |
|
282 return newvar, newrel |
|
283 |
|
284 def _add_eid_restr(rel, restrvar, value): |
|
285 rrel = nodes.make_constant_restriction(restrvar, 'eid', value, 'Int') |
|
286 rel.parent.replace(rel, nodes.And(rel, rrel)) |
|
287 |
|
288 def _remove_relation(rqlst, rel, var): |
|
289 """remove a constraint relation from the syntax tree""" |
|
290 # remove the relation |
|
291 rqlst.remove_node(rel) |
|
292 # remove relations where the filtered variable appears on the |
|
293 # lhs and rhs is a constant restriction |
|
294 extra = [] |
|
295 for vrel in var.stinfo['relations']: |
|
296 if vrel is rel: |
|
297 continue |
|
298 if vrel.children[0].variable is var: |
|
299 if not vrel.children[1].get_nodes(nodes.Constant): |
|
300 extra.append(vrel) |
|
301 rqlst.remove_node(vrel) |
|
302 return extra |
|
303 |
|
304 def _set_orderby(rqlst, newvar, sortasc, sortfuncname): |
|
305 if sortfuncname is None: |
|
306 rqlst.add_sort_var(newvar, sortasc) |
|
307 else: |
|
308 vref = nodes.variable_ref(newvar) |
|
309 vref.register_reference() |
|
310 sortfunc = nodes.Function(sortfuncname) |
|
311 sortfunc.append(vref) |
|
312 term = nodes.SortTerm(sortfunc, sortasc) |
|
313 rqlst.add_sort_term(term) |
|
314 |
|
315 |
|
316 _prepare_vocabulary_rqlst = deprecated('[3.13] renamed prepare_vocabulary_rqlst ')( |
|
317 prepare_vocabulary_rqlst) |
|
318 _cleanup_rqlst = deprecated('[3.13] renamed to cleanup_rqlst')(cleanup_rqlst) |
|
319 |
|
320 |
313 ## base facet classes ########################################################## |
321 ## base facet classes ########################################################## |
314 |
322 |
315 class AbstractFacet(AppObject): |
323 class AbstractFacet(AppObject): |
316 """Abstract base class for all facets. Facets are stored in their own |
324 """Abstract base class for all facets. Facets are stored in their own |
317 'facets' registry. They are similar to contextual components since the use |
325 'facets' registry. They are similar to contextual components since the use |
603 compare to a form value in javascript) for this facet |
611 compare to a form value in javascript) for this facet |
604 """ |
612 """ |
605 rqlst = self.rqlst |
613 rqlst = self.rqlst |
606 rqlst.save_state() |
614 rqlst.save_state() |
607 try: |
615 try: |
608 _cleanup_rqlst(rqlst, self.filtered_variable) |
616 cleanup_rqlst(rqlst, self.filtered_variable) |
609 if self._select_target_entity: |
617 if self._select_target_entity: |
610 _prepare_vocabulary_rqlst(rqlst, self.filtered_variable, self.rtype, |
618 prepare_vocabulary_rqlst(rqlst, self.filtered_variable, self.rtype, |
611 self.role, select_target_entity=True) |
619 self.role, select_target_entity=True) |
612 else: |
620 else: |
613 insert_attr_select_relation( |
621 insert_attr_select_relation( |
614 rqlst, self.filtered_variable, self.rtype, self.role, self.target_attr, |
622 rqlst, self.filtered_variable, self.rtype, self.role, self.target_attr, |
615 select_target_entity=False) |
623 select_target_entity=False) |
616 values = [unicode(x) for x, in self.rqlexec(rqlst.as_string())] |
624 values = [unicode(x) for x, in self.rqlexec(rqlst.as_string())] |
875 """ |
883 """ |
876 rqlst = self.rqlst |
884 rqlst = self.rqlst |
877 rqlst.save_state() |
885 rqlst.save_state() |
878 try: |
886 try: |
879 mainvar = self.filtered_variable |
887 mainvar = self.filtered_variable |
880 _cleanup_rqlst(rqlst, mainvar) |
888 cleanup_rqlst(rqlst, mainvar) |
881 newvar = _prepare_vocabulary_rqlst(rqlst, mainvar, self.rtype, self.role) |
889 newvar = prepare_vocabulary_rqlst(rqlst, mainvar, self.rtype, self.role) |
882 _set_orderby(rqlst, newvar, self.sortasc, self.sortfunc) |
890 _set_orderby(rqlst, newvar, self.sortasc, self.sortfunc) |
883 try: |
891 try: |
884 rset = self.rqlexec(rqlst.as_string(), self.cw_rset.args) |
892 rset = self.rqlexec(rqlst.as_string(), self.cw_rset.args) |
885 except: |
893 except: |
886 self.exception('error while getting vocabulary for %s, rql: %s', |
894 self.exception('error while getting vocabulary for %s, rql: %s', |