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 | # |
---|