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 13QWeb is a python based [http://www.python.org/doc/peps/pep-0333/ WSGI] 14compatible web framework, it provides an infratructure to quickly build web 15applications 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 25QWeb applications are runnable in standalone mode (from commandline), via 26FastCGI, Regular CGI or by any python WSGI compliant server. 27 28QWeb doesn't provide any database access but it integrates nicely with ORMs 29such as SQLObject, SQLAlchemy or plain DB-API. 30 31Written by Antony Lesuisse (email al AT udev.org) 32 33Homepage: http://antony.lesuisse.org/qweb/trac/ 34 35Forum: [http://antony.lesuisse.org/qweb/forum/viewforum.php?id=1 Forum] 36 37== Quick Start (for Linux, MacOS X and cygwin) == 38 39Make 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 48And point your browser to http://localhost:8080/ 49 50You 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 71QWeb Components: 72---------------- 73 74QWeb also feature a simple components api, that enables developers to easily 75produces reusable components. 76 77Default 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 86License 87------- 88qweb/fcgi.py wich is BSD-like from saddi.com. 89Everything else is put in the public domain. 90 91 92TODO 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 108import BaseHTTPServer,SocketServer,Cookie 109import cgi,datetime,email,email.Message,errno,gzip,os,random,re,socket,sys,tempfile,time,types,urllib,urlparse,xml.dom 110try: 111 import cPickle as pickle 112except ImportError: 113 import pickle 114try: 115 import cStringIO as StringIO 116except ImportError: 117 import StringIO 118 119#---------------------------------------------------------- 120# Qweb Xml t-raw t-esc t-if t-foreach t-set t-call t-trim 121#---------------------------------------------------------- 122class 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 as e: 132 pass 133 except AttributeError as e: 134 pass 135 except Exception as 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 160class 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#---------------------------------------------------------- 357class 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) 418class 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) 451class 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 555class 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 564class 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#---------------------------------------------------------- 729def 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#---------------------------------------------------------- 771class 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 868class 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() 882class 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) 890class 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 900class 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 915class 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#---------------------------------------------------------- 1219class 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) as 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') 1292class 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) 1318def 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 as 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 as e: 1341 sys.excepthook(*sys.exc_info()) 1342 1343#---------------------------------------------------------- 1344# Qweb Documentation 1345#---------------------------------------------------------- 1346def 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# 1357