55 def write(self, data): |
54 def write(self, data): |
56 assert isinstance(data, (str, buffer)), \ |
55 assert isinstance(data, (str, buffer)), \ |
57 "Binary objects must use raw strings, not %s" % data.__class__ |
56 "Binary objects must use raw strings, not %s" % data.__class__ |
58 StringIO.write(self, data) |
57 StringIO.write(self, data) |
59 |
58 |
|
59 # use this dictionary to rename entity types while keeping bw compat |
|
60 ETYPE_NAME_MAP = {} |
60 |
61 |
61 class RequestSessionMixIn(object): |
62 # XXX cubic web cube migration map. See if it's worth keeping this mecanism |
62 """mixin class containing stuff shared by server session and web request |
63 # to help in cube renaming |
63 """ |
64 CW_MIGRATION_MAP = {} |
64 def __init__(self, vreg): |
|
65 self.vreg = vreg |
|
66 try: |
|
67 encoding = vreg.property_value('ui.encoding') |
|
68 except: # no vreg or property not registered |
|
69 encoding = 'utf-8' |
|
70 self.encoding = encoding |
|
71 # cache result of execution for (rql expr / eids), |
|
72 # should be emptied on commit/rollback of the server session / web |
|
73 # connection |
|
74 self.local_perm_cache = {} |
|
75 |
|
76 def property_value(self, key): |
|
77 if self.user: |
|
78 return self.user.property_value(key) |
|
79 return self.vreg.property_value(key) |
|
80 |
|
81 def etype_rset(self, etype, size=1): |
|
82 """return a fake result set for a particular entity type""" |
|
83 from cubicweb.rset import ResultSet |
|
84 rset = ResultSet([('A',)]*size, '%s X' % etype, |
|
85 description=[(etype,)]*size) |
|
86 def get_entity(row, col=0, etype=etype, req=self, rset=rset): |
|
87 return req.vreg.etype_class(etype)(req, rset, row, col) |
|
88 rset.get_entity = get_entity |
|
89 return self.decorate_rset(rset) |
|
90 |
|
91 def eid_rset(self, eid, etype=None): |
|
92 """return a result set for the given eid without doing actual query |
|
93 (we have the eid, we can suppose it exists and user has access to the |
|
94 entity) |
|
95 """ |
|
96 from cubicweb.rset import ResultSet |
|
97 eid = typed_eid(eid) |
|
98 if etype is None: |
|
99 etype = self.describe(eid)[0] |
|
100 rset = ResultSet([(eid,)], 'Any X WHERE X eid %(x)s', {'x': eid}, |
|
101 [(etype,)]) |
|
102 return self.decorate_rset(rset) |
|
103 |
|
104 def empty_rset(self): |
|
105 """return an empty result set. This is used e.g. to substitute |
|
106 to a real result set if the user doesn't have permission to |
|
107 access the results of a query. |
|
108 """ |
|
109 from cubicweb.rset import ResultSet |
|
110 return self.decorate_rset(ResultSet([], 'Any X WHERE X eid -1')) |
|
111 |
|
112 def entity_from_eid(self, eid, etype=None): |
|
113 try: |
|
114 return self.entity_cache(eid) |
|
115 except KeyError: |
|
116 rset = self.eid_rset(eid, etype) |
|
117 entity = rset.get_entity(0, 0) |
|
118 self.set_entity_cache(entity) |
|
119 return entity |
|
120 |
|
121 def entity_cache(self, eid): |
|
122 raise KeyError |
|
123 def set_entity_cache(self, entity): |
|
124 pass |
|
125 |
|
126 def create_entity(self, etype, _cw_unsafe=False, **kwargs): |
|
127 """add a new entity of the given type |
|
128 |
|
129 Example (in a shell session): |
|
130 |
|
131 c = create_entity('Company', name=u'Logilab') |
|
132 create_entity('Person', works_for=c, firstname=u'John', lastname=u'Doe') |
|
133 |
|
134 """ |
|
135 if _cw_unsafe: |
|
136 execute = self.unsafe_execute |
|
137 else: |
|
138 execute = self.execute |
|
139 rql = 'INSERT %s X' % etype |
|
140 relations = [] |
|
141 restrictions = set() |
|
142 cachekey = [] |
|
143 pending_relations = [] |
|
144 for attr, value in kwargs.items(): |
|
145 if isinstance(value, (tuple, list, set, frozenset)): |
|
146 if len(value) == 1: |
|
147 value = iter(value).next() |
|
148 else: |
|
149 del kwargs[attr] |
|
150 pending_relations.append( (attr, value) ) |
|
151 continue |
|
152 if hasattr(value, 'eid'): # non final relation |
|
153 rvar = attr.upper() |
|
154 # XXX safer detection of object relation |
|
155 if attr.startswith('reverse_'): |
|
156 relations.append('%s %s X' % (rvar, attr[len('reverse_'):])) |
|
157 else: |
|
158 relations.append('X %s %s' % (attr, rvar)) |
|
159 restriction = '%s eid %%(%s)s' % (rvar, attr) |
|
160 if not restriction in restrictions: |
|
161 restrictions.add(restriction) |
|
162 cachekey.append(attr) |
|
163 kwargs[attr] = value.eid |
|
164 else: # attribute |
|
165 relations.append('X %s %%(%s)s' % (attr, attr)) |
|
166 if relations: |
|
167 rql = '%s: %s' % (rql, ', '.join(relations)) |
|
168 if restrictions: |
|
169 rql = '%s WHERE %s' % (rql, ', '.join(restrictions)) |
|
170 created = execute(rql, kwargs, cachekey).get_entity(0, 0) |
|
171 for attr, values in pending_relations: |
|
172 if attr.startswith('reverse_'): |
|
173 restr = 'Y %s X' % attr[len('reverse_'):] |
|
174 else: |
|
175 restr = 'X %s Y' % attr |
|
176 execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % ( |
|
177 restr, ','.join(str(r.eid) for r in values)), |
|
178 {'x': created.eid}, 'x') |
|
179 return created |
|
180 |
|
181 # url generation methods ################################################## |
|
182 |
|
183 def build_url(self, *args, **kwargs): |
|
184 """return an absolute URL using params dictionary key/values as URL |
|
185 parameters. Values are automatically URL quoted, and the |
|
186 publishing method to use may be specified or will be guessed. |
|
187 """ |
|
188 # use *args since we don't want first argument to be "anonymous" to |
|
189 # avoid potential clash with kwargs |
|
190 assert len(args) == 1, 'only 0 or 1 non-named-argument expected' |
|
191 method = args[0] |
|
192 base_url = kwargs.pop('base_url', None) |
|
193 if base_url is None: |
|
194 base_url = self.base_url() |
|
195 if '_restpath' in kwargs: |
|
196 assert method == 'view', method |
|
197 path = kwargs.pop('_restpath') |
|
198 else: |
|
199 path = method |
|
200 if not kwargs: |
|
201 return u'%s%s' % (base_url, path) |
|
202 return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs)) |
|
203 |
|
204 |
|
205 def build_url_params(self, **kwargs): |
|
206 """return encoded params to incorporate them in an URL""" |
|
207 args = [] |
|
208 for param, values in kwargs.items(): |
|
209 if not isinstance(values, (list, tuple)): |
|
210 values = (values,) |
|
211 for value in values: |
|
212 args.append(u'%s=%s' % (param, self.url_quote(value))) |
|
213 return '&'.join(args) |
|
214 |
|
215 def url_quote(self, value, safe=''): |
|
216 """urllib.quote is not unicode safe, use this method to do the |
|
217 necessary encoding / decoding. Also it's designed to quote each |
|
218 part of a url path and so the '/' character will be encoded as well. |
|
219 """ |
|
220 if isinstance(value, unicode): |
|
221 quoted = urlquote(value.encode(self.encoding), safe=safe) |
|
222 return unicode(quoted, self.encoding) |
|
223 return urlquote(str(value), safe=safe) |
|
224 |
|
225 def url_unquote(self, quoted): |
|
226 """returns a unicode unquoted string |
|
227 |
|
228 decoding is based on `self.encoding` which is the encoding |
|
229 used in `url_quote` |
|
230 """ |
|
231 if isinstance(quoted, unicode): |
|
232 quoted = quoted.encode(self.encoding) |
|
233 try: |
|
234 return unicode(urlunquote(quoted), self.encoding) |
|
235 except UnicodeDecodeError: # might occurs on manually typed URLs |
|
236 return unicode(urlunquote(quoted), 'iso-8859-1') |
|
237 |
|
238 |
|
239 # session's user related methods ##################################### |
|
240 |
|
241 @cached |
|
242 def user_data(self): |
|
243 """returns a dictionnary with this user's information""" |
|
244 userinfo = {} |
|
245 if self.is_internal_session: |
|
246 userinfo['login'] = "cubicweb" |
|
247 userinfo['name'] = "cubicweb" |
|
248 userinfo['email'] = "" |
|
249 return userinfo |
|
250 user = self.actual_session().user |
|
251 userinfo['login'] = user.login |
|
252 userinfo['name'] = user.name() |
|
253 userinfo['email'] = user.get_email() |
|
254 return userinfo |
|
255 |
|
256 def is_internal_session(self): |
|
257 """overrided on the server-side""" |
|
258 return False |
|
259 |
|
260 # abstract methods to override according to the web front-end ############# |
|
261 |
|
262 def base_url(self): |
|
263 """return the root url of the instance""" |
|
264 raise NotImplementedError |
|
265 |
|
266 def decorate_rset(self, rset): |
|
267 """add vreg/req (at least) attributes to the given result set """ |
|
268 raise NotImplementedError |
|
269 |
|
270 def describe(self, eid): |
|
271 """return a tuple (type, sourceuri, extid) for the entity with id <eid>""" |
|
272 raise NotImplementedError |
|
273 |
|
274 |
|
275 # XXX 2.45 is allowing nicer entity type names, use this map for bw compat |
|
276 ETYPE_NAME_MAP = {# 3.2 migration |
|
277 'ECache': 'CWCache', |
|
278 'EUser': 'CWUser', |
|
279 'EGroup': 'CWGroup', |
|
280 'EProperty': 'CWProperty', |
|
281 'EFRDef': 'CWAttribute', |
|
282 'ENFRDef': 'CWRelation', |
|
283 'ERType': 'CWRType', |
|
284 'EEType': 'CWEType', |
|
285 'EConstraintType': 'CWConstraintType', |
|
286 'EConstraint': 'CWConstraint', |
|
287 'EPermission': 'CWPermission', |
|
288 # 2.45 migration |
|
289 'Eetype': 'CWEType', |
|
290 'Ertype': 'CWRType', |
|
291 'Efrdef': 'CWAttribute', |
|
292 'Enfrdef': 'CWRelation', |
|
293 'Econstraint': 'CWConstraint', |
|
294 'Econstrainttype': 'CWConstraintType', |
|
295 'Epermission': 'CWPermission', |
|
296 'Egroup': 'CWGroup', |
|
297 'Euser': 'CWUser', |
|
298 'Eproperty': 'CWProperty', |
|
299 'Emailaddress': 'EmailAddress', |
|
300 'Rqlexpression': 'RQLExpression', |
|
301 'Trinfo': 'TrInfo', |
|
302 } |
|
303 |
|
304 |
|
305 |
|
306 # XXX cubic web cube migration map |
|
307 CW_MIGRATION_MAP = {'erudi': 'cubicweb', |
|
308 |
|
309 'eaddressbook': 'addressbook', |
|
310 'ebasket': 'basket', |
|
311 'eblog': 'blog', |
|
312 'ebook': 'book', |
|
313 'ecomment': 'comment', |
|
314 'ecompany': 'company', |
|
315 'econference': 'conference', |
|
316 'eemail': 'email', |
|
317 'eevent': 'event', |
|
318 'eexpense': 'expense', |
|
319 'efile': 'file', |
|
320 'einvoice': 'invoice', |
|
321 'elink': 'link', |
|
322 'emailinglist': 'mailinglist', |
|
323 'eperson': 'person', |
|
324 'eshopcart': 'shopcart', |
|
325 'eskillmat': 'skillmat', |
|
326 'etask': 'task', |
|
327 'eworkcase': 'workcase', |
|
328 'eworkorder': 'workorder', |
|
329 'ezone': 'zone', |
|
330 'i18ncontent': 'i18ncontent', |
|
331 'svnfile': 'vcsfile', |
|
332 |
|
333 'eclassschemes': 'keyword', |
|
334 'eclassfolders': 'folder', |
|
335 'eclasstags': 'tag', |
|
336 |
|
337 'jpl': 'jpl', |
|
338 'jplintra': 'jplintra', |
|
339 'jplextra': 'jplextra', |
|
340 'jplorg': 'jplorg', |
|
341 'jplrecia': 'jplrecia', |
|
342 'crm': 'crm', |
|
343 'agueol': 'agueol', |
|
344 'docaster': 'docaster', |
|
345 'asteretud': 'asteretud', |
|
346 } |
|
347 |
65 |
348 def neg_role(role): |
66 def neg_role(role): |
349 if role == 'subject': |
67 if role == 'subject': |
350 return 'object' |
68 return 'object' |
351 return 'subject' |
69 return 'subject' |