25 import random |
25 import random |
26 import base64 |
26 import base64 |
27 from datetime import date |
27 from datetime import date |
28 from urlparse import urlsplit |
28 from urlparse import urlsplit |
29 from itertools import count |
29 from itertools import count |
|
30 from warnings import warn |
30 |
31 |
31 from rql.utils import rqlvar_maker |
32 from rql.utils import rqlvar_maker |
32 |
33 |
33 from logilab.common.decorators import cached |
34 from logilab.common.decorators import cached |
34 from logilab.common.deprecation import deprecated |
35 from logilab.common.deprecation import deprecated |
35 from logilab.mtconverter import xml_escape |
36 from logilab.mtconverter import xml_escape |
36 |
37 |
37 from cubicweb.dbapi import DBAPIRequest |
38 from cubicweb.dbapi import DBAPIRequest |
38 from cubicweb.mail import header |
39 from cubicweb.mail import header |
39 from cubicweb.uilib import remove_html_tags |
40 from cubicweb.uilib import remove_html_tags, js |
40 from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid |
41 from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid |
41 from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT |
42 from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT |
42 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit, |
43 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit, |
43 RequestError, StatusResponse, json) |
44 RequestError, StatusResponse) |
44 from cubicweb.web.http_headers import Headers |
45 from cubicweb.web.http_headers import Headers |
45 |
|
46 dumps = json.dumps |
|
47 |
46 |
48 _MARKER = object() |
47 _MARKER = object() |
49 |
48 |
50 |
49 |
51 def list_form_param(form, param, pop=False): |
50 def list_form_param(form, param, pop=False): |
81 |
80 |
82 def __init__(self, vreg, https, form=None): |
81 def __init__(self, vreg, https, form=None): |
83 super(CubicWebRequestBase, self).__init__(vreg) |
82 super(CubicWebRequestBase, self).__init__(vreg) |
84 self.authmode = vreg.config['auth-mode'] |
83 self.authmode = vreg.config['auth-mode'] |
85 self.https = https |
84 self.https = https |
|
85 if https: |
|
86 self.uiprops = vreg.config.https_uiprops |
|
87 self.datadir_url = vreg.config.https_datadir_url |
|
88 else: |
|
89 self.uiprops = vreg.config.uiprops |
|
90 self.datadir_url = vreg.config.datadir_url |
86 # raw html headers that can be added from any view |
91 # raw html headers that can be added from any view |
87 self.html_headers = HTMLHead() |
92 self.html_headers = HTMLHead() |
88 # form parameters |
93 # form parameters |
89 self.setup_params(form) |
94 self.setup_params(form) |
90 # dictionnary that may be used to store request data that has to be |
95 # dictionnary that may be used to store request data that has to be |
92 # controller, application...) |
97 # controller, application...) |
93 self.data = {} |
98 self.data = {} |
94 # search state: 'normal' or 'linksearch' (eg searching for an object |
99 # search state: 'normal' or 'linksearch' (eg searching for an object |
95 # to create a relation with another) |
100 # to create a relation with another) |
96 self.search_state = ('normal',) |
101 self.search_state = ('normal',) |
97 # tabindex generator |
|
98 self.tabindexgen = count(1) |
|
99 self.next_tabindex = self.tabindexgen.next |
|
100 # page id, set by htmlheader template |
102 # page id, set by htmlheader template |
101 self.pageid = None |
103 self.pageid = None |
102 self.datadir_url = self._datadir_url() |
|
103 self._set_pageid() |
104 self._set_pageid() |
104 # prepare output header |
105 # prepare output header |
105 self.headers_out = Headers() |
106 self.headers_out = Headers() |
106 |
107 |
107 def _set_pageid(self): |
108 def _set_pageid(self): |
137 def set_session(self, session, user=None): |
145 def set_session(self, session, user=None): |
138 """method called by the session handler when the user is authenticated |
146 """method called by the session handler when the user is authenticated |
139 or an anonymous connection is open |
147 or an anonymous connection is open |
140 """ |
148 """ |
141 super(CubicWebRequestBase, self).set_session(session, user) |
149 super(CubicWebRequestBase, self).set_session(session, user) |
|
150 # tabindex generator |
|
151 self.next_tabindex = self._get_tabindex_func() |
142 # set request language |
152 # set request language |
143 vreg = self.vreg |
153 vreg = self.vreg |
144 if self.user: |
154 if self.user: |
145 try: |
155 try: |
146 # 1. user specified language |
156 # 1. user specified language |
337 breadcrumbs = self.session.data.get('breadcrumbs') |
347 breadcrumbs = self.session.data.get('breadcrumbs') |
338 if breadcrumbs: |
348 if breadcrumbs: |
339 return breadcrumbs.pop() |
349 return breadcrumbs.pop() |
340 return self.base_url() |
350 return self.base_url() |
341 |
351 |
342 def user_rql_callback(self, args, msg=None): |
352 def user_rql_callback(self, rqlargs, *args, **kwargs): |
343 """register a user callback to execute some rql query and return an url |
353 """register a user callback to execute some rql query and return an url |
344 to call it ready to be inserted in html |
354 to call it ready to be inserted in html. |
|
355 |
|
356 rqlargs should be a tuple containing argument to give to the execute function. |
|
357 |
|
358 For other allowed arguments, see :meth:`user_callback` method |
345 """ |
359 """ |
346 def rqlexec(req, rql, args=None, key=None): |
360 def rqlexec(req, rql, args=None, key=None): |
347 req.execute(rql, args, key) |
361 req.execute(rql, args, key) |
348 return self.user_callback(rqlexec, args, msg) |
362 return self.user_callback(rqlexec, rqlargs, *args, **kwargs) |
349 |
363 |
350 def user_callback(self, cb, args, msg=None, nonify=False): |
364 def user_callback(self, cb, cbargs, *args, **kwargs): |
351 """register the given user callback and return an url to call it ready to be |
365 """register the given user callback and return an url to call it ready |
352 inserted in html |
366 to be inserted in html. |
|
367 |
|
368 You can specify the underlying js function to call using a 'jsfunc' |
|
369 named args, to one of :func:`userCallback`, |
|
370 ':func:`userCallbackThenUpdateUI`, ':func:`userCallbackThenReloadPage` |
|
371 (the default). Take care arguments may vary according to the used |
|
372 function. |
353 """ |
373 """ |
354 self.add_js('cubicweb.ajax.js') |
374 self.add_js('cubicweb.ajax.js') |
355 cbname = self.register_onetime_callback(cb, *args) |
375 jsfunc = kwargs.pop('jsfunc', 'userCallbackThenReloadPage') |
356 msg = dumps(msg or '') |
376 if 'msg' in kwargs: |
357 return "javascript:userCallbackThenReloadPage('%s', %s)" % ( |
377 warn('[3.10] msg should be given as positional argument', |
358 cbname, msg) |
378 DeprecationWarning, stacklevel=2) |
|
379 args = (kwargs.pop('msg'),) + args |
|
380 assert not kwargs, 'dunno what to do with remaining kwargs: %s' % kwargs |
|
381 cbname = self.register_onetime_callback(cb, *cbargs) |
|
382 return "javascript: %s" % getattr(js, jsfunc)(cbname, *args) |
359 |
383 |
360 def register_onetime_callback(self, func, *args): |
384 def register_onetime_callback(self, func, *args): |
361 cbname = 'cb_%s' % ( |
385 cbname = 'cb_%s' % ( |
362 hashlib.sha1('%s%s%s%s' % (time.time(), func.__name__, |
386 hashlib.sha1('%s%s%s%s' % (time.time(), func.__name__, |
363 random.random(), |
387 random.random(), |
364 self.user.login)).hexdigest()) |
388 self.user.login)).hexdigest()) |
365 def _cb(req): |
389 def _cb(req): |
366 try: |
390 try: |
367 ret = func(req, *args) |
391 ret = func(req, *args) |
368 except TypeError: |
392 except TypeError: |
369 from warnings import warn |
|
370 warn('[3.2] user callback should now take request as argument') |
393 warn('[3.2] user callback should now take request as argument') |
371 ret = func(*args) |
394 ret = func(*args) |
372 self.unregister_callback(self.pageid, cbname) |
395 self.unregister_callback(self.pageid, cbname) |
373 return ret |
396 return ret |
374 self.set_page_data(cbname, _cb) |
397 self.set_page_data(cbname, _cb) |
506 |
529 |
507 def set_content_type(self, content_type, filename=None, encoding=None): |
530 def set_content_type(self, content_type, filename=None, encoding=None): |
508 """set output content type for this request. An optional filename |
531 """set output content type for this request. An optional filename |
509 may be given |
532 may be given |
510 """ |
533 """ |
511 if content_type.startswith('text/'): |
534 if content_type.startswith('text/') and ';charset=' not in content_type: |
512 content_type += ';charset=' + (encoding or self.encoding) |
535 content_type += ';charset=' + (encoding or self.encoding) |
513 self.set_header('content-type', content_type) |
536 self.set_header('content-type', content_type) |
514 if filename: |
537 if filename: |
515 if isinstance(filename, unicode): |
538 if isinstance(filename, unicode): |
516 filename = header(filename).encode() |
539 filename = header(filename).encode() |
562 for cssfile in cssfiles: |
585 for cssfile in cssfiles: |
563 if localfile: |
586 if localfile: |
564 cssfile = self.datadir_url + cssfile |
587 cssfile = self.datadir_url + cssfile |
565 add_css(cssfile, media, *extraargs) |
588 add_css(cssfile, media, *extraargs) |
566 |
589 |
|
590 @deprecated('[3.9] use ajax_replace_url() instead, naming rql and vid arguments') |
567 def build_ajax_replace_url(self, nodeid, rql, vid, replacemode='replace', |
591 def build_ajax_replace_url(self, nodeid, rql, vid, replacemode='replace', |
568 **extraparams): |
592 **extraparams): |
|
593 return self.ajax_replace_url(nodeid, replacemode, rql=rql, vid=vid, |
|
594 **extraparams) |
|
595 |
|
596 def ajax_replace_url(self, nodeid, replacemode='replace', **extraparams): |
569 """builds an ajax url that will replace nodeid's content |
597 """builds an ajax url that will replace nodeid's content |
570 |
598 |
571 :param nodeid: the dom id of the node to replace |
599 :param nodeid: the dom id of the node to replace |
572 :param rql: rql to execute |
|
573 :param vid: the view to apply on the resultset |
|
574 :param replacemode: defines how the replacement should be done. |
600 :param replacemode: defines how the replacement should be done. |
575 |
601 |
576 Possible values are : |
602 Possible values are : |
577 - 'replace' to replace the node's content with the generated HTML |
603 - 'replace' to replace the node's content with the generated HTML |
578 - 'swap' to replace the node itself with the generated HTML |
604 - 'swap' to replace the node itself with the generated HTML |
579 - 'append' to append the generated HTML to the node's content |
605 - 'append' to append the generated HTML to the node's content |
580 """ |
606 |
581 url = self.build_url('view', rql=rql, vid=vid, __notemplate=1, |
607 Arbitrary extra named arguments may be given, they will be included as |
582 **extraparams) |
608 parameters of the generated url. |
583 return "javascript: loadxhtml('%s', '%s', '%s')" % ( |
609 """ |
584 nodeid, xml_escape(url), replacemode) |
610 extraparams.setdefault('fname', 'view') |
|
611 url = self.build_url('json', **extraparams) |
|
612 return "javascript: $('#%s').%s; noop()" % ( |
|
613 nodeid, js.loadxhtml(url, None, 'get', replacemode)) |
585 |
614 |
586 # urls/path management #################################################### |
615 # urls/path management #################################################### |
587 |
616 |
588 def url(self, includeparams=True): |
617 def url(self, includeparams=True): |
589 """return currently accessed url""" |
618 """return currently accessed url""" |
590 return self.base_url() + self.relative_path(includeparams) |
619 return self.base_url() + self.relative_path(includeparams) |
591 |
|
592 def _datadir_url(self): |
|
593 """return url of the instance's data directory""" |
|
594 return self.base_url() + 'data%s/' % self.vreg.config.instance_md5_version() |
|
595 |
620 |
596 def selected(self, url): |
621 def selected(self, url): |
597 """return True if the url is equivalent to currently accessed url""" |
622 """return True if the url is equivalent to currently accessed url""" |
598 reqpath = self.relative_path().lower() |
623 reqpath = self.relative_path().lower() |
599 baselen = len(self.base_url()) |
624 baselen = len(self.base_url()) |
615 controller = self.relative_path(False).split('/', 1)[0] |
640 controller = self.relative_path(False).split('/', 1)[0] |
616 registered_controllers = self.vreg['controllers'].keys() |
641 registered_controllers = self.vreg['controllers'].keys() |
617 if controller in registered_controllers: |
642 if controller in registered_controllers: |
618 return controller |
643 return controller |
619 return 'view' |
644 return 'view' |
620 |
|
621 def external_resource(self, rid, default=_MARKER): |
|
622 """return a path to an external resource, using its identifier |
|
623 |
|
624 raise KeyError if the resource is not defined |
|
625 """ |
|
626 try: |
|
627 value = self.vreg.config.ext_resources[rid] |
|
628 except KeyError: |
|
629 if default is _MARKER: |
|
630 raise |
|
631 return default |
|
632 if value is None: |
|
633 return None |
|
634 baseurl = self.datadir_url[:-1] # remove trailing / |
|
635 if isinstance(value, list): |
|
636 return [v.replace('DATADIR', baseurl) for v in value] |
|
637 return value.replace('DATADIR', baseurl) |
|
638 external_resource = cached(external_resource, keyarg=1) |
|
639 |
645 |
640 def validate_cache(self): |
646 def validate_cache(self): |
641 """raise a `DirectResponse` exception if a cached page along the way |
647 """raise a `DirectResponse` exception if a cached page along the way |
642 exists and is still usable. |
648 exists and is still usable. |
643 |
649 |
709 return user.decode('UTF8'), passwd |
715 return user.decode('UTF8'), passwd |
710 except Exception, ex: |
716 except Exception, ex: |
711 self.debug('bad authorization %s (%s: %s)', |
717 self.debug('bad authorization %s (%s: %s)', |
712 auth, ex.__class__.__name__, ex) |
718 auth, ex.__class__.__name__, ex) |
713 return None, None |
719 return None, None |
714 |
|
715 @deprecated("[3.4] use parse_accept_header('Accept-Language')") |
|
716 def header_accept_language(self): |
|
717 """returns an ordered list of preferred languages""" |
|
718 return [value.split('-')[0] for value in |
|
719 self.parse_accept_header('Accept-Language')] |
|
720 |
720 |
721 def parse_accept_header(self, header): |
721 def parse_accept_header(self, header): |
722 """returns an ordered list of preferred languages""" |
722 """returns an ordered list of preferred languages""" |
723 accepteds = self.get_header(header, '') |
723 accepteds = self.get_header(header, '') |
724 values = [] |
724 values = [] |
821 if self.xhtml_browser(): |
821 if self.xhtml_browser(): |
822 return (u'<?xml version="1.0"?>\n' + STRICT_DOCTYPE + # XXX encoding ? |
822 return (u'<?xml version="1.0"?>\n' + STRICT_DOCTYPE + # XXX encoding ? |
823 u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">') |
823 u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">') |
824 return u'<div>' |
824 return u'<div>' |
825 |
825 |
|
826 @deprecated('[3.9] use req.uiprops[rid]') |
|
827 def external_resource(self, rid, default=_MARKER): |
|
828 """return a path to an external resource, using its identifier |
|
829 |
|
830 raise `KeyError` if the resource is not defined |
|
831 """ |
|
832 try: |
|
833 return self.uiprops[rid] |
|
834 except KeyError: |
|
835 if default is _MARKER: |
|
836 raise |
|
837 return default |
|
838 |
|
839 @deprecated("[3.4] use parse_accept_header('Accept-Language')") |
|
840 def header_accept_language(self): |
|
841 """returns an ordered list of preferred languages""" |
|
842 return [value.split('-')[0] for value in |
|
843 self.parse_accept_header('Accept-Language')] |
|
844 |
|
845 |
826 from cubicweb import set_log_methods |
846 from cubicweb import set_log_methods |
827 set_log_methods(CubicWebRequestBase, LOGGER) |
847 set_log_methods(CubicWebRequestBase, LOGGER) |