|
1 """Specific views for CWProperty |
|
2 |
|
3 :organization: Logilab |
|
4 :copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
6 """ |
|
7 __docformat__ = "restructuredtext en" |
|
8 |
|
9 from logilab.mtconverter import html_escape |
|
10 |
|
11 from logilab.common.decorators import cached |
|
12 |
|
13 from cubicweb import UnknownProperty |
|
14 from cubicweb.selectors import (one_line_rset, none_rset, implements, |
|
15 match_user_groups, entity_implements) |
|
16 from cubicweb.utils import UStringIO |
|
17 from cubicweb.view import StartupView |
|
18 from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, uicfg |
|
19 from cubicweb.web.views import baseviews |
|
20 from cubicweb.web import stdmsgs |
|
21 from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn |
|
22 from cubicweb.web.formfields import FIELDS, StringField |
|
23 from cubicweb.web.formwidgets import Select, Button, SubmitButton |
|
24 |
|
25 _ = unicode |
|
26 |
|
27 # some string we want to be internationalizable for nicer display of eproperty |
|
28 # groups |
|
29 _('navigation') |
|
30 _('ui') |
|
31 _('actions') |
|
32 _('boxes') |
|
33 _('components') |
|
34 _('contentnavigation') |
|
35 |
|
36 |
|
37 def make_togglable_link(nodeid, label, cookiename): |
|
38 """builds a HTML link that switches the visibility & remembers it""" |
|
39 action = u"javascript: toggle_and_remember_visibility('%s', '%s')" % \ |
|
40 (nodeid, cookiename) |
|
41 return u'<a href="%s">%s</a>' % (action, label) |
|
42 |
|
43 def css_class(someclass): |
|
44 return someclass and 'class="%s"' % someclass or '' |
|
45 |
|
46 |
|
47 class CWPropertyPrimaryView(baseviews.PrimaryView): |
|
48 __select__ = implements('CWProperty') |
|
49 skip_none = False |
|
50 |
|
51 |
|
52 class SystemEPropertiesForm(FormViewMixIn, StartupView): |
|
53 id = 'systemepropertiesform' |
|
54 __select__ = none_rset() & match_user_groups('managers') |
|
55 |
|
56 title = _('site configuration') |
|
57 category = 'startupview' |
|
58 |
|
59 def linkable(self): |
|
60 return True |
|
61 |
|
62 def url(self): |
|
63 """return the url associated with this view. We can omit rql here""" |
|
64 return self.build_url('view', vid=self.id) |
|
65 |
|
66 def _cookie_name(self, somestr): |
|
67 return str('%s_property_%s' % (self.config.appid, somestr)) |
|
68 |
|
69 def _group_status(self, group, default=u'hidden'): |
|
70 """return css class name 'hidden' (collapsed), or '' (open)""" |
|
71 cookies = self.req.get_cookie() |
|
72 cookiename = self._cookie_name(group) |
|
73 cookie = cookies.get(cookiename) |
|
74 if cookie is None: |
|
75 cookies[cookiename] = default |
|
76 self.req.set_cookie(cookies, cookiename, maxage=None) |
|
77 status = default |
|
78 else: |
|
79 status = cookie.value |
|
80 return status |
|
81 |
|
82 def call(self, **kwargs): |
|
83 """The default view representing the application's index""" |
|
84 self.req.add_js('cubicweb.preferences.js') |
|
85 self.req.add_css('cubicweb.preferences.css') |
|
86 vreg = self.vreg |
|
87 values = self.defined_keys |
|
88 groupedopts = {} |
|
89 mainopts = {} |
|
90 # "self.id=='systemepropertiesform'" to skip site wide properties on |
|
91 # user's preference but not site's configuration |
|
92 for key in vreg.user_property_keys(self.id=='systemepropertiesform'): |
|
93 parts = key.split('.') |
|
94 if parts[0] in vreg: |
|
95 # appobject configuration |
|
96 reg, oid, propid = parts |
|
97 groupedopts.setdefault(reg, {}).setdefault(oid, []).append(key) |
|
98 else: |
|
99 mainopts.setdefault(parts[0], []).append(key) |
|
100 # precompute form to consume error message |
|
101 for group, keys in mainopts.items(): |
|
102 mainopts[group] = self.form(keys, True) |
|
103 for group, objects in groupedopts.items(): |
|
104 for oid, keys in objects.items(): |
|
105 groupedopts[group][oid] = self.form(keys, True) |
|
106 w = self.w |
|
107 req = self.req |
|
108 _ = req._ |
|
109 w(u'<h1>%s</h1>\n' % _(self.title)) |
|
110 # we don't want this in each sub-forms |
|
111 w(u'<div id="progress">%s</div>' % self.req._('validating...')) |
|
112 for label, group, form in sorted((_(g), g, f) |
|
113 for g, f in mainopts.iteritems()): |
|
114 status = css_class(self._group_status(group)) |
|
115 w(u'<h2 class="propertiesform">%s</h2>\n' % |
|
116 (make_togglable_link('fieldset_' + group, label, |
|
117 self._cookie_name(group)))) |
|
118 w(u'<div id="fieldset_%s" %s>' % (group, status)) |
|
119 w(form) |
|
120 w(u'</div>') |
|
121 for label, group, objects in sorted((_(g), g, o) |
|
122 for g, o in groupedopts.iteritems()): |
|
123 status = css_class(self._group_status(group)) |
|
124 w(u'<h2 class="propertiesform">%s</h2>\n' % |
|
125 (make_togglable_link('fieldset_' + group, label, |
|
126 self._cookie_name(group)))) |
|
127 w(u'<div id="fieldset_%s" %s>' % (group, status)) |
|
128 for label, oid, form in sorted((self.req.__('%s_%s' % (group, o)), o, f) |
|
129 for o, f in objects.iteritems()): |
|
130 w(u'<fieldset class="subentity">') |
|
131 w(u'<legend class="componentTitle">%s</legend>\n' % label) |
|
132 docmsgid = '%s_%s_description' % (group, oid) |
|
133 doc = _(docmsgid) |
|
134 if doc != docmsgid: |
|
135 w(u'<p class="description">%s</p>' % html_escape(doc)) |
|
136 w(form) |
|
137 w(u'</fieldset>') |
|
138 w(u'</div>') |
|
139 |
|
140 @property |
|
141 @cached |
|
142 def eprops_rset(self): |
|
143 return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, ' |
|
144 'P value V, NOT P for_user U') |
|
145 |
|
146 @property |
|
147 def defined_keys(self): |
|
148 values = {} |
|
149 for i, entity in enumerate(self.eprops_rset.entities()): |
|
150 values[entity.pkey] = i |
|
151 return values |
|
152 |
|
153 def entity_for_key(self, key): |
|
154 values = self.defined_keys |
|
155 if key in values: |
|
156 entity = self.eprops_rset.get_entity(values[key], 0) |
|
157 else: |
|
158 entity = self.vreg.etype_class('CWProperty')(self.req, None, None) |
|
159 entity.eid = self.req.varmaker.next() |
|
160 entity['pkey'] = key |
|
161 entity['value'] = self.vreg.property_value(key) |
|
162 return entity |
|
163 |
|
164 def form(self, keys, splitlabel=False): |
|
165 buttons = [SubmitButton(), |
|
166 Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] |
|
167 form = CompositeForm(self.req, domid=None, action=self.build_url(), |
|
168 form_buttons=buttons, |
|
169 submitmsg=self.req._('changes applied')) |
|
170 path = self.req.relative_path() |
|
171 if '?' in path: |
|
172 path, params = path.split('?', 1) |
|
173 form.form_add_hidden('__redirectparams', params) |
|
174 form.form_add_hidden('__redirectpath', path) |
|
175 for key in keys: |
|
176 self.form_row(form, key, splitlabel) |
|
177 return form.form_render(display_progress_div=False) |
|
178 |
|
179 def form_row(self, form, key, splitlabel): |
|
180 entity = self.entity_for_key(key) |
|
181 if splitlabel: |
|
182 label = key.split('.')[-1] |
|
183 else: |
|
184 label = key |
|
185 subform = EntityFieldsForm(self.req, entity=entity, set_error_url=False) |
|
186 subform.append_field(PropertyValueField(name='value', label=label, |
|
187 eidparam=True)) |
|
188 subform.vreg = self.vreg |
|
189 subform.form_add_hidden('pkey', key, eidparam=True) |
|
190 form.form_add_subform(subform) |
|
191 return subform |
|
192 |
|
193 |
|
194 def is_user_prefs(cls, req, rset, row=None, col=0, **kwargs): |
|
195 return req.user.eid == rset[row or 0][col] |
|
196 |
|
197 |
|
198 class EPropertiesForm(SystemEPropertiesForm): |
|
199 id = 'epropertiesform' |
|
200 __select__ = ( |
|
201 # we don't want guests to be able to come here |
|
202 match_user_groups('users', 'managers') & |
|
203 (none_rset() | ((one_line_rset() & is_user_prefs) & |
|
204 (one_line_rset() & match_user_groups('managers')))) |
|
205 ) |
|
206 |
|
207 title = _('preferences') |
|
208 |
|
209 @property |
|
210 def user(self): |
|
211 if self.rset is None: |
|
212 return self.req.user |
|
213 return self.rset.get_entity(self.row or 0, self.col or 0) |
|
214 |
|
215 @property |
|
216 @cached |
|
217 def eprops_rset(self): |
|
218 return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, P value V,' |
|
219 'P for_user U, U eid %(x)s', {'x': self.user.eid}) |
|
220 |
|
221 def form_row(self, form, key, splitlabel): |
|
222 subform = super(EPropertiesForm, self).form_row(form, key, splitlabel) |
|
223 # if user is in the managers group and the property is being created, |
|
224 # we have to set for_user explicitly |
|
225 if not subform.edited_entity.has_eid() and self.user.matching_groups('managers'): |
|
226 subform.form_add_hidden('for_user', self.user.eid, eidparam=True) |
|
227 |
|
228 |
|
229 # eproperty form objects ###################################################### |
|
230 |
|
231 class PlaceHolderWidget(object): |
|
232 |
|
233 def render(self, form, field): |
|
234 domid = form.context[field]['id'] |
|
235 # empty span as well else html validation fail (label is refering to |
|
236 # this id) |
|
237 return '<div id="div:%s"><span id="%s">%s</span></div>' % ( |
|
238 domid, domid, form.req._('select a key first')) |
|
239 |
|
240 |
|
241 class NotEditableWidget(object): |
|
242 def __init__(self, value, msg=None): |
|
243 self.value = value |
|
244 self.msg = msg |
|
245 |
|
246 def render(self, form, field): |
|
247 domid = form.context[field]['id'] |
|
248 value = '<span class="value" id="%s">%s</span>' % (domid, self.value) |
|
249 if self.msg: |
|
250 value + '<div class="helper">%s</div>' % self.msg |
|
251 return value |
|
252 |
|
253 |
|
254 class PropertyKeyField(StringField): |
|
255 """specific field for CWProperty.pkey to set the value widget according to |
|
256 the selected key |
|
257 """ |
|
258 widget = Select |
|
259 |
|
260 def render(self, form, renderer): |
|
261 wdg = self.get_widget(form) |
|
262 wdg.attrs['tabindex'] = form.req.next_tabindex() |
|
263 wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % ( |
|
264 form.edited_entity.eid, form.req.next_tabindex()) |
|
265 return wdg.render(form, self) |
|
266 |
|
267 def vocabulary(self, form): |
|
268 entity = form.edited_entity |
|
269 _ = form.req._ |
|
270 if entity.has_eid(): |
|
271 return [(_(entity.pkey), entity.pkey)] |
|
272 # key beginning with 'system.' should usually not be edited by hand |
|
273 choices = entity.vreg.user_property_keys() |
|
274 return [(u'', u'')] + sorted(zip((_(v) for v in choices), choices)) |
|
275 |
|
276 |
|
277 class PropertyValueField(StringField): |
|
278 """specific field for CWProperty.value which will be different according to |
|
279 the selected key type and vocabulary information |
|
280 """ |
|
281 widget = PlaceHolderWidget |
|
282 |
|
283 def render(self, form, renderer=None, tabindex=None): |
|
284 wdg = self.get_widget(form) |
|
285 if tabindex is not None: |
|
286 wdg.attrs['tabindex'] = tabindex |
|
287 return wdg.render(form, self) |
|
288 |
|
289 def form_init(self, form): |
|
290 entity = form.edited_entity |
|
291 if not (entity.has_eid() or 'pkey' in entity): |
|
292 # no key set yet, just include an empty div which will be filled |
|
293 # on key selection |
|
294 return |
|
295 try: |
|
296 pdef = form.vreg.property_info(entity.pkey) |
|
297 except UnknownProperty, ex: |
|
298 self.warning('%s (you should probably delete that property ' |
|
299 'from the database)', ex) |
|
300 msg = form.req._('you should probably delete that property') |
|
301 self.widget = NotEditableWidget(entity.printable_value('value'), |
|
302 '%s (%s)' % (msg, ex)) |
|
303 if entity.pkey.startswith('system.'): |
|
304 msg = form.req._('value associated to this key is not editable ' |
|
305 'manually') |
|
306 self.widget = NotEditableWidget(entity.printable_value('value'), msg) |
|
307 # XXX race condition when used from CWPropertyForm, should not rely on |
|
308 # instance attributes |
|
309 self.initial = pdef['default'] |
|
310 self.help = pdef['help'] |
|
311 vocab = pdef['vocabulary'] |
|
312 if vocab is not None: |
|
313 if callable(vocab): |
|
314 # list() just in case its a generator function |
|
315 self.choices = list(vocab(form.req)) |
|
316 else: |
|
317 self.choices = vocab |
|
318 wdg = Select() |
|
319 else: |
|
320 wdg = FIELDS[pdef['type']].widget() |
|
321 if pdef['type'] == 'Boolean': |
|
322 self.choices = [(form.req._('yes'), '1'), (form.req._('no'), '')] |
|
323 elif pdef['type'] in ('Float', 'Int'): |
|
324 wdg.attrs.setdefault('size', 3) |
|
325 self.widget = wdg |
|
326 |
|
327 uicfg.rfields.set_rtag(PropertyKeyField, 'pkey', 'subject', 'CWProperty') |
|
328 uicfg.rfields.set_rtag(PropertyValueField, 'value', 'subject', 'CWProperty') |
|
329 |