| 1 | #!/usr/bin/python2.3 | 
|---|
| 2 | # | 
|---|
| 3 | # vim:set et ts=4 fdc=0 fdn=2 fdl=0: | 
|---|
| 4 | # | 
|---|
| 5 | # There are no blank lines between blocks beacause i use folding from: | 
|---|
| 6 | # http://www.vim.org/scripts/script.php?script_id=515 | 
|---|
| 7 | # | 
|---|
| 8 |  | 
|---|
| 9 | """= QWeb Framework = | 
|---|
| 10 |  | 
|---|
| 11 | == What is QWeb ? == | 
|---|
| 12 |  | 
|---|
| 13 | QWeb is a python based [http://www.python.org/doc/peps/pep-0333/ WSGI] | 
|---|
| 14 | compatible web framework, it provides an infratructure to quickly build web | 
|---|
| 15 | applications consisting of: | 
|---|
| 16 |  | 
|---|
| 17 | * A lightweight request handler (QWebRequest) | 
|---|
| 18 | * An xml templating engine (QWebXml and QWebHtml) | 
|---|
| 19 | * A simple name based controler (qweb_control) | 
|---|
| 20 | * A standalone WSGI Server (QWebWSGIServer) | 
|---|
| 21 | * A cgi and fastcgi WSGI wrapper (taken from flup) | 
|---|
| 22 | * A startup function that starts cgi, factgi or standalone according to the | 
|---|
| 23 | evironement (qweb_autorun). | 
|---|
| 24 |  | 
|---|
| 25 | QWeb applications are runnable in standalone mode (from commandline), via | 
|---|
| 26 | FastCGI, Regular CGI or by any python WSGI compliant server. | 
|---|
| 27 |  | 
|---|
| 28 | QWeb doesn't provide any database access but it integrates nicely with ORMs | 
|---|
| 29 | such as SQLObject, SQLAlchemy or plain DB-API. | 
|---|
| 30 |  | 
|---|
| 31 | Written by Antony Lesuisse (email al AT udev.org) | 
|---|
| 32 |  | 
|---|
| 33 | Homepage: http://antony.lesuisse.org/qweb/trac/ | 
|---|
| 34 |  | 
|---|
| 35 | Forum: [http://antony.lesuisse.org/qweb/forum/viewforum.php?id=1 Forum] | 
|---|
| 36 |  | 
|---|
| 37 | == Quick Start (for Linux, MacOS X and cygwin) == | 
|---|
| 38 |  | 
|---|
| 39 | Make sure you have at least python 2.3 installed and run the following commands: | 
|---|
| 40 |  | 
|---|
| 41 | {{{ | 
|---|
| 42 | $ wget http://antony.lesuisse.org/qweb/files/QWeb-0.7.tar.gz | 
|---|
| 43 | $ tar zxvf QWeb-0.7.tar.gz | 
|---|
| 44 | $ cd QWeb-0.7/examples/blog | 
|---|
| 45 | $ ./blog.py | 
|---|
| 46 | }}} | 
|---|
| 47 |  | 
|---|
| 48 | And point your browser to http://localhost:8080/ | 
|---|
| 49 |  | 
|---|
| 50 | You may also try AjaxTerm which uses qweb request handler. | 
|---|
| 51 |  | 
|---|
| 52 | == Download == | 
|---|
| 53 |  | 
|---|
| 54 | * Version 0.7: | 
|---|
| 55 | * Source [/qweb/files/QWeb-0.7.tar.gz QWeb-0.7.tar.gz] | 
|---|
| 56 | * Python 2.3 Egg [/qweb/files/QWeb-0.7-py2.3.egg QWeb-0.7-py2.3.egg] | 
|---|
| 57 | * Python 2.4 Egg [/qweb/files/QWeb-0.7-py2.4.egg QWeb-0.7-py2.4.egg] | 
|---|
| 58 |  | 
|---|
| 59 | * [/qweb/trac/browser Browse the source repository] | 
|---|
| 60 |  | 
|---|
| 61 | == Documentation == | 
|---|
| 62 |  | 
|---|
| 63 | * [/qweb/trac/browser/trunk/README.txt?format=raw Read the included documentation] | 
|---|
| 64 | * QwebTemplating | 
|---|
| 65 |  | 
|---|
| 66 | == Mailin-list == | 
|---|
| 67 |  | 
|---|
| 68 | * Forum: [http://antony.lesuisse.org/qweb/forum/viewforum.php?id=1 Forum] | 
|---|
| 69 | * No mailing-list exists yet, discussion should happen on: [http://mail.python.org/mailman/listinfo/web-sig web-sig] [http://mail.python.org/pipermail/web-sig/ archives] | 
|---|
| 70 |  | 
|---|
| 71 | QWeb Components: | 
|---|
| 72 | ---------------- | 
|---|
| 73 |  | 
|---|
| 74 | QWeb also feature a simple components api, that enables developers to easily | 
|---|
| 75 | produces reusable components. | 
|---|
| 76 |  | 
|---|
| 77 | Default qweb components: | 
|---|
| 78 |  | 
|---|
| 79 | - qweb_static: | 
|---|
| 80 | A qweb component to serve static content from the filesystem or from | 
|---|
| 81 | zipfiles. | 
|---|
| 82 |  | 
|---|
| 83 | - qweb_dbadmin: | 
|---|
| 84 | scaffolding for sqlobject | 
|---|
| 85 |  | 
|---|
| 86 | License | 
|---|
| 87 | ------- | 
|---|
| 88 | qweb/fcgi.py wich is BSD-like from saddi.com. | 
|---|
| 89 | Everything else is put in the public domain. | 
|---|
| 90 |  | 
|---|
| 91 |  | 
|---|
| 92 | TODO | 
|---|
| 93 | ---- | 
|---|
| 94 | Announce QWeb to python-announce-list@python.org web-sig@python.org | 
|---|
| 95 | qweb_core | 
|---|
| 96 | rename request methods into | 
|---|
| 97 | request_save_files | 
|---|
| 98 | response_404 | 
|---|
| 99 | response_redirect | 
|---|
| 100 | response_download | 
|---|
| 101 | request callback_generator, callback_function ? | 
|---|
| 102 | wsgi callback_server_local | 
|---|
| 103 | xml tags explicitly call render_attributes(t_att)? | 
|---|
| 104 | priority form-checkbox over t-value (for t-option) | 
|---|
| 105 |  | 
|---|
| 106 | """ | 
|---|
| 107 |  | 
|---|
| 108 | import BaseHTTPServer,SocketServer,Cookie | 
|---|
| 109 | import cgi,datetime,email,email.Message,errno,gzip,os,random,re,socket,sys,tempfile,time,types,urllib,urlparse,xml.dom | 
|---|
| 110 | try: | 
|---|
| 111 | import cPickle as pickle | 
|---|
| 112 | except ImportError: | 
|---|
| 113 | import pickle | 
|---|
| 114 | try: | 
|---|
| 115 | import cStringIO as StringIO | 
|---|
| 116 | except ImportError: | 
|---|
| 117 | import StringIO | 
|---|
| 118 |  | 
|---|
| 119 | #---------------------------------------------------------- | 
|---|
| 120 | # Qweb Xml t-raw t-esc t-if t-foreach t-set t-call t-trim | 
|---|
| 121 | #---------------------------------------------------------- | 
|---|
| 122 | class QWebEval: | 
|---|
| 123 | def __init__(self,data): | 
|---|
| 124 | self.data=data | 
|---|
| 125 | def __getitem__(self,expr): | 
|---|
| 126 | if self.data.has_key(expr): | 
|---|
| 127 | return self.data[expr] | 
|---|
| 128 | r=None | 
|---|
| 129 | try: | 
|---|
| 130 | r=eval(expr,self.data) | 
|---|
| 131 | except NameError,e: | 
|---|
| 132 | pass | 
|---|
| 133 | except AttributeError,e: | 
|---|
| 134 | pass | 
|---|
| 135 | except Exception,e: | 
|---|
| 136 | print "qweb: expression error '%s' "%expr,e | 
|---|
| 137 | if self.data.has_key("__builtins__"): | 
|---|
| 138 | del self.data["__builtins__"] | 
|---|
| 139 | return r | 
|---|
| 140 | def eval_object(self,expr): | 
|---|
| 141 | return self[expr] | 
|---|
| 142 | def eval_str(self,expr): | 
|---|
| 143 | if expr=="0": | 
|---|
| 144 | return self.data[0] | 
|---|
| 145 | if isinstance(self[expr],unicode): | 
|---|
| 146 | return self[expr].encode("utf8") | 
|---|
| 147 | return str(self[expr]) | 
|---|
| 148 | def eval_format(self,expr): | 
|---|
| 149 | try: | 
|---|
| 150 | return str(expr%self) | 
|---|
| 151 | except: | 
|---|
| 152 | return "qweb: format error '%s' "%expr | 
|---|
| 153 | #       if isinstance(r,unicode): | 
|---|
| 154 | #           return r.encode("utf8") | 
|---|
| 155 | def eval_bool(self,expr): | 
|---|
| 156 | if self.eval_object(expr): | 
|---|
| 157 | return 1 | 
|---|
| 158 | else: | 
|---|
| 159 | return 0 | 
|---|
| 160 | class QWebXml: | 
|---|
| 161 | """QWeb Xml templating engine | 
|---|
| 162 |  | 
|---|
| 163 | The templating engine use a very simple syntax, "magic" xml attributes, to | 
|---|
| 164 | produce any kind of texutal output (even non-xml). | 
|---|
| 165 |  | 
|---|
| 166 | QWebXml: | 
|---|
| 167 | the template engine core implements the basic magic attributes: | 
|---|
| 168 |  | 
|---|
| 169 | t-att t-raw t-esc t-if t-foreach t-set t-call t-trim | 
|---|
| 170 |  | 
|---|
| 171 | """ | 
|---|
| 172 | def __init__(self,x=None,zipname=None): | 
|---|
| 173 | self.node=xml.dom.Node | 
|---|
| 174 | self._t={} | 
|---|
| 175 | self._render_tag={} | 
|---|
| 176 | prefix='render_tag_' | 
|---|
| 177 | for i in [j for j in dir(self) if j.startswith(prefix)]: | 
|---|
| 178 | name=i[len(prefix):].replace('_','-') | 
|---|
| 179 | self._render_tag[name]=getattr(self.__class__,i) | 
|---|
| 180 |  | 
|---|
| 181 | self._render_att={} | 
|---|
| 182 | prefix='render_att_' | 
|---|
| 183 | for i in [j for j in dir(self) if j.startswith(prefix)]: | 
|---|
| 184 | name=i[len(prefix):].replace('_','-') | 
|---|
| 185 | self._render_att[name]=getattr(self.__class__,i) | 
|---|
| 186 |  | 
|---|
| 187 | if x!=None: | 
|---|
| 188 | if zipname!=None: | 
|---|
| 189 | import zipfile | 
|---|
| 190 | zf=zipfile.ZipFile(zipname, 'r') | 
|---|
| 191 | self.add_template(zf.read(x)) | 
|---|
| 192 | else: | 
|---|
| 193 | self.add_template(x) | 
|---|
| 194 | def register_tag(self,tag,func): | 
|---|
| 195 | self._render_tag[tag]=func | 
|---|
| 196 | def add_template(self,x): | 
|---|
| 197 | if hasattr(x,'documentElement'): | 
|---|
| 198 | dom=x | 
|---|
| 199 | elif x.startswith("<?xml"): | 
|---|
| 200 | import xml.dom.minidom | 
|---|
| 201 | dom=xml.dom.minidom.parseString(x) | 
|---|
| 202 | else: | 
|---|
| 203 | import xml.dom.minidom | 
|---|
| 204 | dom=xml.dom.minidom.parse(x) | 
|---|
| 205 | for n in dom.documentElement.childNodes: | 
|---|
| 206 | if n.nodeName=="t": | 
|---|
| 207 | self._t[str(n.getAttribute("t-name"))]=n | 
|---|
| 208 | def get_template(self,name): | 
|---|
| 209 | return self._t[name] | 
|---|
| 210 |  | 
|---|
| 211 | def eval_object(self,expr,v): | 
|---|
| 212 | return QWebEval(v).eval_object(expr) | 
|---|
| 213 | def eval_str(self,expr,v): | 
|---|
| 214 | return QWebEval(v).eval_str(expr) | 
|---|
| 215 | def eval_format(self,expr,v): | 
|---|
| 216 | return QWebEval(v).eval_format(expr) | 
|---|
| 217 | def eval_bool(self,expr,v): | 
|---|
| 218 | return QWebEval(v).eval_bool(expr) | 
|---|
| 219 |  | 
|---|
| 220 | def render(self,tname,v={},out=None): | 
|---|
| 221 | if self._t.has_key(tname): | 
|---|
| 222 | return self.render_node(self._t[tname],v) | 
|---|
| 223 | else: | 
|---|
| 224 | return 'qweb: template "%s" not found'%tname | 
|---|
| 225 | def render_node(self,e,v): | 
|---|
| 226 | r="" | 
|---|
| 227 | if e.nodeType==self.node.TEXT_NODE or e.nodeType==self.node.CDATA_SECTION_NODE: | 
|---|
| 228 | r=e.data.encode("utf8") | 
|---|
| 229 | elif e.nodeType==self.node.ELEMENT_NODE: | 
|---|
| 230 | pre="" | 
|---|
| 231 | g_att="" | 
|---|
| 232 | t_render=None | 
|---|
| 233 | t_att={} | 
|---|
| 234 | for (an,av) in e.attributes.items(): | 
|---|
| 235 | an=str(an) | 
|---|
| 236 | if isinstance(av,types.UnicodeType): | 
|---|
| 237 | av=av.encode("utf8") | 
|---|
| 238 | else: | 
|---|
| 239 | av=av.nodeValue.encode("utf8") | 
|---|
| 240 | if an.startswith("t-"): | 
|---|
| 241 | for i in self._render_att: | 
|---|
| 242 | if an[2:].startswith(i): | 
|---|
| 243 | g_att+=self._render_att[i](self,e,an,av,v) | 
|---|
| 244 | break | 
|---|
| 245 | else: | 
|---|
| 246 | if self._render_tag.has_key(an[2:]): | 
|---|
| 247 | t_render=an[2:] | 
|---|
| 248 | t_att[an[2:]]=av | 
|---|
| 249 | else: | 
|---|
| 250 | g_att+=' %s="%s"'%(an,cgi.escape(av,1)); | 
|---|
| 251 | if t_render: | 
|---|
| 252 | if self._render_tag.has_key(t_render): | 
|---|
| 253 | r=self._render_tag[t_render](self,e,t_att,g_att,v) | 
|---|
| 254 | else: | 
|---|
| 255 | r=self.render_element(e,g_att,v,pre,t_att.get("trim",0)) | 
|---|
| 256 | return r | 
|---|
| 257 | def render_element(self,e,g_att,v,pre="",trim=0): | 
|---|
| 258 | g_inner=[] | 
|---|
| 259 | for n in e.childNodes: | 
|---|
| 260 | g_inner.append(self.render_node(n,v)) | 
|---|
| 261 | name=str(e.nodeName) | 
|---|
| 262 | inner="".join(g_inner) | 
|---|
| 263 | if trim==0: | 
|---|
| 264 | pass | 
|---|
| 265 | elif trim=='left': | 
|---|
| 266 | inner=inner.lstrip() | 
|---|
| 267 | elif trim=='right': | 
|---|
| 268 | inner=inner.rstrip() | 
|---|
| 269 | elif trim=='both': | 
|---|
| 270 | inner=inner.strip() | 
|---|
| 271 | if name=="t": | 
|---|
| 272 | return inner | 
|---|
| 273 | elif len(inner): | 
|---|
| 274 | return "<%s%s>%s%s</%s>"%(name,g_att,pre,inner,name) | 
|---|
| 275 | else: | 
|---|
| 276 | return "<%s%s/>"%(name,g_att) | 
|---|
| 277 |  | 
|---|
| 278 | # Attributes | 
|---|
| 279 | def render_att_att(self,e,an,av,v): | 
|---|
| 280 | if an.startswith("t-attf-"): | 
|---|
| 281 | att,val=an[7:],self.eval_format(av,v) | 
|---|
| 282 | elif an.startswith("t-att-"): | 
|---|
| 283 | att,val=(an[6:],self.eval_str(av,v)) | 
|---|
| 284 | else: | 
|---|
| 285 | att,val=self.eval_object(av,v) | 
|---|
| 286 | return ' %s="%s"'%(att,cgi.escape(val,1)) | 
|---|
| 287 |  | 
|---|
| 288 | # Tags | 
|---|
| 289 | def render_tag_raw(self,e,t_att,g_att,v): | 
|---|
| 290 | return self.eval_str(t_att["raw"],v) | 
|---|
| 291 | def render_tag_rawf(self,e,t_att,g_att,v): | 
|---|
| 292 | return self.eval_format(t_att["rawf"],v) | 
|---|
| 293 | def render_tag_esc(self,e,t_att,g_att,v): | 
|---|
| 294 | return cgi.escape(self.eval_str(t_att["esc"],v)) | 
|---|
| 295 | def render_tag_escf(self,e,t_att,g_att,v): | 
|---|
| 296 | return cgi.escape(self.eval_format(t_att["escf"],v)) | 
|---|
| 297 | def render_tag_foreach(self,e,t_att,g_att,v): | 
|---|
| 298 | expr=t_att["foreach"] | 
|---|
| 299 | enum=self.eval_object(expr,v) | 
|---|
| 300 | if enum!=None: | 
|---|
| 301 | var=t_att.get('as',expr).replace('.','_') | 
|---|
| 302 | d=v.copy() | 
|---|
| 303 | size=-1 | 
|---|
| 304 | if isinstance(enum,types.ListType): | 
|---|
| 305 | size=len(enum) | 
|---|
| 306 | elif isinstance(enum,types.TupleType): | 
|---|
| 307 | size=len(enum) | 
|---|
| 308 | elif hasattr(enum,'count'): | 
|---|
| 309 | size=enum.count() | 
|---|
| 310 | d["%s_size"%var]=size | 
|---|
| 311 | d["%s_all"%var]=enum | 
|---|
| 312 | index=0 | 
|---|
| 313 | ru=[] | 
|---|
| 314 | for i in enum: | 
|---|
| 315 | d["%s_value"%var]=i | 
|---|
| 316 | d["%s_index"%var]=index | 
|---|
| 317 | d["%s_first"%var]=index==0 | 
|---|
| 318 | d["%s_even"%var]=index%2 | 
|---|
| 319 | d["%s_odd"%var]=(index+1)%2 | 
|---|
| 320 | d["%s_last"%var]=index+1==size | 
|---|
| 321 | if index%2: | 
|---|
| 322 | d["%s_parity"%var]='odd' | 
|---|
| 323 | else: | 
|---|
| 324 | d["%s_parity"%var]='even' | 
|---|
| 325 | if isinstance(i,types.DictType): | 
|---|
| 326 | d.update(i) | 
|---|
| 327 | else: | 
|---|
| 328 | d[var]=i | 
|---|
| 329 | ru.append(self.render_element(e,g_att,d)) | 
|---|
| 330 | index+=1 | 
|---|
| 331 | return "".join(ru) | 
|---|
| 332 | else: | 
|---|
| 333 | return "qweb: t-foreach %s not found."%expr | 
|---|
| 334 | def render_tag_if(self,e,t_att,g_att,v): | 
|---|
| 335 | if self.eval_bool(t_att["if"],v): | 
|---|
| 336 | return self.render_element(e,g_att,v) | 
|---|
| 337 | else: | 
|---|
| 338 | return "" | 
|---|
| 339 | def render_tag_call(self,e,t_att,g_att,v): | 
|---|
| 340 | # TODO t-prefix | 
|---|
| 341 | if t_att.has_key("import"): | 
|---|
| 342 | d=v | 
|---|
| 343 | else: | 
|---|
| 344 | d=v.copy() | 
|---|
| 345 | d[0]=self.render_element(e,g_att,d) | 
|---|
| 346 | return self.render(t_att["call"],d) | 
|---|
| 347 | def render_tag_set(self,e,t_att,g_att,v): | 
|---|
| 348 | if t_att.has_key("eval"): | 
|---|
| 349 | v[t_att["set"]]=self.eval_object(t_att["eval"],v) | 
|---|
| 350 | else: | 
|---|
| 351 | v[t_att["set"]]=self.render_element(e,g_att,v) | 
|---|
| 352 | return "" | 
|---|
| 353 |  | 
|---|
| 354 | #---------------------------------------------------------- | 
|---|
| 355 | # QWeb HTML (+deprecated QWebFORM and QWebOLD) | 
|---|
| 356 | #---------------------------------------------------------- | 
|---|
| 357 | class QWebURL: | 
|---|
| 358 | """ URL helper | 
|---|
| 359 | assert req.PATH_INFO== "/site/admin/page_edit" | 
|---|
| 360 | u = QWebURL(root_path="/site/",req_path=req.PATH_INFO) | 
|---|
| 361 | s=u.url2_href("user/login",{'a':'1'}) | 
|---|
| 362 | assert s=="../user/login?a=1" | 
|---|
| 363 |  | 
|---|
| 364 | """ | 
|---|
| 365 | def __init__(self, root_path="/", req_path="/",defpath="",defparam={}): | 
|---|
| 366 | self.defpath=defpath | 
|---|
| 367 | self.defparam=defparam | 
|---|
| 368 | self.root_path=root_path | 
|---|
| 369 | self.req_path=req_path | 
|---|
| 370 | self.req_list=req_path.split("/")[:-1] | 
|---|
| 371 | self.req_len=len(self.req_list) | 
|---|
| 372 | def decode(self,s): | 
|---|
| 373 | h={} | 
|---|
| 374 | for k,v in cgi.parse_qsl(s,1): | 
|---|
| 375 | h[k]=v | 
|---|
| 376 | return h | 
|---|
| 377 | def encode(self,h): | 
|---|
| 378 | return urllib.urlencode(h.items()) | 
|---|
| 379 | def request(self,req): | 
|---|
| 380 | return req.REQUEST | 
|---|
| 381 | def copy(self,path=None,param=None): | 
|---|
| 382 | npath=self.defpath | 
|---|
| 383 | if path: | 
|---|
| 384 | npath=path | 
|---|
| 385 | nparam=self.defparam.copy() | 
|---|
| 386 | if param: | 
|---|
| 387 | nparam.update(param) | 
|---|
| 388 | return QWebURL(self.root_path,self.req_path,npath,nparam) | 
|---|
| 389 | def path(self,path=''): | 
|---|
| 390 | if not path: | 
|---|
| 391 | path=self.defpath | 
|---|
| 392 | pl=(self.root_path+path).split('/') | 
|---|
| 393 | i=0 | 
|---|
| 394 | for i in range(min(len(pl), self.req_len)): | 
|---|
| 395 | if pl[i]!=self.req_list[i]: | 
|---|
| 396 | break | 
|---|
| 397 | else: | 
|---|
| 398 | i+=1 | 
|---|
| 399 | dd=self.req_len-i | 
|---|
| 400 | if dd<0: | 
|---|
| 401 | dd=0 | 
|---|
| 402 | return '/'.join(['..']*dd+pl[i:]) | 
|---|
| 403 | def href(self,path='',arg={}): | 
|---|
| 404 | p=self.path(path) | 
|---|
| 405 | tmp=self.defparam.copy() | 
|---|
| 406 | tmp.update(arg) | 
|---|
| 407 | s=self.encode(tmp) | 
|---|
| 408 | if len(s): | 
|---|
| 409 | return p+"?"+s | 
|---|
| 410 | else: | 
|---|
| 411 | return p | 
|---|
| 412 | def form(self,path='',arg={}): | 
|---|
| 413 | p=self.path(path) | 
|---|
| 414 | tmp=self.defparam.copy() | 
|---|
| 415 | tmp.update(arg) | 
|---|
| 416 | r=''.join(['<input type="hidden" name="%s" value="%s"/>'%(k,cgi.escape(str(v),1)) for k,v in tmp.items()]) | 
|---|
| 417 | return (p,r) | 
|---|
| 418 | class QWebField: | 
|---|
| 419 | def __init__(self,name=None,default="",check=None): | 
|---|
| 420 | self.name=name | 
|---|
| 421 | self.default=default | 
|---|
| 422 | self.check=check | 
|---|
| 423 | # optional attributes | 
|---|
| 424 | self.type=None | 
|---|
| 425 | self.trim=1 | 
|---|
| 426 | self.required=1 | 
|---|
| 427 | self.cssvalid="form_valid" | 
|---|
| 428 | self.cssinvalid="form_invalid" | 
|---|
| 429 | # set by addfield | 
|---|
| 430 | self.form=None | 
|---|
| 431 | # set by processing | 
|---|
| 432 | self.input=None | 
|---|
| 433 | self.css=None | 
|---|
| 434 | self.value=None | 
|---|
| 435 | self.valid=None | 
|---|
| 436 | self.invalid=None | 
|---|
| 437 | self.validate(1) | 
|---|
| 438 | def validate(self,val=1,update=1): | 
|---|
| 439 | if val: | 
|---|
| 440 | self.valid=1 | 
|---|
| 441 | self.invalid=0 | 
|---|
| 442 | self.css=self.cssvalid | 
|---|
| 443 | else: | 
|---|
| 444 | self.valid=0 | 
|---|
| 445 | self.invalid=1 | 
|---|
| 446 | self.css=self.cssinvalid | 
|---|
| 447 | if update and self.form: | 
|---|
| 448 | self.form.update() | 
|---|
| 449 | def invalidate(self,update=1): | 
|---|
| 450 | self.validate(0,update) | 
|---|
| 451 | class QWebForm: | 
|---|
| 452 | class QWebFormF: | 
|---|
| 453 | pass | 
|---|
| 454 | def __init__(self,e=None,arg=None,default=None): | 
|---|
| 455 | self.fields={} | 
|---|
| 456 | # all fields have been submitted | 
|---|
| 457 | self.submitted=False | 
|---|
| 458 | self.missing=[] | 
|---|
| 459 | # at least one field is invalid or missing | 
|---|
| 460 | self.invalid=False | 
|---|
| 461 | self.error=[] | 
|---|
| 462 | # all fields have been submitted and are valid | 
|---|
| 463 | self.valid=False | 
|---|
| 464 | # fields under self.f for convenience | 
|---|
| 465 | self.f=self.QWebFormF() | 
|---|
| 466 | if e: | 
|---|
| 467 | self.add_template(e) | 
|---|
| 468 | # assume that the fields are done with the template | 
|---|
| 469 | if default: | 
|---|
| 470 | self.set_default(default,e==None) | 
|---|
| 471 | if arg!=None: | 
|---|
| 472 | self.process_input(arg) | 
|---|
| 473 | def __getitem__(self,k): | 
|---|
| 474 | return self.fields[k] | 
|---|
| 475 | def set_default(self,default,add_missing=1): | 
|---|
| 476 | for k,v in default.items(): | 
|---|
| 477 | if self.fields.has_key(k): | 
|---|
| 478 | self.fields[k].default=str(v) | 
|---|
| 479 | elif add_missing: | 
|---|
| 480 | self.add_field(QWebField(k,v)) | 
|---|
| 481 | def add_field(self,f): | 
|---|
| 482 | self.fields[f.name]=f | 
|---|
| 483 | f.form=self | 
|---|
| 484 | setattr(self.f,f.name,f) | 
|---|
| 485 | def add_template(self,e): | 
|---|
| 486 | att={} | 
|---|
| 487 | for (an,av) in e.attributes.items(): | 
|---|
| 488 | an=str(an) | 
|---|
| 489 | if an.startswith("t-"): | 
|---|
| 490 | att[an[2:]]=av.encode("utf8") | 
|---|
| 491 | for i in ["form-text", "form-password", "form-radio", "form-checkbox", "form-select","form-textarea"]: | 
|---|
| 492 | if att.has_key(i): | 
|---|
| 493 | name=att[i].split(".")[-1] | 
|---|
| 494 | default=att.get("default","") | 
|---|
| 495 | check=att.get("check",None) | 
|---|
| 496 | f=QWebField(name,default,check) | 
|---|
| 497 | if i=="form-textarea": | 
|---|
| 498 | f.type="textarea" | 
|---|
| 499 | f.trim=0 | 
|---|
| 500 | if i=="form-checkbox": | 
|---|
| 501 | f.type="checkbox" | 
|---|
| 502 | f.required=0 | 
|---|
| 503 | self.add_field(f) | 
|---|
| 504 | for n in e.childNodes: | 
|---|
| 505 | if n.nodeType==n.ELEMENT_NODE: | 
|---|
| 506 | self.add_template(n) | 
|---|
| 507 | def process_input(self,arg): | 
|---|
| 508 | for f in self.fields.values(): | 
|---|
| 509 | if arg.has_key(f.name): | 
|---|
| 510 | f.input=arg[f.name] | 
|---|
| 511 | f.value=f.input | 
|---|
| 512 | if f.trim: | 
|---|
| 513 | f.input=f.input.strip() | 
|---|
| 514 | f.validate(1,False) | 
|---|
| 515 | if f.check==None: | 
|---|
| 516 | continue | 
|---|
| 517 | elif callable(f.check): | 
|---|
| 518 | pass | 
|---|
| 519 | elif isinstance(f.check,str): | 
|---|
| 520 | v=f.check | 
|---|
| 521 | if f.check=="email": | 
|---|
| 522 | v=r"/^[^@#!& ]+@[A-Za-z0-9-][.A-Za-z0-9-]{0,64}\.[A-Za-z]{2,5}$/" | 
|---|
| 523 | if f.check=="date": | 
|---|
| 524 | v=r"/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/" | 
|---|
| 525 | if not re.match(v[1:-1],f.input): | 
|---|
| 526 | f.validate(0,False) | 
|---|
| 527 | else: | 
|---|
| 528 | f.value=f.default | 
|---|
| 529 | self.update() | 
|---|
| 530 | def validate_all(self,val=1): | 
|---|
| 531 | for f in self.fields.values(): | 
|---|
| 532 | f.validate(val,0) | 
|---|
| 533 | self.update() | 
|---|
| 534 | def invalidate_all(self): | 
|---|
| 535 | self.validate_all(0) | 
|---|
| 536 | def update(self): | 
|---|
| 537 | self.submitted=True | 
|---|
| 538 | self.valid=True | 
|---|
| 539 | self.errors=[] | 
|---|
| 540 | for f in self.fields.values(): | 
|---|
| 541 | if f.required and f.input==None: | 
|---|
| 542 | self.submitted=False | 
|---|
| 543 | self.valid=False | 
|---|
| 544 | self.missing.append(f.name) | 
|---|
| 545 | if f.invalid: | 
|---|
| 546 | self.valid=False | 
|---|
| 547 | self.error.append(f.name) | 
|---|
| 548 | # invalid have been submitted and | 
|---|
| 549 | self.invalid=self.submitted and self.valid==False | 
|---|
| 550 | def collect(self): | 
|---|
| 551 | d={} | 
|---|
| 552 | for f in self.fields.values(): | 
|---|
| 553 | d[f.name]=f.value | 
|---|
| 554 | return d | 
|---|
| 555 | class QWebURLEval(QWebEval): | 
|---|
| 556 | def __init__(self,data): | 
|---|
| 557 | QWebEval.__init__(self,data) | 
|---|
| 558 | def __getitem__(self,expr): | 
|---|
| 559 | r=QWebEval.__getitem__(self,expr) | 
|---|
| 560 | if isinstance(r,str): | 
|---|
| 561 | return urllib.quote_plus(r) | 
|---|
| 562 | else: | 
|---|
| 563 | return r | 
|---|
| 564 | class QWebHtml(QWebXml): | 
|---|
| 565 | """QWebHtml | 
|---|
| 566 | QWebURL: | 
|---|
| 567 | QWebField: | 
|---|
| 568 | QWebForm: | 
|---|
| 569 | QWebHtml: | 
|---|
| 570 | an extended template engine, with a few utility class to easily produce | 
|---|
| 571 | HTML, handle URLs and process forms, it adds the following magic attributes: | 
|---|
| 572 |  | 
|---|
| 573 | t-href t-action t-form-text t-form-password t-form-textarea t-form-radio | 
|---|
| 574 | t-form-checkbox t-form-select t-option t-selected t-checked t-pager | 
|---|
| 575 |  | 
|---|
| 576 | # explication URL: | 
|---|
| 577 | # v['tableurl']=QWebUrl({p=afdmin,saar=,orderby=,des=,mlink;meta_active=}) | 
|---|
| 578 | # t-href="tableurl?desc=1" | 
|---|
| 579 | # | 
|---|
| 580 | # explication FORM: t-if="form.valid()" | 
|---|
| 581 | # Foreach i | 
|---|
| 582 | #   email: <input type="text" t-esc-name="i" t-esc-value="form[i].value" t-esc-class="form[i].css"/> | 
|---|
| 583 | #   <input type="radio" name="spamtype" t-esc-value="i" t-selected="i==form.f.spamtype.value"/> | 
|---|
| 584 | #   <option t-esc-value="cc" t-selected="cc==form.f.country.value"><t t-esc="cname"></option> | 
|---|
| 585 | # Simple forms: | 
|---|
| 586 | #   <input t-form-text="form.email" t-check="email"/> | 
|---|
| 587 | #   <input t-form-password="form.email" t-check="email"/> | 
|---|
| 588 | #   <input t-form-radio="form.email" /> | 
|---|
| 589 | #   <input t-form-checkbox="form.email" /> | 
|---|
| 590 | #   <textarea t-form-textarea="form.email" t-check="email"/> | 
|---|
| 591 | #   <select t-form-select="form.email"/> | 
|---|
| 592 | #       <option t-value="1"> | 
|---|
| 593 | #   <input t-form-radio="form.spamtype" t-value="1"/> Cars | 
|---|
| 594 | #   <input t-form-radio="form.spamtype" t-value="2"/> Sprt | 
|---|
| 595 | """ | 
|---|
| 596 | # QWebForm from a template | 
|---|
| 597 | def form(self,tname,arg=None,default=None): | 
|---|
| 598 | form=QWebForm(self._t[tname],arg,default) | 
|---|
| 599 | return form | 
|---|
| 600 |  | 
|---|
| 601 | # HTML Att | 
|---|
| 602 | def eval_url(self,av,v): | 
|---|
| 603 | s=QWebURLEval(v).eval_format(av) | 
|---|
| 604 | a=s.split('?',1) | 
|---|
| 605 | arg={} | 
|---|
| 606 | if len(a)>1: | 
|---|
| 607 | for k,v in cgi.parse_qsl(a[1],1): | 
|---|
| 608 | arg[k]=v | 
|---|
| 609 | b=a[0].split('/',1) | 
|---|
| 610 | path='' | 
|---|
| 611 | if len(b)>1: | 
|---|
| 612 | path=b[1] | 
|---|
| 613 | u=b[0] | 
|---|
| 614 | return u,path,arg | 
|---|
| 615 | def render_att_url_(self,e,an,av,v): | 
|---|
| 616 | u,path,arg=self.eval_url(av,v) | 
|---|
| 617 | if not isinstance(v.get(u,0),QWebURL): | 
|---|
| 618 | out='qweb: missing url %r %r %r'%(u,path,arg) | 
|---|
| 619 | else: | 
|---|
| 620 | out=v[u].href(path,arg) | 
|---|
| 621 | return ' %s="%s"'%(an[6:],cgi.escape(out,1)) | 
|---|
| 622 | def render_att_href(self,e,an,av,v): | 
|---|
| 623 | return self.render_att_url_(e,"t-url-href",av,v) | 
|---|
| 624 | def render_att_checked(self,e,an,av,v): | 
|---|
| 625 | if self.eval_bool(av,v): | 
|---|
| 626 | return ' %s="%s"'%(an[2:],an[2:]) | 
|---|
| 627 | else: | 
|---|
| 628 | return '' | 
|---|
| 629 | def render_att_selected(self,e,an,av,v): | 
|---|
| 630 | return self.render_att_checked(e,an,av,v) | 
|---|
| 631 |  | 
|---|
| 632 | # HTML Tags forms | 
|---|
| 633 | def render_tag_rawurl(self,e,t_att,g_att,v): | 
|---|
| 634 | u,path,arg=self.eval_url(t_att["rawurl"],v) | 
|---|
| 635 | return v[u].href(path,arg) | 
|---|
| 636 | def render_tag_escurl(self,e,t_att,g_att,v): | 
|---|
| 637 | u,path,arg=self.eval_url(t_att["escurl"],v) | 
|---|
| 638 | return cgi.escape(v[u].href(path,arg)) | 
|---|
| 639 | def render_tag_action(self,e,t_att,g_att,v): | 
|---|
| 640 | u,path,arg=self.eval_url(t_att["action"],v) | 
|---|
| 641 | if not isinstance(v.get(u,0),QWebURL): | 
|---|
| 642 | action,input=('qweb: missing url %r %r %r'%(u,path,arg),'') | 
|---|
| 643 | else: | 
|---|
| 644 | action,input=v[u].form(path,arg) | 
|---|
| 645 | g_att+=' action="%s"'%action | 
|---|
| 646 | return self.render_element(e,g_att,v,input) | 
|---|
| 647 | def render_tag_form_text(self,e,t_att,g_att,v): | 
|---|
| 648 | f=self.eval_object(t_att["form-text"],v) | 
|---|
| 649 | g_att+=' type="text" name="%s" value="%s" class="%s"'%(f.name,cgi.escape(f.value,1),f.css) | 
|---|
| 650 | return self.render_element(e,g_att,v) | 
|---|
| 651 | def render_tag_form_password(self,e,t_att,g_att,v): | 
|---|
| 652 | f=self.eval_object(t_att["form-password"],v) | 
|---|
| 653 | g_att+=' type="password" name="%s" value="%s" class="%s"'%(f.name,cgi.escape(f.value,1),f.css) | 
|---|
| 654 | return self.render_element(e,g_att,v) | 
|---|
| 655 | def render_tag_form_textarea(self,e,t_att,g_att,v): | 
|---|
| 656 | type="textarea" | 
|---|
| 657 | f=self.eval_object(t_att["form-textarea"],v) | 
|---|
| 658 | g_att+=' name="%s" class="%s"'%(f.name,f.css) | 
|---|
| 659 | r="<%s%s>%s</%s>"%(type,g_att,cgi.escape(f.value,1),type) | 
|---|
| 660 | return r | 
|---|
| 661 | def render_tag_form_radio(self,e,t_att,g_att,v): | 
|---|
| 662 | f=self.eval_object(t_att["form-radio"],v) | 
|---|
| 663 | val=t_att["value"] | 
|---|
| 664 | g_att+=' type="radio" name="%s" value="%s"'%(f.name,val) | 
|---|
| 665 | if f.value==val: | 
|---|
| 666 | g_att+=' checked="checked"' | 
|---|
| 667 | return self.render_element(e,g_att,v) | 
|---|
| 668 | def render_tag_form_checkbox(self,e,t_att,g_att,v): | 
|---|
| 669 | f=self.eval_object(t_att["form-checkbox"],v) | 
|---|
| 670 | val=t_att["value"] | 
|---|
| 671 | g_att+=' type="checkbox" name="%s" value="%s"'%(f.name,val) | 
|---|
| 672 | if f.value==val: | 
|---|
| 673 | g_att+=' checked="checked"' | 
|---|
| 674 | return self.render_element(e,g_att,v) | 
|---|
| 675 | def render_tag_form_select(self,e,t_att,g_att,v): | 
|---|
| 676 | f=self.eval_object(t_att["form-select"],v) | 
|---|
| 677 | g_att+=' name="%s" class="%s"'%(f.name,f.css) | 
|---|
| 678 | return self.render_element(e,g_att,v) | 
|---|
| 679 | def render_tag_option(self,e,t_att,g_att,v): | 
|---|
| 680 | f=self.eval_object(e.parentNode.getAttribute("t-form-select"),v) | 
|---|
| 681 | val=t_att["option"] | 
|---|
| 682 | g_att+=' value="%s"'%(val) | 
|---|
| 683 | if f.value==val: | 
|---|
| 684 | g_att+=' selected="selected"' | 
|---|
| 685 | return self.render_element(e,g_att,v) | 
|---|
| 686 |  | 
|---|
| 687 | # HTML Tags others | 
|---|
| 688 | def render_tag_pager(self,e,t_att,g_att,v): | 
|---|
| 689 | pre=t_att["pager"] | 
|---|
| 690 | total=int(self.eval_str(t_att["total"],v)) | 
|---|
| 691 | start=int(self.eval_str(t_att["start"],v)) | 
|---|
| 692 | step=int(self.eval_str(t_att.get("step","100"),v)) | 
|---|
| 693 | scope=int(self.eval_str(t_att.get("scope","5"),v)) | 
|---|
| 694 | # Compute Pager | 
|---|
| 695 | p=pre+"_" | 
|---|
| 696 | d={} | 
|---|
| 697 | d[p+"tot_size"]=total | 
|---|
| 698 | d[p+"tot_page"]=tot_page=total/step | 
|---|
| 699 | d[p+"win_start0"]=total and start | 
|---|
| 700 | d[p+"win_start1"]=total and start+1 | 
|---|
| 701 | d[p+"win_end0"]=max(0,min(start+step-1,total-1)) | 
|---|
| 702 | d[p+"win_end1"]=min(start+step,total) | 
|---|
| 703 | d[p+"win_page0"]=win_page=start/step | 
|---|
| 704 | d[p+"win_page1"]=win_page+1 | 
|---|
| 705 | d[p+"prev"]=(win_page!=0) | 
|---|
| 706 | d[p+"prev_start"]=(win_page-1)*step | 
|---|
| 707 | d[p+"next"]=(tot_page>=win_page+1) | 
|---|
| 708 | d[p+"next_start"]=(win_page+1)*step | 
|---|
| 709 | l=[] | 
|---|
| 710 | begin=win_page-scope | 
|---|
| 711 | end=win_page+scope | 
|---|
| 712 | if begin<0: | 
|---|
| 713 | end-=begin | 
|---|
| 714 | if end>tot_page: | 
|---|
| 715 | begin-=(end-tot_page) | 
|---|
| 716 | i=max(0,begin) | 
|---|
| 717 | while i<=min(end,tot_page) and total!=step: | 
|---|
| 718 | l.append( { p+"page0":i, p+"page1":i+1, p+"start":i*step, p+"sel":(win_page==i) }) | 
|---|
| 719 | i+=1 | 
|---|
| 720 | d[p+"active"]=len(l)>1 | 
|---|
| 721 | d[p+"list"]=l | 
|---|
| 722 | # Update v | 
|---|
| 723 | v.update(d) | 
|---|
| 724 | return "" | 
|---|
| 725 |  | 
|---|
| 726 | #---------------------------------------------------------- | 
|---|
| 727 | # QWeb Simple Controller | 
|---|
| 728 | #---------------------------------------------------------- | 
|---|
| 729 | def qweb_control(self,jump='main',p=[]): | 
|---|
| 730 | """ qweb_control(self,jump='main',p=[]): | 
|---|
| 731 | A simple function to handle the controler part of your application. It | 
|---|
| 732 | dispatch the control to the jump argument, while ensuring that prefix | 
|---|
| 733 | function have been called. | 
|---|
| 734 |  | 
|---|
| 735 | qweb_control replace '/' to '_' and strip '_' from the jump argument. | 
|---|
| 736 |  | 
|---|
| 737 | name1 | 
|---|
| 738 | name1_name2 | 
|---|
| 739 | name1_name2_name3 | 
|---|
| 740 |  | 
|---|
| 741 | """ | 
|---|
| 742 | jump=jump.replace('/','_').strip('_') | 
|---|
| 743 | if not hasattr(self,jump): | 
|---|
| 744 | return 0 | 
|---|
| 745 | done={} | 
|---|
| 746 | todo=[] | 
|---|
| 747 | while 1: | 
|---|
| 748 | if jump!=None: | 
|---|
| 749 | tmp="" | 
|---|
| 750 | todo=[] | 
|---|
| 751 | for i in jump.split("_"): | 
|---|
| 752 | tmp+=i+"_"; | 
|---|
| 753 | if not done.has_key(tmp[:-1]): | 
|---|
| 754 | todo.append(tmp[:-1]) | 
|---|
| 755 | jump=None | 
|---|
| 756 | elif len(todo): | 
|---|
| 757 | i=todo.pop(0) | 
|---|
| 758 | done[i]=1 | 
|---|
| 759 | if hasattr(self,i): | 
|---|
| 760 | f=getattr(self,i) | 
|---|
| 761 | r=f(*p) | 
|---|
| 762 | if isinstance(r,types.StringType): | 
|---|
| 763 | jump=r | 
|---|
| 764 | else: | 
|---|
| 765 | break | 
|---|
| 766 | return 1 | 
|---|
| 767 |  | 
|---|
| 768 | #---------------------------------------------------------- | 
|---|
| 769 | # QWeb WSGI Request handler | 
|---|
| 770 | #---------------------------------------------------------- | 
|---|
| 771 | class QWebSession(dict): | 
|---|
| 772 | def __init__(self,environ,**kw): | 
|---|
| 773 | dict.__init__(self) | 
|---|
| 774 | default={ | 
|---|
| 775 | "path" : tempfile.gettempdir(), | 
|---|
| 776 | "cookie_name" : "QWEBSID", | 
|---|
| 777 | "cookie_lifetime" : 0, | 
|---|
| 778 | "cookie_path" : '/', | 
|---|
| 779 | "cookie_domain" : '', | 
|---|
| 780 | "limit_cache" : 1, | 
|---|
| 781 | "probability" : 0.01, | 
|---|
| 782 | "maxlifetime" : 3600, | 
|---|
| 783 | "disable" : 0, | 
|---|
| 784 | } | 
|---|
| 785 | for k,v in default.items(): | 
|---|
| 786 | setattr(self,'session_%s'%k,kw.get(k,v)) | 
|---|
| 787 | # Try to find session | 
|---|
| 788 | self.session_found_cookie=0 | 
|---|
| 789 | self.session_found_url=0 | 
|---|
| 790 | self.session_found=0 | 
|---|
| 791 | self.session_orig="" | 
|---|
| 792 | # Try cookie | 
|---|
| 793 | c=Cookie.SimpleCookie() | 
|---|
| 794 | c.load(environ.get('HTTP_COOKIE', '')) | 
|---|
| 795 | if c.has_key(self.session_cookie_name): | 
|---|
| 796 | sid=c[self.session_cookie_name].value[:64] | 
|---|
| 797 | if re.match('[a-f0-9]+$',sid) and self.session_load(sid): | 
|---|
| 798 | self.session_id=sid | 
|---|
| 799 | self.session_found_cookie=1 | 
|---|
| 800 | self.session_found=1 | 
|---|
| 801 | # Try URL | 
|---|
| 802 | if not self.session_found_cookie: | 
|---|
| 803 | mo=re.search('&%s=([a-f0-9]+)'%self.session_cookie_name,environ.get('QUERY_STRING','')) | 
|---|
| 804 | if mo and self.session_load(mo.group(1)): | 
|---|
| 805 | self.session_id=mo.group(1) | 
|---|
| 806 | self.session_found_url=1 | 
|---|
| 807 | self.session_found=1 | 
|---|
| 808 | # New session | 
|---|
| 809 | if not self.session_found: | 
|---|
| 810 | self.session_id='%032x'%random.randint(1,2**128) | 
|---|
| 811 | self.session_trans_sid="&%s=%s"%(self.session_cookie_name,self.session_id) | 
|---|
| 812 | # Clean old session | 
|---|
| 813 | if random.random() < self.session_probability: | 
|---|
| 814 | self.session_clean() | 
|---|
| 815 | def session_get_headers(self): | 
|---|
| 816 | h=[] | 
|---|
| 817 | if (not self.session_disable) and (len(self) or len(self.session_orig)): | 
|---|
| 818 | self.session_save() | 
|---|
| 819 | if not self.session_found_cookie: | 
|---|
| 820 | c=Cookie.SimpleCookie() | 
|---|
| 821 | c[self.session_cookie_name] = self.session_id | 
|---|
| 822 | c[self.session_cookie_name]['path'] = self.session_cookie_path | 
|---|
| 823 | if self.session_cookie_domain: | 
|---|
| 824 | c[self.session_cookie_name]['domain'] = self.session_cookie_domain | 
|---|
| 825 | #               if self.session_cookie_lifetime: | 
|---|
| 826 | #                   c[self.session_cookie_name]['expires'] = TODO date localtime or not, datetime.datetime(1970, 1, 1) | 
|---|
| 827 | h.append(("Set-Cookie", c[self.session_cookie_name].OutputString())) | 
|---|
| 828 | if self.session_limit_cache: | 
|---|
| 829 | h.append(('Cache-Control','no-store, no-cache, must-revalidate, post-check=0, pre-check=0')) | 
|---|
| 830 | h.append(('Expires','Thu, 19 Nov 1981 08:52:00 GMT')) | 
|---|
| 831 | h.append(('Pragma','no-cache')) | 
|---|
| 832 | return h | 
|---|
| 833 | def session_load(self,sid): | 
|---|
| 834 | fname=os.path.join(self.session_path,'qweb_sess_%s'%sid) | 
|---|
| 835 | try: | 
|---|
| 836 | orig=file(fname).read() | 
|---|
| 837 | d=pickle.loads(orig) | 
|---|
| 838 | except: | 
|---|
| 839 | return | 
|---|
| 840 | self.session_orig=orig | 
|---|
| 841 | self.update(d) | 
|---|
| 842 | return 1 | 
|---|
| 843 | def session_save(self): | 
|---|
| 844 | if not os.path.isdir(self.session_path): | 
|---|
| 845 | os.makedirs(self.session_path) | 
|---|
| 846 | fname=os.path.join(self.session_path,'qweb_sess_%s'%self.session_id) | 
|---|
| 847 | try: | 
|---|
| 848 | oldtime=os.path.getmtime(fname) | 
|---|
| 849 | except OSError,IOError: | 
|---|
| 850 | oldtime=0 | 
|---|
| 851 | dump=pickle.dumps(self.copy()) | 
|---|
| 852 | if (dump != self.session_orig) or (time.time() > oldtime+self.session_maxlifetime/4): | 
|---|
| 853 | tmpname=os.path.join(self.session_path,'qweb_sess_%s_%x'%(self.session_id,random.randint(1,2**32))) | 
|---|
| 854 | f=file(tmpname,'wb') | 
|---|
| 855 | f.write(dump) | 
|---|
| 856 | f.close() | 
|---|
| 857 | if sys.platform=='win32' and os.path.isfile(fname): | 
|---|
| 858 | os.remove(fname) | 
|---|
| 859 | os.rename(tmpname,fname) | 
|---|
| 860 | def session_clean(self): | 
|---|
| 861 | t=time.time() | 
|---|
| 862 | try: | 
|---|
| 863 | for i in [os.path.join(self.session_path,i) for i in os.listdir(self.session_path) if i.startswith('qweb_sess_')]: | 
|---|
| 864 | if (t > os.path.getmtime(i)+self.session_maxlifetime): | 
|---|
| 865 | os.unlink(i) | 
|---|
| 866 | except OSError,IOError: | 
|---|
| 867 | pass | 
|---|
| 868 | class QWebSessionMem(QWebSession): | 
|---|
| 869 | def session_load(self,sid): | 
|---|
| 870 | global _qweb_sessions | 
|---|
| 871 | if not "_qweb_sessions" in globals(): | 
|---|
| 872 | _qweb_sessions={} | 
|---|
| 873 | if _qweb_sessions.has_key(sid): | 
|---|
| 874 | self.session_orig=_qweb_sessions[sid] | 
|---|
| 875 | self.update(self.session_orig) | 
|---|
| 876 | return 1 | 
|---|
| 877 | def session_save(self): | 
|---|
| 878 | global _qweb_sessions | 
|---|
| 879 | if not "_qweb_sessions" in globals(): | 
|---|
| 880 | _qweb_sessions={} | 
|---|
| 881 | _qweb_sessions[self.session_id]=self.copy() | 
|---|
| 882 | class QWebSessionService: | 
|---|
| 883 | def __init__(self, wsgiapp, url_rewrite=0): | 
|---|
| 884 | self.wsgiapp=wsgiapp | 
|---|
| 885 | self.url_rewrite_tags="a=href,area=href,frame=src,form=,fieldset=" | 
|---|
| 886 | def __call__(self, environ, start_response): | 
|---|
| 887 | # TODO | 
|---|
| 888 | # use QWebSession to provide environ["qweb.session"] | 
|---|
| 889 | return self.wsgiapp(environ,start_response) | 
|---|
| 890 | class QWebDict(dict): | 
|---|
| 891 | def __init__(self,*p): | 
|---|
| 892 | dict.__init__(self,*p) | 
|---|
| 893 | def __getitem__(self,key): | 
|---|
| 894 | return self.get(key,"") | 
|---|
| 895 | def int(self,key): | 
|---|
| 896 | try: | 
|---|
| 897 | return int(self.get(key,"0")) | 
|---|
| 898 | except ValueError: | 
|---|
| 899 | return 0 | 
|---|
| 900 | class QWebListDict(dict): | 
|---|
| 901 | def __init__(self,*p): | 
|---|
| 902 | dict.__init__(self,*p) | 
|---|
| 903 | def __getitem__(self,key): | 
|---|
| 904 | return self.get(key,[]) | 
|---|
| 905 | def appendlist(self,key,val): | 
|---|
| 906 | if self.has_key(key): | 
|---|
| 907 | self[key].append(val) | 
|---|
| 908 | else: | 
|---|
| 909 | self[key]=[val] | 
|---|
| 910 | def get_qwebdict(self): | 
|---|
| 911 | d=QWebDict() | 
|---|
| 912 | for k,v in self.items(): | 
|---|
| 913 | d[k]=v[-1] | 
|---|
| 914 | return d | 
|---|
| 915 | class QWebRequest: | 
|---|
| 916 | """QWebRequest a WSGI request handler. | 
|---|
| 917 |  | 
|---|
| 918 | QWebRequest is a WSGI request handler that feature GET, POST and POST | 
|---|
| 919 | multipart methods, handles cookies and headers and provide a dict-like | 
|---|
| 920 | SESSION Object (either on the filesystem or in memory). | 
|---|
| 921 |  | 
|---|
| 922 | It is constructed with the environ and start_response WSGI arguments: | 
|---|
| 923 |  | 
|---|
| 924 | req=qweb.QWebRequest(environ, start_response) | 
|---|
| 925 |  | 
|---|
| 926 | req has the folowing attributes : | 
|---|
| 927 |  | 
|---|
| 928 | req.environ standard WSGI dict (CGI and wsgi ones) | 
|---|
| 929 |  | 
|---|
| 930 | Some CGI vars as attributes from environ for convenience: | 
|---|
| 931 |  | 
|---|
| 932 | req.SCRIPT_NAME | 
|---|
| 933 | req.PATH_INFO | 
|---|
| 934 | req.REQUEST_URI | 
|---|
| 935 |  | 
|---|
| 936 | Some computed value (also for convenience) | 
|---|
| 937 |  | 
|---|
| 938 | req.FULL_URL full URL recontructed (http://host/query) | 
|---|
| 939 | req.FULL_PATH (URL path before ?querystring) | 
|---|
| 940 |  | 
|---|
| 941 | Dict constructed from querystring and POST datas, PHP-like. | 
|---|
| 942 |  | 
|---|
| 943 | req.GET contains GET vars | 
|---|
| 944 | req.POST contains POST vars | 
|---|
| 945 | req.REQUEST contains merge of GET and POST | 
|---|
| 946 | req.FILES contains uploaded files | 
|---|
| 947 | req.GET_LIST req.POST_LIST req.REQUEST_LIST req.FILES_LIST multiple arguments versions | 
|---|
| 948 | req.debug() returns an HTML dump of those vars | 
|---|
| 949 |  | 
|---|
| 950 | A dict-like session object. | 
|---|
| 951 |  | 
|---|
| 952 | req.SESSION the session start when the dict is not empty. | 
|---|
| 953 |  | 
|---|
| 954 | Attribute for handling the response | 
|---|
| 955 |  | 
|---|
| 956 | req.response_headers dict-like to set headers | 
|---|
| 957 | req.response_cookies a SimpleCookie to set cookies | 
|---|
| 958 | req.response_status a string to set the status like '200 OK' | 
|---|
| 959 |  | 
|---|
| 960 | req.write() to write to the buffer | 
|---|
| 961 |  | 
|---|
| 962 | req itselfs is an iterable object with the buffer, it will also also call | 
|---|
| 963 | start_response automatically before returning anything via the iterator. | 
|---|
| 964 |  | 
|---|
| 965 | To make it short, it means that you may use | 
|---|
| 966 |  | 
|---|
| 967 | return req | 
|---|
| 968 |  | 
|---|
| 969 | at the end of your request handling to return the reponse to any WSGI | 
|---|
| 970 | application server. | 
|---|
| 971 | """ | 
|---|
| 972 | # | 
|---|
| 973 | # This class contains part ripped from colubrid (with the permission of | 
|---|
| 974 | # mitsuhiko) see http://wsgiarea.pocoo.org/colubrid/ | 
|---|
| 975 | # | 
|---|
| 976 | # - the class HttpHeaders | 
|---|
| 977 | # - the method load_post_data (tuned version) | 
|---|
| 978 | # | 
|---|
| 979 | class HttpHeaders(object): | 
|---|
| 980 | def __init__(self): | 
|---|
| 981 | self.data = [('Content-Type', 'text/html')] | 
|---|
| 982 | def __setitem__(self, key, value): | 
|---|
| 983 | self.set(key, value) | 
|---|
| 984 | def __delitem__(self, key): | 
|---|
| 985 | self.remove(key) | 
|---|
| 986 | def __contains__(self, key): | 
|---|
| 987 | key = key.lower() | 
|---|
| 988 | for k, v in self.data: | 
|---|
| 989 | if k.lower() == key: | 
|---|
| 990 | return True | 
|---|
| 991 | return False | 
|---|
| 992 | def add(self, key, value): | 
|---|
| 993 | self.data.append((key, value)) | 
|---|
| 994 | def remove(self, key, count=-1): | 
|---|
| 995 | removed = 0 | 
|---|
| 996 | data = [] | 
|---|
| 997 | for _key, _value in self.data: | 
|---|
| 998 | if _key.lower() != key.lower(): | 
|---|
| 999 | if count > -1: | 
|---|
| 1000 | if removed >= count: | 
|---|
| 1001 | break | 
|---|
| 1002 | else: | 
|---|
| 1003 | removed += 1 | 
|---|
| 1004 | data.append((_key, _value)) | 
|---|
| 1005 | self.data = data | 
|---|
| 1006 | def clear(self): | 
|---|
| 1007 | self.data = [] | 
|---|
| 1008 | def set(self, key, value): | 
|---|
| 1009 | self.remove(key) | 
|---|
| 1010 | self.add(key, value) | 
|---|
| 1011 | def get(self, key=False, httpformat=False): | 
|---|
| 1012 | if not key: | 
|---|
| 1013 | result = self.data | 
|---|
| 1014 | else: | 
|---|
| 1015 | result = [] | 
|---|
| 1016 | for _key, _value in self.data: | 
|---|
| 1017 | if _key.lower() == key.lower(): | 
|---|
| 1018 | result.append((_key, _value)) | 
|---|
| 1019 | if httpformat: | 
|---|
| 1020 | return '\n'.join(['%s: %s' % item for item in result]) | 
|---|
| 1021 | return result | 
|---|
| 1022 | def load_post_data(self,environ,POST,FILES): | 
|---|
| 1023 | length = int(environ['CONTENT_LENGTH']) | 
|---|
| 1024 | DATA = environ['wsgi.input'].read(length) | 
|---|
| 1025 | if environ.get('CONTENT_TYPE', '').startswith('multipart'): | 
|---|
| 1026 | lines = ['Content-Type: %s' % environ.get('CONTENT_TYPE', '')] | 
|---|
| 1027 | for key, value in environ.items(): | 
|---|
| 1028 | if key.startswith('HTTP_'): | 
|---|
| 1029 | lines.append('%s: %s' % (key, value)) | 
|---|
| 1030 | raw = '\r\n'.join(lines) + '\r\n\r\n' + DATA | 
|---|
| 1031 | msg = email.message_from_string(raw) | 
|---|
| 1032 | for sub in msg.get_payload(): | 
|---|
| 1033 | if not isinstance(sub, email.Message.Message): | 
|---|
| 1034 | continue | 
|---|
| 1035 | name_dict = cgi.parse_header(sub['Content-Disposition'])[1] | 
|---|
| 1036 | if 'filename' in name_dict: | 
|---|
| 1037 | # Nested MIME Messages are not supported' | 
|---|
| 1038 | if type([]) == type(sub.get_payload()): | 
|---|
| 1039 | continue | 
|---|
| 1040 | if not name_dict['filename'].strip(): | 
|---|
| 1041 | continue | 
|---|
| 1042 | filename = name_dict['filename'] | 
|---|
| 1043 | # why not keep all the filename? because IE always send 'C:\documents and settings\blub\blub.png' | 
|---|
| 1044 | filename = filename[filename.rfind('\\') + 1:] | 
|---|
| 1045 | if 'Content-Type' in sub: | 
|---|
| 1046 | content_type = sub['Content-Type'] | 
|---|
| 1047 | else: | 
|---|
| 1048 | content_type = None | 
|---|
| 1049 | s = { "name":filename, "type":content_type, "data":sub.get_payload() } | 
|---|
| 1050 | FILES.appendlist(name_dict['name'], s) | 
|---|
| 1051 | else: | 
|---|
| 1052 | POST.appendlist(name_dict['name'], sub.get_payload()) | 
|---|
| 1053 | else: | 
|---|
| 1054 | POST.update(cgi.parse_qs(DATA,keep_blank_values=1)) | 
|---|
| 1055 | return DATA | 
|---|
| 1056 |  | 
|---|
| 1057 | def __init__(self,environ,start_response,session=QWebSession): | 
|---|
| 1058 | self.environ=environ | 
|---|
| 1059 | self.start_response=start_response | 
|---|
| 1060 | self.buffer=[] | 
|---|
| 1061 |  | 
|---|
| 1062 | self.SCRIPT_NAME = environ.get('SCRIPT_NAME', '') | 
|---|
| 1063 | self.PATH_INFO = environ.get('PATH_INFO', '') | 
|---|
| 1064 | # extensions: | 
|---|
| 1065 | self.FULL_URL = environ['FULL_URL'] = self.get_full_url(environ) | 
|---|
| 1066 | # REQUEST_URI is optional, fake it if absent | 
|---|
| 1067 | if not environ.has_key("REQUEST_URI"): | 
|---|
| 1068 | environ["REQUEST_URI"]=urllib.quote(self.SCRIPT_NAME+self.PATH_INFO) | 
|---|
| 1069 | if environ.get('QUERY_STRING'): | 
|---|
| 1070 | environ["REQUEST_URI"]+='?'+environ['QUERY_STRING'] | 
|---|
| 1071 | self.REQUEST_URI = environ["REQUEST_URI"] | 
|---|
| 1072 | # full quote url path before the ? | 
|---|
| 1073 | self.FULL_PATH = environ['FULL_PATH'] = self.REQUEST_URI.split('?')[0] | 
|---|
| 1074 |  | 
|---|
| 1075 | self.request_cookies=Cookie.SimpleCookie() | 
|---|
| 1076 | self.request_cookies.load(environ.get('HTTP_COOKIE', '')) | 
|---|
| 1077 |  | 
|---|
| 1078 | self.response_started=False | 
|---|
| 1079 | self.response_gzencode=False | 
|---|
| 1080 | self.response_cookies=Cookie.SimpleCookie() | 
|---|
| 1081 | # to delete a cookie use: c[key]['expires'] = datetime.datetime(1970, 1, 1) | 
|---|
| 1082 | self.response_headers=self.HttpHeaders() | 
|---|
| 1083 | self.response_status="200 OK" | 
|---|
| 1084 |  | 
|---|
| 1085 | self.php=None | 
|---|
| 1086 | if self.environ.has_key("php"): | 
|---|
| 1087 | self.php=environ["php"] | 
|---|
| 1088 | self.SESSION=self.php._SESSION | 
|---|
| 1089 | self.GET=self.php._GET | 
|---|
| 1090 | self.POST=self.php._POST | 
|---|
| 1091 | self.REQUEST=self.php._ARG | 
|---|
| 1092 | self.FILES=self.php._FILES | 
|---|
| 1093 | else: | 
|---|
| 1094 | if isinstance(session,QWebSession): | 
|---|
| 1095 | self.SESSION=session | 
|---|
| 1096 | elif session: | 
|---|
| 1097 | self.SESSION=session(environ) | 
|---|
| 1098 | else: | 
|---|
| 1099 | self.SESSION=None | 
|---|
| 1100 | self.GET_LIST=QWebListDict(cgi.parse_qs(environ.get('QUERY_STRING', ''),keep_blank_values=1)) | 
|---|
| 1101 | self.POST_LIST=QWebListDict() | 
|---|
| 1102 | self.FILES_LIST=QWebListDict() | 
|---|
| 1103 | self.REQUEST_LIST=QWebListDict(self.GET_LIST) | 
|---|
| 1104 | if environ['REQUEST_METHOD'] == 'POST': | 
|---|
| 1105 | self.DATA=self.load_post_data(environ,self.POST_LIST,self.FILES_LIST) | 
|---|
| 1106 | self.REQUEST_LIST.update(self.POST_LIST) | 
|---|
| 1107 | self.GET=self.GET_LIST.get_qwebdict() | 
|---|
| 1108 | self.POST=self.POST_LIST.get_qwebdict() | 
|---|
| 1109 | self.FILES=self.FILES_LIST.get_qwebdict() | 
|---|
| 1110 | self.REQUEST=self.REQUEST_LIST.get_qwebdict() | 
|---|
| 1111 | def get_full_url(environ): | 
|---|
| 1112 | # taken from PEP 333 | 
|---|
| 1113 | if 'FULL_URL' in environ: | 
|---|
| 1114 | return environ['FULL_URL'] | 
|---|
| 1115 | url = environ['wsgi.url_scheme']+'://' | 
|---|
| 1116 | if environ.get('HTTP_HOST'): | 
|---|
| 1117 | url += environ['HTTP_HOST'] | 
|---|
| 1118 | else: | 
|---|
| 1119 | url += environ['SERVER_NAME'] | 
|---|
| 1120 | if environ['wsgi.url_scheme'] == 'https': | 
|---|
| 1121 | if environ['SERVER_PORT'] != '443': | 
|---|
| 1122 | url += ':' + environ['SERVER_PORT'] | 
|---|
| 1123 | else: | 
|---|
| 1124 | if environ['SERVER_PORT'] != '80': | 
|---|
| 1125 | url += ':' + environ['SERVER_PORT'] | 
|---|
| 1126 | if environ.has_key('REQUEST_URI'): | 
|---|
| 1127 | url += environ['REQUEST_URI'] | 
|---|
| 1128 | else: | 
|---|
| 1129 | url += urllib.quote(environ.get('SCRIPT_NAME', '')) | 
|---|
| 1130 | url += urllib.quote(environ.get('PATH_INFO', '')) | 
|---|
| 1131 | if environ.get('QUERY_STRING'): | 
|---|
| 1132 | url += '?' + environ['QUERY_STRING'] | 
|---|
| 1133 | return url | 
|---|
| 1134 | get_full_url=staticmethod(get_full_url) | 
|---|
| 1135 | def save_files(self): | 
|---|
| 1136 | for k,v in self.FILES.items(): | 
|---|
| 1137 | if not v.has_key("tmp_file"): | 
|---|
| 1138 | f=tempfile.NamedTemporaryFile() | 
|---|
| 1139 | f.write(v["data"]) | 
|---|
| 1140 | f.flush() | 
|---|
| 1141 | v["tmp_file"]=f | 
|---|
| 1142 | v["tmp_name"]=f.name | 
|---|
| 1143 | def debug(self): | 
|---|
| 1144 | body='' | 
|---|
| 1145 | for name,d in [ | 
|---|
| 1146 | ("GET",self.GET), ("POST",self.POST), ("REQUEST",self.REQUEST), ("FILES",self.FILES), | 
|---|
| 1147 | ("GET_LIST",self.GET_LIST), ("POST_LIST",self.POST_LIST), ("REQUEST_LIST",self.REQUEST_LIST), ("FILES_LIST",self.FILES_LIST), | 
|---|
| 1148 | ("SESSION",self.SESSION), ("environ",self.environ), | 
|---|
| 1149 | ]: | 
|---|
| 1150 | body+='<table border="1" width="100%" align="center">\n' | 
|---|
| 1151 | body+='<tr><th colspan="2" align="center">%s</th></tr>\n'%name | 
|---|
| 1152 | keys=d.keys() | 
|---|
| 1153 | keys.sort() | 
|---|
| 1154 | body+=''.join(['<tr><td>%s</td><td>%s</td></tr>\n'%(k,cgi.escape(repr(d[k]))) for k in keys]) | 
|---|
| 1155 | body+='</table><br><br>\n\n' | 
|---|
| 1156 | return body | 
|---|
| 1157 | def write(self,s): | 
|---|
| 1158 | self.buffer.append(s) | 
|---|
| 1159 | def echo(self,*s): | 
|---|
| 1160 | self.buffer.extend([str(i) for i in s]) | 
|---|
| 1161 | def response(self): | 
|---|
| 1162 | if not self.response_started: | 
|---|
| 1163 | if not self.php: | 
|---|
| 1164 | for k,v in self.FILES.items(): | 
|---|
| 1165 | if v.has_key("tmp_file"): | 
|---|
| 1166 | try: | 
|---|
| 1167 | v["tmp_file"].close() | 
|---|
| 1168 | except OSError: | 
|---|
| 1169 | pass | 
|---|
| 1170 | if self.response_gzencode and self.environ.get('HTTP_ACCEPT_ENCODING','').find('gzip')!=-1: | 
|---|
| 1171 | zbuf=StringIO.StringIO() | 
|---|
| 1172 | zfile=gzip.GzipFile(mode='wb', fileobj=zbuf) | 
|---|
| 1173 | zfile.write(''.join(self.buffer)) | 
|---|
| 1174 | zfile.close() | 
|---|
| 1175 | zbuf=zbuf.getvalue() | 
|---|
| 1176 | self.buffer=[zbuf] | 
|---|
| 1177 | self.response_headers['Content-Encoding']="gzip" | 
|---|
| 1178 | self.response_headers['Content-Length']=str(len(zbuf)) | 
|---|
| 1179 | headers = self.response_headers.get() | 
|---|
| 1180 | if isinstance(self.SESSION, QWebSession): | 
|---|
| 1181 | headers.extend(self.SESSION.session_get_headers()) | 
|---|
| 1182 | headers.extend([('Set-Cookie', self.response_cookies[i].OutputString()) for i in self.response_cookies]) | 
|---|
| 1183 | self.start_response(self.response_status, headers) | 
|---|
| 1184 | self.response_started=True | 
|---|
| 1185 | return self.buffer | 
|---|
| 1186 | def __iter__(self): | 
|---|
| 1187 | return self.response().__iter__() | 
|---|
| 1188 | def http_redirect(self,url,permanent=1): | 
|---|
| 1189 | if permanent: | 
|---|
| 1190 | self.response_status="301 Moved Permanently" | 
|---|
| 1191 | else: | 
|---|
| 1192 | self.response_status="302 Found" | 
|---|
| 1193 | self.response_headers["Location"]=url | 
|---|
| 1194 | def http_404(self,msg="<h1>404 Not Found</h1>"): | 
|---|
| 1195 | self.response_status="404 Not Found" | 
|---|
| 1196 | if msg: | 
|---|
| 1197 | self.write(msg) | 
|---|
| 1198 | def http_download(self,fname,fstr,partial=0): | 
|---|
| 1199 | #       allow fstr to be a file-like object | 
|---|
| 1200 | #       if parital: | 
|---|
| 1201 | #           say accept ranages | 
|---|
| 1202 | #           parse range headers... | 
|---|
| 1203 | #           if range: | 
|---|
| 1204 | #               header("HTTP/1.1 206 Partial Content"); | 
|---|
| 1205 | #               header("Content-Range: bytes $offset-".($fsize-1)."/".$fsize); | 
|---|
| 1206 | #               header("Content-Length: ".($fsize-$offset)); | 
|---|
| 1207 | #               fseek($fd,$offset); | 
|---|
| 1208 | #           else: | 
|---|
| 1209 | self.response_headers["Content-Type"]="application/octet-stream" | 
|---|
| 1210 | self.response_headers["Content-Disposition"]="attachment; filename=\"%s\""%fname | 
|---|
| 1211 | self.response_headers["Content-Transfer-Encoding"]="binary" | 
|---|
| 1212 | self.response_headers["Content-Length"]="%d"%len(fstr) | 
|---|
| 1213 | self.write(fstr) | 
|---|
| 1214 |  | 
|---|
| 1215 | #---------------------------------------------------------- | 
|---|
| 1216 | # QWeb WSGI HTTP Server to run any WSGI app | 
|---|
| 1217 | # autorun, run an app as FCGI or CGI otherwise launch the server | 
|---|
| 1218 | #---------------------------------------------------------- | 
|---|
| 1219 | class QWebWSGIHandler(BaseHTTPServer.BaseHTTPRequestHandler): | 
|---|
| 1220 | def log_message(self,*p): | 
|---|
| 1221 | if self.server.log: | 
|---|
| 1222 | return BaseHTTPServer.BaseHTTPRequestHandler.log_message(self,*p) | 
|---|
| 1223 | def address_string(self): | 
|---|
| 1224 | return self.client_address[0] | 
|---|
| 1225 | def start_response(self,status,headers): | 
|---|
| 1226 | l=status.split(' ',1) | 
|---|
| 1227 | self.send_response(int(l[0]),l[1]) | 
|---|
| 1228 | ctype_sent=0 | 
|---|
| 1229 | for i in headers: | 
|---|
| 1230 | if i[0].lower()=="content-type": | 
|---|
| 1231 | ctype_sent=1 | 
|---|
| 1232 | self.send_header(*i) | 
|---|
| 1233 | if not ctype_sent: | 
|---|
| 1234 | self.send_header("Content-type", "text/html") | 
|---|
| 1235 | self.end_headers() | 
|---|
| 1236 | return self.write | 
|---|
| 1237 | def write(self,data): | 
|---|
| 1238 | try: | 
|---|
| 1239 | self.wfile.write(data) | 
|---|
| 1240 | except (socket.error, socket.timeout),e: | 
|---|
| 1241 | print e | 
|---|
| 1242 | def bufferon(self): | 
|---|
| 1243 | if not getattr(self,'wfile_buf',0): | 
|---|
| 1244 | self.wfile_buf=1 | 
|---|
| 1245 | self.wfile_bak=self.wfile | 
|---|
| 1246 | self.wfile=StringIO.StringIO() | 
|---|
| 1247 | def bufferoff(self): | 
|---|
| 1248 | if self.wfile_buf: | 
|---|
| 1249 | buf=self.wfile | 
|---|
| 1250 | self.wfile=self.wfile_bak | 
|---|
| 1251 | self.write(buf.getvalue()) | 
|---|
| 1252 | self.wfile_buf=0 | 
|---|
| 1253 | def serve(self,type): | 
|---|
| 1254 | path_info, parameters, query = urlparse.urlparse(self.path)[2:5] | 
|---|
| 1255 | environ = { | 
|---|
| 1256 | 'wsgi.version':         (1,0), | 
|---|
| 1257 | 'wsgi.url_scheme':      'http', | 
|---|
| 1258 | 'wsgi.input':           self.rfile, | 
|---|
| 1259 | 'wsgi.errors':          sys.stderr, | 
|---|
| 1260 | 'wsgi.multithread':     0, | 
|---|
| 1261 | 'wsgi.multiprocess':    0, | 
|---|
| 1262 | 'wsgi.run_once':        0, | 
|---|
| 1263 | 'REQUEST_METHOD':       self.command, | 
|---|
| 1264 | 'SCRIPT_NAME':          '', | 
|---|
| 1265 | 'QUERY_STRING':         query, | 
|---|
| 1266 | 'CONTENT_TYPE':         self.headers.get('Content-Type', ''), | 
|---|
| 1267 | 'CONTENT_LENGTH':       self.headers.get('Content-Length', ''), | 
|---|
| 1268 | 'REMOTE_ADDR':          self.client_address[0], | 
|---|
| 1269 | 'REMOTE_PORT':          str(self.client_address[1]), | 
|---|
| 1270 | 'SERVER_NAME':          self.server.server_address[0], | 
|---|
| 1271 | 'SERVER_PORT':          str(self.server.server_address[1]), | 
|---|
| 1272 | 'SERVER_PROTOCOL':      self.request_version, | 
|---|
| 1273 | # extention | 
|---|
| 1274 | 'FULL_PATH':            self.path, | 
|---|
| 1275 | 'qweb.mode':            'standalone', | 
|---|
| 1276 | } | 
|---|
| 1277 | if path_info: | 
|---|
| 1278 | environ['PATH_INFO'] = urllib.unquote(path_info) | 
|---|
| 1279 | for key, value in self.headers.items(): | 
|---|
| 1280 | environ['HTTP_' + key.upper().replace('-', '_')] = value | 
|---|
| 1281 | # Hack to avoid may TCP packets | 
|---|
| 1282 | self.bufferon() | 
|---|
| 1283 | appiter=self.server.wsgiapp(environ, self.start_response) | 
|---|
| 1284 | for data in appiter: | 
|---|
| 1285 | self.write(data) | 
|---|
| 1286 | self.bufferoff() | 
|---|
| 1287 | self.bufferoff() | 
|---|
| 1288 | def do_GET(self): | 
|---|
| 1289 | self.serve('GET') | 
|---|
| 1290 | def do_POST(self): | 
|---|
| 1291 | self.serve('GET') | 
|---|
| 1292 | class QWebWSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): | 
|---|
| 1293 | """ QWebWSGIServer | 
|---|
| 1294 | qweb_wsgi_autorun(wsgiapp,ip='127.0.0.1',port=8080,threaded=1) | 
|---|
| 1295 | A WSGI HTTP server threaded or not and a function to automatically run your | 
|---|
| 1296 | app according to the environement (either standalone, CGI or FastCGI). | 
|---|
| 1297 |  | 
|---|
| 1298 | This feature is called QWeb autorun. If you want to  To use it on your | 
|---|
| 1299 | application use the following lines at the end of the main application | 
|---|
| 1300 | python file: | 
|---|
| 1301 |  | 
|---|
| 1302 | if __name__ == '__main__': | 
|---|
| 1303 | qweb.qweb_wsgi_autorun(your_wsgi_app) | 
|---|
| 1304 |  | 
|---|
| 1305 | this function will select the approriate running mode according to the | 
|---|
| 1306 | calling environement (http-server, FastCGI or CGI). | 
|---|
| 1307 | """ | 
|---|
| 1308 | def __init__(self, wsgiapp, ip, port, threaded=1, log=1): | 
|---|
| 1309 | BaseHTTPServer.HTTPServer.__init__(self, (ip, port), QWebWSGIHandler) | 
|---|
| 1310 | self.wsgiapp = wsgiapp | 
|---|
| 1311 | self.threaded = threaded | 
|---|
| 1312 | self.log = log | 
|---|
| 1313 | def process_request(self,*p): | 
|---|
| 1314 | if self.threaded: | 
|---|
| 1315 | return SocketServer.ThreadingMixIn.process_request(self,*p) | 
|---|
| 1316 | else: | 
|---|
| 1317 | return BaseHTTPServer.HTTPServer.process_request(self,*p) | 
|---|
| 1318 | def qweb_wsgi_autorun(wsgiapp,ip='127.0.0.1',port=8080,threaded=1,log=1,callback_ready=None): | 
|---|
| 1319 | if sys.platform=='win32': | 
|---|
| 1320 | fcgi=0 | 
|---|
| 1321 | else: | 
|---|
| 1322 | fcgi=1 | 
|---|
| 1323 | sock = socket.fromfd(0, socket.AF_INET, socket.SOCK_STREAM) | 
|---|
| 1324 | try: | 
|---|
| 1325 | sock.getpeername() | 
|---|
| 1326 | except socket.error, e: | 
|---|
| 1327 | if e[0] == errno.ENOTSOCK: | 
|---|
| 1328 | fcgi=0 | 
|---|
| 1329 | if fcgi or os.environ.has_key('REQUEST_METHOD'): | 
|---|
| 1330 | import fcgi | 
|---|
| 1331 | fcgi.WSGIServer(wsgiapp,multithreaded=False).run() | 
|---|
| 1332 | else: | 
|---|
| 1333 | if log: | 
|---|
| 1334 | print 'Serving on %s:%d'%(ip,port) | 
|---|
| 1335 | s=QWebWSGIServer(wsgiapp,ip=ip,port=port,threaded=threaded,log=log) | 
|---|
| 1336 | if callback_ready: | 
|---|
| 1337 | callback_ready() | 
|---|
| 1338 | try: | 
|---|
| 1339 | s.serve_forever() | 
|---|
| 1340 | except KeyboardInterrupt,e: | 
|---|
| 1341 | sys.excepthook(*sys.exc_info()) | 
|---|
| 1342 |  | 
|---|
| 1343 | #---------------------------------------------------------- | 
|---|
| 1344 | # Qweb Documentation | 
|---|
| 1345 | #---------------------------------------------------------- | 
|---|
| 1346 | def qweb_doc(): | 
|---|
| 1347 | body=__doc__ | 
|---|
| 1348 | for i in [QWebXml ,QWebHtml ,QWebForm ,QWebURL ,qweb_control ,QWebRequest ,QWebSession ,QWebWSGIServer ,qweb_wsgi_autorun]: | 
|---|
| 1349 | n=i.__name__ | 
|---|
| 1350 | d=i.__doc__ | 
|---|
| 1351 | body+='\n\n%s\n%s\n\n%s'%(n,'-'*len(n),d) | 
|---|
| 1352 | return body | 
|---|
| 1353 |  | 
|---|
| 1354 | print qweb_doc() | 
|---|
| 1355 |  | 
|---|
| 1356 | # | 
|---|