1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8 """
9
10 from contenttype import contenttype
11 from storage import Storage, StorageList, Settings, Messages
12 from validators import IS_NOT_IN_DB, IS_NOT_EMPTY, IS_IN_DB, IS_EMAIL, IS_EXPR, IS_IN_SET, IS_INT_IN_RANGE, CRYPT
13 from html import DIV, URL, A, BR, SPAN, XML, UL, LI, H1, H2, H3, P, SCRIPT, TAG, IFRAME, LABEL, CODE
14 from html import FORM, INPUT, OPTION, SELECT
15 from html import TABLE, TR, TD
16 from sqlhtml import SQLFORM, SQLTABLE
17 from http import HTTP, redirect
18 from utils import web2py_uuid
19
20 import base64
21 import cPickle
22 import datetime
23 import thread
24 import logging
25 import sys
26 import os
27 import re
28 import time
29 import smtplib
30 import urllib
31 import urllib2
32 import Cookie
33 import cStringIO
34
35 from email import MIMEBase, MIMEMultipart, MIMEText, Encoders, Header, message_from_string
36
37 import serializers
38 import contrib.simplejson as simplejson
39 from dal import Field
40
41 __all__ = ['Mail', 'Auth', 'Recaptcha', 'Crud', 'Service', 'PluginManager', 'fetch', 'geocode', 'prettydate']
42
43 logger = logging.getLogger("web2py")
44
45 DEFAULT = lambda: None
46
47 -def callback(actions,form,tablename=None):
48 if actions:
49 if tablename and isinstance(actions,dict):
50 actions = actions.get(tablename, [])
51 if not isinstance(actions,(list, tuple)):
52 actions = [actions]
53 [action(form) for action in actions]
54
56 b = []
57 for item in a:
58 if isinstance(item, (list, tuple)):
59 b = b + list(item)
60 else:
61 b.append(item)
62 return b
63
69
71 """
72 Class for configuring and sending emails with alternative text / html
73 body, multiple attachments and encryption support
74
75 Works with SMTP and Google App Engine.
76 """
77
79 """
80 Email attachment
81
82 Arguments::
83
84 payload: path to file or file-like object with read() method
85 filename: name of the attachment stored in message; if set to
86 None, it will be fetched from payload path; file-like
87 object payload must have explicit filename specified
88 content_id: id of the attachment; automatically contained within
89 < and >
90 content_type: content type of the attachment; if set to None,
91 it will be fetched from filename using gluon.contenttype
92 module
93 encoding: encoding of all strings passed to this function (except
94 attachment body)
95
96 Content ID is used to identify attachments within the html body;
97 in example, attached image with content ID 'photo' may be used in
98 html message as a source of img tag <img src="cid:photo" />.
99
100 Examples::
101
102 #Create attachment from text file:
103 attachment = Mail.Attachment('/path/to/file.txt')
104
105 Content-Type: text/plain
106 MIME-Version: 1.0
107 Content-Disposition: attachment; filename="file.txt"
108 Content-Transfer-Encoding: base64
109
110 SOMEBASE64CONTENT=
111
112 #Create attachment from image file with custom filename and cid:
113 attachment = Mail.Attachment('/path/to/file.png',
114 filename='photo.png',
115 content_id='photo')
116
117 Content-Type: image/png
118 MIME-Version: 1.0
119 Content-Disposition: attachment; filename="photo.png"
120 Content-Id: <photo>
121 Content-Transfer-Encoding: base64
122
123 SOMEOTHERBASE64CONTENT=
124 """
125
126 - def __init__(
127 self,
128 payload,
129 filename=None,
130 content_id=None,
131 content_type=None,
132 encoding='utf-8'):
133 if isinstance(payload, str):
134 if filename == None:
135 filename = os.path.basename(payload)
136 handle = open(payload, 'rb')
137 payload = handle.read()
138 handle.close()
139 else:
140 if filename == None:
141 raise Exception('Missing attachment name')
142 payload = payload.read()
143 filename = filename.encode(encoding)
144 if content_type == None:
145 content_type = contenttype(filename)
146 self.my_filename = filename
147 self.my_payload = payload
148 MIMEBase.MIMEBase.__init__(self, *content_type.split('/', 1))
149 self.set_payload(payload)
150 self['Content-Disposition'] = 'attachment; filename="%s"' % filename
151 if content_id != None:
152 self['Content-Id'] = '<%s>' % content_id.encode(encoding)
153 Encoders.encode_base64(self)
154
155 - def __init__(self, server=None, sender=None, login=None, tls=True):
156 """
157 Main Mail object
158
159 Arguments::
160
161 server: SMTP server address in address:port notation
162 sender: sender email address
163 login: sender login name and password in login:password notation
164 or None if no authentication is required
165 tls: enables/disables encryption (True by default)
166
167 In Google App Engine use::
168
169 server='gae'
170
171 For sake of backward compatibility all fields are optional and default
172 to None, however, to be able to send emails at least server and sender
173 must be specified. They are available under following fields:
174
175 mail.settings.server
176 mail.settings.sender
177 mail.settings.login
178
179 When server is 'logging', email is logged but not sent (debug mode)
180
181 Optionally you can use PGP encryption or X509:
182
183 mail.settings.cipher_type = None
184 mail.settings.sign = True
185 mail.settings.sign_passphrase = None
186 mail.settings.encrypt = True
187 mail.settings.x509_sign_keyfile = None
188 mail.settings.x509_sign_certfile = None
189 mail.settings.x509_crypt_certfiles = None
190
191 cipher_type : None
192 gpg - need a python-pyme package and gpgme lib
193 x509 - smime
194 sign : sign the message (True or False)
195 sign_passphrase : passphrase for key signing
196 encrypt : encrypt the message
197 ... x509 only ...
198 x509_sign_keyfile : the signers private key filename (PEM format)
199 x509_sign_certfile: the signers certificate filename (PEM format)
200 x509_crypt_certfiles: the certificates file to encrypt the messages
201 with can be a file name or a list of
202 file names (PEM format)
203
204 Examples::
205
206 #Create Mail object with authentication data for remote server:
207 mail = Mail('example.com:25', 'me@example.com', 'me:password')
208 """
209
210 self.settings = Settings()
211 self.settings.server = server
212 self.settings.sender = sender
213 self.settings.login = login
214 self.settings.tls = tls
215 self.settings.cipher_type = None
216 self.settings.sign = True
217 self.settings.sign_passphrase = None
218 self.settings.encrypt = True
219 self.settings.x509_sign_keyfile = None
220 self.settings.x509_sign_certfile = None
221 self.settings.x509_crypt_certfiles = None
222 self.settings.debug = False
223 self.settings.lock_keys = True
224 self.result = {}
225 self.error = None
226
227 - def send(
228 self,
229 to,
230 subject='None',
231 message='None',
232 attachments=None,
233 cc=None,
234 bcc=None,
235 reply_to=None,
236 encoding='utf-8',
237 ):
238 """
239 Sends an email using data specified in constructor
240
241 Arguments::
242
243 to: list or tuple of receiver addresses; will also accept single
244 object
245 subject: subject of the email
246 message: email body text; depends on type of passed object:
247 if 2-list or 2-tuple is passed: first element will be
248 source of plain text while second of html text;
249 otherwise: object will be the only source of plain text
250 and html source will be set to None;
251 If text or html source is:
252 None: content part will be ignored,
253 string: content part will be set to it,
254 file-like object: content part will be fetched from
255 it using it's read() method
256 attachments: list or tuple of Mail.Attachment objects; will also
257 accept single object
258 cc: list or tuple of carbon copy receiver addresses; will also
259 accept single object
260 bcc: list or tuple of blind carbon copy receiver addresses; will
261 also accept single object
262 reply_to: address to which reply should be composed
263 encoding: encoding of all strings passed to this method (including
264 message bodies)
265
266 Examples::
267
268 #Send plain text message to single address:
269 mail.send('you@example.com',
270 'Message subject',
271 'Plain text body of the message')
272
273 #Send html message to single address:
274 mail.send('you@example.com',
275 'Message subject',
276 '<html>Plain text body of the message</html>')
277
278 #Send text and html message to three addresses (two in cc):
279 mail.send('you@example.com',
280 'Message subject',
281 ('Plain text body', '<html>html body</html>'),
282 cc=['other1@example.com', 'other2@example.com'])
283
284 #Send html only message with image attachment available from
285 the message by 'photo' content id:
286 mail.send('you@example.com',
287 'Message subject',
288 (None, '<html><img src="cid:photo" /></html>'),
289 Mail.Attachment('/path/to/photo.jpg'
290 content_id='photo'))
291
292 #Send email with two attachments and no body text
293 mail.send('you@example.com,
294 'Message subject',
295 None,
296 [Mail.Attachment('/path/to/fist.file'),
297 Mail.Attachment('/path/to/second.file')])
298
299 Returns True on success, False on failure.
300
301 Before return, method updates two object's fields:
302 self.result: return value of smtplib.SMTP.sendmail() or GAE's
303 mail.send_mail() method
304 self.error: Exception message or None if above was successful
305 """
306
307 def encode_header(key):
308 if [c for c in key if 32>ord(c) or ord(c)>127]:
309 return Header.Header(key.encode('utf-8'),'utf-8')
310 else:
311 return key
312
313 if not isinstance(self.settings.server, str):
314 raise Exception('Server address not specified')
315 if not isinstance(self.settings.sender, str):
316 raise Exception('Sender address not specified')
317 payload_in = MIMEMultipart.MIMEMultipart('mixed')
318 if to:
319 if not isinstance(to, (list,tuple)):
320 to = [to]
321 else:
322 raise Exception('Target receiver address not specified')
323 if cc:
324 if not isinstance(cc, (list, tuple)):
325 cc = [cc]
326 if bcc:
327 if not isinstance(bcc, (list, tuple)):
328 bcc = [bcc]
329 if message == None:
330 text = html = None
331 elif isinstance(message, (list, tuple)):
332 text, html = message
333 elif message.strip().startswith('<html') and message.strip().endswith('</html>'):
334 text = self.settings.server=='gae' and message or None
335 html = message
336 else:
337 text = message
338 html = None
339 if text != None or html != None:
340 attachment = MIMEMultipart.MIMEMultipart('alternative')
341 if text != None:
342 if isinstance(text, basestring):
343 text = text.decode(encoding).encode('utf-8')
344 else:
345 text = text.read().decode(encoding).encode('utf-8')
346 attachment.attach(MIMEText.MIMEText(text,_charset='utf-8'))
347 if html != None:
348 if isinstance(html, basestring):
349 html = html.decode(encoding).encode('utf-8')
350 else:
351 html = html.read().decode(encoding).encode('utf-8')
352 attachment.attach(MIMEText.MIMEText(html, 'html',_charset='utf-8'))
353 payload_in.attach(attachment)
354 if attachments == None:
355 pass
356 elif isinstance(attachments, (list, tuple)):
357 for attachment in attachments:
358 payload_in.attach(attachment)
359 else:
360 payload_in.attach(attachments)
361
362
363
364
365
366 cipher_type = self.settings.cipher_type
367 sign = self.settings.sign
368 sign_passphrase = self.settings.sign_passphrase
369 encrypt = self.settings.encrypt
370
371
372
373 if cipher_type == 'gpg':
374 if not sign and not encrypt:
375 self.error="No sign and no encrypt is set but cipher type to gpg"
376 return False
377
378
379 from pyme import core, errors
380 from pyme.constants.sig import mode
381
382
383
384 if sign:
385 import string
386 core.check_version(None)
387 pin=string.replace(payload_in.as_string(),'\n','\r\n')
388 plain = core.Data(pin)
389 sig = core.Data()
390 c = core.Context()
391 c.set_armor(1)
392 c.signers_clear()
393
394 for sigkey in c.op_keylist_all(self.settings.sender, 1):
395 if sigkey.can_sign:
396 c.signers_add(sigkey)
397 if not c.signers_enum(0):
398 self.error='No key for signing [%s]' % self.settings.sender
399 return False
400 c.set_passphrase_cb(lambda x,y,z: sign_passphrase)
401 try:
402
403 c.op_sign(plain,sig,mode.DETACH)
404 sig.seek(0,0)
405
406 payload=MIMEMultipart.MIMEMultipart('signed',
407 boundary=None,
408 _subparts=None,
409 **dict(micalg="pgp-sha1",
410 protocol="application/pgp-signature"))
411
412 payload.attach(payload_in)
413
414 p=MIMEBase.MIMEBase("application",'pgp-signature')
415 p.set_payload(sig.read())
416 payload.attach(p)
417
418 payload_in=payload
419 except errors.GPGMEError, ex:
420 self.error="GPG error: %s" % ex.getstring()
421 return False
422
423
424
425 if encrypt:
426 core.check_version(None)
427 plain = core.Data(payload_in.as_string())
428 cipher = core.Data()
429 c = core.Context()
430 c.set_armor(1)
431
432 recipients=[]
433 rec=to
434 if cc:
435 rec.extend(cc)
436 if bcc:
437 rec.extend(bcc)
438 for addr in rec:
439 c.op_keylist_start(addr,0)
440 r = c.op_keylist_next()
441 if r == None:
442 self.error='No key for [%s]' % addr
443 return False
444 recipients.append(r)
445 try:
446
447 c.op_encrypt(recipients, 1, plain, cipher)
448 cipher.seek(0,0)
449
450 payload=MIMEMultipart.MIMEMultipart('encrypted',
451 boundary=None,
452 _subparts=None,
453 **dict(protocol="application/pgp-encrypted"))
454 p=MIMEBase.MIMEBase("application",'pgp-encrypted')
455 p.set_payload("Version: 1\r\n")
456 payload.attach(p)
457 p=MIMEBase.MIMEBase("application",'octet-stream')
458 p.set_payload(cipher.read())
459 payload.attach(p)
460 except errors.GPGMEError, ex:
461 self.error="GPG error: %s" % ex.getstring()
462 return False
463
464
465
466 elif cipher_type == 'x509':
467 if not sign and not encrypt:
468 self.error="No sign and no encrypt is set but cipher type to x509"
469 return False
470 x509_sign_keyfile=self.settings.x509_sign_keyfile
471 if self.settings.x509_sign_certfile:
472 x509_sign_certfile=self.settings.x509_sign_certfile
473 else:
474
475
476 x509_sign_certfile=self.settings.x509_sign_keyfile
477
478 x509_crypt_certfiles=self.settings.x509_crypt_certfiles
479
480
481
482 from M2Crypto import BIO, SMIME, X509
483 msg_bio = BIO.MemoryBuffer(payload_in.as_string())
484 s = SMIME.SMIME()
485
486
487 if sign:
488
489 try:
490 s.load_key(x509_sign_keyfile, x509_sign_certfile, callback=lambda x: sign_passphrase)
491 if encrypt:
492 p7 = s.sign(msg_bio)
493 else:
494 p7 = s.sign(msg_bio,flags=SMIME.PKCS7_DETACHED)
495 msg_bio = BIO.MemoryBuffer(payload_in.as_string())
496 except Exception,e:
497 self.error="Something went wrong on signing: <%s>" %str(e)
498 return False
499
500
501 if encrypt:
502 try:
503 sk = X509.X509_Stack()
504 if not isinstance(x509_crypt_certfiles, (list, tuple)):
505 x509_crypt_certfiles = [x509_crypt_certfiles]
506
507
508 for x in x509_crypt_certfiles:
509 sk.push(X509.load_cert(x))
510 s.set_x509_stack(sk)
511
512 s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
513 tmp_bio = BIO.MemoryBuffer()
514 if sign:
515 s.write(tmp_bio, p7)
516 else:
517 tmp_bio.write(payload_in.as_string())
518 p7 = s.encrypt(tmp_bio)
519 except Exception,e:
520 self.error="Something went wrong on encrypting: <%s>" %str(e)
521 return False
522
523
524 out = BIO.MemoryBuffer()
525 if encrypt:
526 s.write(out, p7)
527 else:
528 if sign:
529 s.write(out, p7, msg_bio, SMIME.PKCS7_DETACHED)
530 else:
531 out.write('\r\n')
532 out.write(payload_in.as_string())
533 out.close()
534 st=str(out.read())
535 payload=message_from_string(st)
536 else:
537
538 payload=payload_in
539 payload['From'] = encode_header(self.settings.sender.decode(encoding))
540 if to:
541 payload['To'] = encode_header(', '.join(to).decode(encoding))
542 if reply_to:
543 payload['Reply-To'] = encode_header(reply_to.decode(encoding))
544 if cc:
545 payload['Cc'] = encode_header(', '.join(cc).decode(encoding))
546 to.extend(cc)
547 if bcc:
548 to.extend(bcc)
549 payload['Subject'] = encode_header(subject.decode(encoding))
550 payload['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S +0000",
551 time.gmtime())
552 result = {}
553 try:
554 if self.settings.server == 'logging':
555 logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\n\n%s\n%s\n' % \
556 ('-'*40,self.settings.sender,
557 ', '.join(to),text or html,'-'*40))
558 elif self.settings.server == 'gae':
559 from google.appengine.api import mail
560 attachments = attachments and [(a.my_filename,a.my_payload) for a in attachments]
561 if attachments:
562 result = mail.send_mail(sender=self.settings.sender, to=to,
563 subject=subject, body=text, html=html,
564 attachments=attachments)
565 elif html:
566 result = mail.send_mail(sender=self.settings.sender, to=to,
567 subject=subject, body=text, html=html)
568 else:
569 result = mail.send_mail(sender=self.settings.sender, to=to,
570 subject=subject, body=text)
571 else:
572 server = smtplib.SMTP(*self.settings.server.split(':'))
573 if self.settings.login != None:
574 if self.settings.tls:
575 server.ehlo()
576 server.starttls()
577 server.ehlo()
578 server.login(*self.settings.login.split(':',1))
579 result = server.sendmail(self.settings.sender, to, payload.as_string())
580 server.quit()
581 except Exception, e:
582 logger.warn('Mail.send failure:%s' % e)
583 self.result = result
584 self.error = e
585 return False
586 self.result = result
587 self.error = None
588 return True
589
590
592
593 API_SSL_SERVER = 'https://api-secure.recaptcha.net'
594 API_SERVER = 'http://api.recaptcha.net'
595 VERIFY_SERVER = 'api-verify.recaptcha.net'
596
597 - def __init__(
598 self,
599 request,
600 public_key='',
601 private_key='',
602 use_ssl=False,
603 error=None,
604 error_message='invalid',
605 label = 'Verify:',
606 options = ''
607 ):
608 self.remote_addr = request.env.remote_addr
609 self.public_key = public_key
610 self.private_key = private_key
611 self.use_ssl = use_ssl
612 self.error = error
613 self.errors = Storage()
614 self.error_message = error_message
615 self.components = []
616 self.attributes = {}
617 self.label = label
618 self.options = options
619 self.comment = ''
620
622
623
624
625 recaptcha_challenge_field = \
626 self.request_vars.recaptcha_challenge_field
627 recaptcha_response_field = \
628 self.request_vars.recaptcha_response_field
629 private_key = self.private_key
630 remoteip = self.remote_addr
631 if not (recaptcha_response_field and recaptcha_challenge_field
632 and len(recaptcha_response_field)
633 and len(recaptcha_challenge_field)):
634 self.errors['captcha'] = self.error_message
635 return False
636 params = urllib.urlencode({
637 'privatekey': private_key,
638 'remoteip': remoteip,
639 'challenge': recaptcha_challenge_field,
640 'response': recaptcha_response_field,
641 })
642 request = urllib2.Request(
643 url='http://%s/verify' % self.VERIFY_SERVER,
644 data=params,
645 headers={'Content-type': 'application/x-www-form-urlencoded',
646 'User-agent': 'reCAPTCHA Python'})
647 httpresp = urllib2.urlopen(request)
648 return_values = httpresp.read().splitlines()
649 httpresp.close()
650 return_code = return_values[0]
651 if return_code == 'true':
652 del self.request_vars.recaptcha_challenge_field
653 del self.request_vars.recaptcha_response_field
654 self.request_vars.captcha = ''
655 return True
656 self.errors['captcha'] = self.error_message
657 return False
658
660 public_key = self.public_key
661 use_ssl = (self.use_ssl, )
662 error_param = ''
663 if self.error:
664 error_param = '&error=%s' % self.error
665 if use_ssl:
666 server = self.API_SSL_SERVER
667 else:
668 server = self.API_SERVER
669 captcha = DIV(
670 SCRIPT("var RecaptchaOptions = {%s};" % self.options),
671 SCRIPT(_type="text/javascript",
672 _src="%s/challenge?k=%s%s" % (server,public_key,error_param)),
673 TAG.noscript(IFRAME(_src="%s/noscript?k=%s%s" % (server,public_key,error_param),
674 _height="300",_width="500",_frameborder="0"), BR(),
675 INPUT(_type='hidden', _name='recaptcha_response_field',
676 _value='manual_challenge')), _id='recaptcha')
677 if not self.errors.captcha:
678 return XML(captcha).xml()
679 else:
680 captcha.append(DIV(self.errors['captcha'], _class='error'))
681 return XML(captcha).xml()
682
683
684 -def addrow(form,a,b,c,style,_id,position=-1):
685 if style == "divs":
686 form[0].insert(position, DIV(DIV(LABEL(a),_class='w2p_fl'),
687 DIV(b, _class='w2p_fw'),
688 DIV(c, _class='w2p_fc'),
689 _id = _id))
690 elif style == "table2cols":
691 form[0].insert(position, TR(LABEL(a),''))
692 form[0].insert(position+1, TR(b, _colspan=2, _id = _id))
693 elif style == "ul":
694 form[0].insert(position, LI(DIV(LABEL(a),_class='w2p_fl'),
695 DIV(b, _class='w2p_fw'),
696 DIV(c, _class='w2p_fc'),
697 _id = _id))
698 else:
699 form[0].insert(position, TR(LABEL(a),b,c,_id = _id))
700
701
703 """
704 Class for authentication, authorization, role based access control.
705
706 Includes:
707
708 - registration and profile
709 - login and logout
710 - username and password retrieval
711 - event logging
712 - role creation and assignment
713 - user defined group/role based permission
714
715 Authentication Example::
716
717 from contrib.utils import *
718 mail=Mail()
719 mail.settings.server='smtp.gmail.com:587'
720 mail.settings.sender='you@somewhere.com'
721 mail.settings.login='username:password'
722 auth=Auth(globals(), db)
723 auth.settings.mailer=mail
724 # auth.settings....=...
725 auth.define_tables()
726 def authentication():
727 return dict(form=auth())
728
729 exposes:
730
731 - http://.../{application}/{controller}/authentication/login
732 - http://.../{application}/{controller}/authentication/logout
733 - http://.../{application}/{controller}/authentication/register
734 - http://.../{application}/{controller}/authentication/verify_email
735 - http://.../{application}/{controller}/authentication/retrieve_username
736 - http://.../{application}/{controller}/authentication/retrieve_password
737 - http://.../{application}/{controller}/authentication/reset_password
738 - http://.../{application}/{controller}/authentication/profile
739 - http://.../{application}/{controller}/authentication/change_password
740
741 On registration a group with role=new_user.id is created
742 and user is given membership of this group.
743
744 You can create a group with::
745
746 group_id=auth.add_group('Manager', 'can access the manage action')
747 auth.add_permission(group_id, 'access to manage')
748
749 Here \"access to manage\" is just a user defined string.
750 You can give access to a user::
751
752 auth.add_membership(group_id, user_id)
753
754 If user id is omitted, the logged in user is assumed
755
756 Then you can decorate any action::
757
758 @auth.requires_permission('access to manage')
759 def manage():
760 return dict()
761
762 You can restrict a permission to a specific table::
763
764 auth.add_permission(group_id, 'edit', db.sometable)
765 @auth.requires_permission('edit', db.sometable)
766
767 Or to a specific record::
768
769 auth.add_permission(group_id, 'edit', db.sometable, 45)
770 @auth.requires_permission('edit', db.sometable, 45)
771
772 If authorization is not granted calls::
773
774 auth.settings.on_failed_authorization
775
776 Other options::
777
778 auth.settings.mailer=None
779 auth.settings.expiration=3600 # seconds
780
781 ...
782
783 ### these are messages that can be customized
784 ...
785 """
786
787
788 - def url(self, f=None, args=[], vars={}):
789 return self.environment.URL(r=self.environment.request,
790 c=self.settings.controller,
791 f=f, args=args, vars=vars)
792
793 - def __init__(self, environment, db=None, controller='default'):
794 """
795 auth=Auth(globals(), db)
796
797 - globals() has to be the web2py environment including
798 request, response, session
799 - db has to be the database where to create tables for authentication
800
801 """
802
803 self.environment = Storage(environment)
804 self.db = db
805 request = self.environment.request
806 session = self.environment.session
807 auth = session.auth
808 if auth and auth.last_visit and auth.last_visit + \
809 datetime.timedelta(days=0, seconds=auth.expiration) > request.now:
810 self.user = auth.user
811
812 if (request.now - auth.last_visit).seconds > (auth.expiration/10):
813 auth.last_visit = request.now
814 else:
815 self.user = None
816 session.auth = None
817 self.settings = Settings()
818
819
820
821
822
823 self.settings.hideerror = False
824 self.settings.actions_disabled = []
825 self.settings.reset_password_requires_verification = False
826 self.settings.registration_requires_verification = False
827 self.settings.registration_requires_approval = False
828 self.settings.alternate_requires_registration = False
829 self.settings.create_user_groups = True
830
831 self.settings.controller = controller
832 self.settings.login_url = self.url('user', args='login')
833 self.settings.logged_url = self.url('user', args='profile')
834 self.settings.download_url = self.url('download')
835 self.settings.mailer = None
836 self.settings.login_captcha = None
837 self.settings.register_captcha = None
838 self.settings.retrieve_username_captcha = None
839 self.settings.retrieve_password_captcha = None
840 self.settings.captcha = None
841 self.settings.expiration = 3600
842 self.settings.long_expiration = 3600*30*24
843 self.settings.remember_me_form = True
844 self.settings.allow_basic_login = False
845 self.settings.allow_basic_login_only = False
846 self.settings.on_failed_authorization = \
847 self.url('user',args='not_authorized')
848
849 self.settings.on_failed_authentication = lambda x: redirect(x)
850
851 self.settings.formstyle = 'table3cols'
852
853
854
855 self.settings.password_field = 'password'
856 self.settings.table_user_name = 'auth_user'
857 self.settings.table_group_name = 'auth_group'
858 self.settings.table_membership_name = 'auth_membership'
859 self.settings.table_permission_name = 'auth_permission'
860 self.settings.table_event_name = 'auth_event'
861
862
863
864 self.settings.table_user = None
865 self.settings.table_group = None
866 self.settings.table_membership = None
867 self.settings.table_permission = None
868 self.settings.table_event = None
869
870
871
872 self.settings.showid = False
873
874
875
876 self.settings.login_next = self.url('index')
877 self.settings.login_onvalidation = []
878 self.settings.login_onaccept = []
879 self.settings.login_methods = [self]
880 self.settings.login_form = self
881 self.settings.login_email_validate = True
882 self.settings.login_userfield = None
883
884 self.settings.logout_next = self.url('index')
885 self.settings.logout_onlogout = None
886
887 self.settings.register_next = self.url('index')
888 self.settings.register_onvalidation = []
889 self.settings.register_onaccept = []
890 self.settings.register_fields = None
891
892 self.settings.verify_email_next = self.url('user', args='login')
893 self.settings.verify_email_onaccept = []
894
895 self.settings.profile_next = self.url('index')
896 self.settings.profile_onvalidation = []
897 self.settings.profile_onaccept = []
898 self.settings.profile_fields = None
899 self.settings.retrieve_username_next = self.url('index')
900 self.settings.retrieve_password_next = self.url('index')
901 self.settings.request_reset_password_next = self.url('user', args='login')
902 self.settings.reset_password_next = self.url('user', args='login')
903
904 self.settings.change_password_next = self.url('index')
905 self.settings.change_password_onvalidation = []
906 self.settings.change_password_onaccept = []
907
908 self.settings.retrieve_password_onvalidation = []
909 self.settings.reset_password_onvalidation = []
910
911 self.settings.hmac_key = None
912 self.settings.lock_keys = True
913
914
915
916 self.messages = Messages(self.environment.T)
917 self.messages.submit_button = 'Submit'
918 self.messages.verify_password = 'Verify Password'
919 self.messages.delete_label = 'Check to delete:'
920 self.messages.function_disabled = 'Function disabled'
921 self.messages.access_denied = 'Insufficient privileges'
922 self.messages.registration_verifying = 'Registration needs verification'
923 self.messages.registration_pending = 'Registration is pending approval'
924 self.messages.login_disabled = 'Login disabled by administrator'
925 self.messages.logged_in = 'Logged in'
926 self.messages.email_sent = 'Email sent'
927 self.messages.unable_to_send_email = 'Unable to send email'
928 self.messages.email_verified = 'Email verified'
929 self.messages.logged_out = 'Logged out'
930 self.messages.registration_successful = 'Registration successful'
931 self.messages.invalid_email = 'Invalid email'
932 self.messages.unable_send_email = 'Unable to send email'
933 self.messages.invalid_login = 'Invalid login'
934 self.messages.invalid_user = 'Invalid user'
935 self.messages.invalid_password = 'Invalid password'
936 self.messages.is_empty = "Cannot be empty"
937 self.messages.mismatched_password = "Password fields don't match"
938 self.messages.verify_email = \
939 'Click on the link http://...verify_email/%(key)s to verify your email'
940 self.messages.verify_email_subject = 'Email verification'
941 self.messages.username_sent = 'Your username was emailed to you'
942 self.messages.new_password_sent = 'A new password was emailed to you'
943 self.messages.password_changed = 'Password changed'
944 self.messages.retrieve_username = 'Your username is: %(username)s'
945 self.messages.retrieve_username_subject = 'Username retrieve'
946 self.messages.retrieve_password = 'Your password is: %(password)s'
947 self.messages.retrieve_password_subject = 'Password retrieve'
948 self.messages.reset_password = \
949 'Click on the link http://...reset_password/%(key)s to reset your password'
950 self.messages.reset_password_subject = 'Password reset'
951 self.messages.invalid_reset_password = 'Invalid reset password'
952 self.messages.profile_updated = 'Profile updated'
953 self.messages.new_password = 'New password'
954 self.messages.old_password = 'Old password'
955 self.messages.group_description = \
956 'Group uniquely assigned to user %(id)s'
957
958 self.messages.register_log = 'User %(id)s Registered'
959 self.messages.login_log = 'User %(id)s Logged-in'
960 self.messages.login_failed_log = None
961 self.messages.logout_log = 'User %(id)s Logged-out'
962 self.messages.profile_log = 'User %(id)s Profile updated'
963 self.messages.verify_email_log = 'User %(id)s Verification email sent'
964 self.messages.retrieve_username_log = 'User %(id)s Username retrieved'
965 self.messages.retrieve_password_log = 'User %(id)s Password retrieved'
966 self.messages.reset_password_log = 'User %(id)s Password reset'
967 self.messages.change_password_log = 'User %(id)s Password changed'
968 self.messages.add_group_log = 'Group %(group_id)s created'
969 self.messages.del_group_log = 'Group %(group_id)s deleted'
970 self.messages.add_membership_log = None
971 self.messages.del_membership_log = None
972 self.messages.has_membership_log = None
973 self.messages.add_permission_log = None
974 self.messages.del_permission_log = None
975 self.messages.has_permission_log = None
976 self.messages.impersonate_log = 'User %(id)s is impersonating %(other_id)s'
977
978 self.messages.label_first_name = 'First name'
979 self.messages.label_last_name = 'Last name'
980 self.messages.label_username = 'Username'
981 self.messages.label_email = 'E-mail'
982 self.messages.label_password = 'Password'
983 self.messages.label_registration_key = 'Registration key'
984 self.messages.label_reset_password_key = 'Reset Password key'
985 self.messages.label_registration_id = 'Registration identifier'
986 self.messages.label_role = 'Role'
987 self.messages.label_description = 'Description'
988 self.messages.label_user_id = 'User ID'
989 self.messages.label_group_id = 'Group ID'
990 self.messages.label_name = 'Name'
991 self.messages.label_table_name = 'Table name'
992 self.messages.label_record_id = 'Record ID'
993 self.messages.label_time_stamp = 'Timestamp'
994 self.messages.label_client_ip = 'Client IP'
995 self.messages.label_origin = 'Origin'
996 self.messages.label_remember_me = "Remember me (for 30 days)"
997 self.messages['T'] = self.environment.T
998 self.messages.verify_password_comment = 'please input your password again'
999 self.messages.lock_keys = True
1000
1001
1002 response = self.environment.response
1003 if auth and auth.remember:
1004
1005
1006
1007
1008
1009
1010 response.cookies[response.session_id_name]["expires"] = auth.expiration
1011
1013 "accessor for auth.user_id"
1014 return self.user and self.user.id or None
1015 user_id = property(_get_user_id, doc="user.id or None")
1016
1017 - def _HTTP(self, *a, **b):
1018 """
1019 only used in lambda: self._HTTP(404)
1020 """
1021
1022 raise HTTP(*a, **b)
1023
1025 """
1026 usage:
1027
1028 def authentication(): return dict(form=auth())
1029 """
1030
1031 request = self.environment.request
1032 args = request.args
1033 if not args:
1034 redirect(self.url(args='login',vars=request.vars))
1035 elif args[0] in self.settings.actions_disabled:
1036 raise HTTP(404)
1037 if args[0] == 'login':
1038 return self.login()
1039 elif args[0] == 'logout':
1040 return self.logout()
1041 elif args[0] == 'register':
1042 return self.register()
1043 elif args[0] == 'verify_email':
1044 return self.verify_email()
1045 elif args[0] == 'retrieve_username':
1046 return self.retrieve_username()
1047 elif args[0] == 'retrieve_password':
1048 return self.retrieve_password()
1049 elif args[0] == 'reset_password':
1050 return self.reset_password()
1051 elif args[0] == 'request_reset_password':
1052 return self.request_reset_password()
1053 elif args[0] == 'change_password':
1054 return self.change_password()
1055 elif args[0] == 'profile':
1056 return self.profile()
1057 elif args[0] == 'groups':
1058 return self.groups()
1059 elif args[0] == 'impersonate':
1060 return self.impersonate()
1061 elif args[0] == 'not_authorized':
1062 return self.not_authorized()
1063 else:
1064 raise HTTP(404)
1065
1066 - def navbar(self,prefix='Welcome',action=None):
1067 request = self.environment.request
1068 T = self.environment.T
1069 if isinstance(prefix,str):
1070 prefix = T(prefix)
1071 if not action:
1072 action=URL(request.application,request.controller,'user')
1073 if prefix:
1074 prefix = prefix.strip()+' '
1075 if self.user_id:
1076 logout=A(T('logout'),_href=action+'/logout')
1077 profile=A(T('profile'),_href=action+'/profile')
1078 password=A(T('password'),_href=action+'/change_password')
1079 bar = SPAN(prefix,self.user.first_name,' [ ', logout, ']',_class='auth_navbar')
1080 if not 'profile' in self.settings.actions_disabled:
1081 bar.insert(4, ' | ')
1082 bar.insert(5, profile)
1083 if not 'change_password' in self.settings.actions_disabled:
1084 bar.insert(-1, ' | ')
1085 bar.insert(-1, password)
1086 else:
1087 login=A(T('login'),_href=action+'/login')
1088 register=A(T('register'),_href=action+'/register')
1089 retrieve_username=A(T('forgot username?'),
1090 _href=action+'/retrieve_username')
1091 lost_password=A(T('lost password?'),
1092 _href=action+'/request_reset_password')
1093 bar = SPAN('[ ',login,' ]',_class='auth_navbar')
1094
1095 if not 'register' in self.settings.actions_disabled:
1096 bar.insert(2, ' | ')
1097 bar.insert(3, register)
1098 if 'username' in self.settings.table_user.fields() and \
1099 not 'retrieve_username' in self.settings.actions_disabled:
1100 bar.insert(-1, ' | ')
1101 bar.insert(-1, retrieve_username)
1102 if not 'request_reset_password' in self.settings.actions_disabled:
1103 bar.insert(-1, ' | ')
1104 bar.insert(-1, lost_password)
1105 return bar
1106
1108
1109 if type(migrate).__name__ == 'str':
1110 return (migrate + tablename + '.table')
1111 elif migrate == False:
1112 return False
1113 else:
1114 return True
1115
1116 - def define_tables(self, username=False, migrate=True, fake_migrate=False):
1117 """
1118 to be called unless tables are defined manually
1119
1120 usages::
1121
1122 # defines all needed tables and table files
1123 # 'myprefix_auth_user.table', ...
1124 auth.define_tables(migrate='myprefix_')
1125
1126 # defines all needed tables without migration/table files
1127 auth.define_tables(migrate=False)
1128
1129 """
1130
1131 db = self.db
1132 if not self.settings.table_user_name in db.tables:
1133 passfield = self.settings.password_field
1134 if username:
1135 table = db.define_table(
1136 self.settings.table_user_name,
1137 Field('first_name', length=128, default='',
1138 label=self.messages.label_first_name),
1139 Field('last_name', length=128, default='',
1140 label=self.messages.label_last_name),
1141 Field('username', length=128, default='',
1142 label=self.messages.label_username),
1143 Field('email', length=512, default='',
1144 label=self.messages.label_email),
1145 Field(passfield, 'password', length=512,
1146 readable=False, label=self.messages.label_password),
1147 Field('registration_key', length=512,
1148 writable=False, readable=False, default='',
1149 label=self.messages.label_registration_key),
1150 Field('reset_password_key', length=512,
1151 writable=False, readable=False, default='',
1152 label=self.messages.label_reset_password_key),
1153 Field('registration_id', length=512,
1154 writable=False, readable=False, default='',
1155 label=self.messages.label_registration_id),
1156 migrate=\
1157 self.__get_migrate(self.settings.table_user_name, migrate),
1158 fake_migrate=fake_migrate,
1159 format='%(username)s')
1160 table.username.requires = IS_NOT_IN_DB(db, table.username)
1161 else:
1162 table = db.define_table(
1163 self.settings.table_user_name,
1164 Field('first_name', length=128, default='',
1165 label=self.messages.label_first_name),
1166 Field('last_name', length=128, default='',
1167 label=self.messages.label_last_name),
1168 Field('email', length=512, default='',
1169 label=self.messages.label_email),
1170 Field(passfield, 'password', length=512,
1171 readable=False, label=self.messages.label_password),
1172 Field('registration_key', length=512,
1173 writable=False, readable=False, default='',
1174 label=self.messages.label_registration_key),
1175 Field('reset_password_key', length=512,
1176 writable=False, readable=False, default='',
1177 label=self.messages.label_reset_password_key),
1178 migrate=\
1179 self.__get_migrate(self.settings.table_user_name, migrate),
1180 fake_migrate=fake_migrate,
1181 format='%(first_name)s %(last_name)s (%(id)s)')
1182 table.first_name.requires = \
1183 IS_NOT_EMPTY(error_message=self.messages.is_empty)
1184 table.last_name.requires = \
1185 IS_NOT_EMPTY(error_message=self.messages.is_empty)
1186 table[passfield].requires = [CRYPT(key=self.settings.hmac_key)]
1187 table.email.requires = \
1188 [IS_EMAIL(error_message=self.messages.invalid_email),
1189 IS_NOT_IN_DB(db, table.email)]
1190 table.registration_key.default = ''
1191 self.settings.table_user = db[self.settings.table_user_name]
1192 if not self.settings.table_group_name in db.tables:
1193 table = db.define_table(
1194 self.settings.table_group_name,
1195 Field('role', length=512, default='',
1196 label=self.messages.label_role),
1197 Field('description', 'text',
1198 label=self.messages.label_description),
1199 migrate=self.__get_migrate(
1200 self.settings.table_group_name, migrate),
1201 fake_migrate=fake_migrate,
1202 format = '%(role)s (%(id)s)')
1203 table.role.requires = IS_NOT_IN_DB(db, '%s.role'
1204 % self.settings.table_group_name)
1205 self.settings.table_group = db[self.settings.table_group_name]
1206 if not self.settings.table_membership_name in db.tables:
1207 table = db.define_table(
1208 self.settings.table_membership_name,
1209 Field('user_id', self.settings.table_user,
1210 label=self.messages.label_user_id),
1211 Field('group_id', self.settings.table_group,
1212 label=self.messages.label_group_id),
1213 migrate=self.__get_migrate(
1214 self.settings.table_membership_name, migrate),
1215 fake_migrate=fake_migrate)
1216 table.user_id.requires = IS_IN_DB(db, '%s.id' %
1217 self.settings.table_user_name,
1218 '%(first_name)s %(last_name)s (%(id)s)')
1219 table.group_id.requires = IS_IN_DB(db, '%s.id' %
1220 self.settings.table_group_name,
1221 '%(role)s (%(id)s)')
1222 self.settings.table_membership = db[self.settings.table_membership_name]
1223 if not self.settings.table_permission_name in db.tables:
1224 table = db.define_table(
1225 self.settings.table_permission_name,
1226 Field('group_id', self.settings.table_group,
1227 label=self.messages.label_group_id),
1228 Field('name', default='default', length=512,
1229 label=self.messages.label_name),
1230 Field('table_name', length=512,
1231 label=self.messages.label_table_name),
1232 Field('record_id', 'integer',
1233 label=self.messages.label_record_id),
1234 migrate=self.__get_migrate(
1235 self.settings.table_permission_name, migrate),
1236 fake_migrate=fake_migrate)
1237 table.group_id.requires = IS_IN_DB(db, '%s.id' %
1238 self.settings.table_group_name,
1239 '%(role)s (%(id)s)')
1240 table.name.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1241 table.table_name.requires = IS_IN_SET(self.db.tables)
1242 table.record_id.requires = IS_INT_IN_RANGE(0, 10 ** 9)
1243 self.settings.table_permission = db[self.settings.table_permission_name]
1244 if not self.settings.table_event_name in db.tables:
1245 table = db.define_table(
1246 self.settings.table_event_name,
1247 Field('time_stamp', 'datetime',
1248 default=self.environment.request.now,
1249 label=self.messages.label_time_stamp),
1250 Field('client_ip',
1251 default=self.environment.request.client,
1252 label=self.messages.label_client_ip),
1253 Field('user_id', self.settings.table_user, default=None,
1254 label=self.messages.label_user_id),
1255 Field('origin', default='auth', length=512,
1256 label=self.messages.label_origin),
1257 Field('description', 'text', default='',
1258 label=self.messages.label_description),
1259 migrate=self.__get_migrate(
1260 self.settings.table_event_name, migrate),
1261 fake_migrate=fake_migrate)
1262 table.user_id.requires = IS_IN_DB(db, '%s.id' %
1263 self.settings.table_user_name,
1264 '%(first_name)s %(last_name)s (%(id)s)')
1265 table.origin.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1266 table.description.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1267 self.settings.table_event = db[self.settings.table_event_name]
1268 def lazy_user (auth = self): return auth.user_id
1269 now = self.environment.request.now
1270 self.signature = db.Table(self.db,'auth_signature',
1271 Field('is_active','boolean',default=True),
1272 Field('created_on','datetime',default=now,
1273 writable=False,readable=False),
1274 Field('created_by',self.settings.table_user,default=lazy_user,
1275 writable=False,readable=False),
1276 Field('modified_on','datetime',update=now,default=now,
1277 writable=False,readable=False),
1278 Field('modified_by',self.settings.table_user,
1279 default=lazy_user,update=lazy_user,
1280 writable=False,readable=False))
1281
1282
1283 - def log_event(self, description, origin='auth'):
1284 """
1285 usage::
1286
1287 auth.log_event(description='this happened', origin='auth')
1288 """
1289
1290 if self.is_logged_in():
1291 user_id = self.user.id
1292 else:
1293 user_id = None
1294 self.settings.table_event.insert(description=description,
1295 origin=origin, user_id=user_id)
1296
1298 """
1299 Used for alternate login methods:
1300 If the user exists already then password is updated.
1301 If the user doesn't yet exist, then they are created.
1302 """
1303 table_user = self.settings.table_user
1304 if 'registration_id' in table_user.fields() and 'registration_id' in keys:
1305 username = 'registration_id'
1306 elif 'username' in table_user.fields():
1307 username = 'username'
1308 elif 'email' in table_user.fields():
1309 username = 'email'
1310 else:
1311 raise SyntaxError, "user must have username or email"
1312 passfield = self.settings.password_field
1313 user = self.db(table_user[username] == keys[username]).select().first()
1314 if user:
1315 if passfield in keys and keys[passfield]:
1316 user.update_record(**{passfield: keys[passfield],
1317 'registration_key': ''})
1318 else:
1319 d = {username: keys[username],
1320 'first_name': keys.get('first_name', keys[username]),
1321 'last_name': keys.get('last_name', ''),
1322 'registration_key': ''}
1323 keys = dict([(k, v) for (k, v) in keys.items() \
1324 if k in table_user.fields])
1325 d.update(keys)
1326 user_id = table_user.insert(**d)
1327 user = self.user = table_user[user_id]
1328 if self.settings.create_user_groups:
1329 group_id = self.add_group("user_%s" % user_id)
1330 self.add_membership(group_id, user_id)
1331 return user
1332
1334 if not self.settings.allow_basic_login:
1335 return False
1336 basic = self.environment.request.env.http_authorization
1337 if not basic or not basic[:6].lower() == 'basic ':
1338 return False
1339 (username, password) = base64.b64decode(basic[6:]).split(':')
1340 return self.login_bare(username, password)
1341
1343 """
1344 logins user
1345 """
1346
1347 request = self.environment.request
1348 session = self.environment.session
1349 table_user = self.settings.table_user
1350 if self.settings.login_userfield:
1351 userfield = self.settings.login_userfield
1352 elif 'username' in table_user.fields:
1353 userfield = 'username'
1354 else:
1355 userfield = 'email'
1356 passfield = self.settings.password_field
1357 user = self.db(table_user[userfield] == username).select().first()
1358 password = table_user[passfield].validate(password)[0]
1359 if user:
1360 if not user.registration_key and user[passfield] == password:
1361 user = Storage(table_user._filter_fields(user, id=True))
1362 session.auth = Storage(user=user, last_visit=request.now,
1363 expiration=self.settings.expiration)
1364 self.user = user
1365 return user
1366 return False
1367
1375 """
1376 returns a login form
1377
1378 .. method:: Auth.login([next=DEFAULT [, onvalidation=DEFAULT
1379 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1380
1381 """
1382
1383 table_user = self.settings.table_user
1384 if self.settings.login_userfield:
1385 username = self.settings.login_userfield
1386 elif 'username' in table_user.fields:
1387 username = 'username'
1388 else:
1389 username = 'email'
1390 if 'username' in table_user.fields or not self.settings.login_email_validate:
1391 tmpvalidator = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1392 else:
1393 tmpvalidator = IS_EMAIL(error_message=self.messages.invalid_email)
1394 old_requires = table_user[username].requires
1395 table_user[username].requires = tmpvalidator
1396 request = self.environment.request
1397 response = self.environment.response
1398 session = self.environment.session
1399 passfield = self.settings.password_field
1400 if next == DEFAULT:
1401 next = request.get_vars._next \
1402 or request.post_vars._next \
1403 or self.settings.login_next
1404 if onvalidation == DEFAULT:
1405 onvalidation = self.settings.login_onvalidation
1406 if onaccept == DEFAULT:
1407 onaccept = self.settings.login_onaccept
1408 if log == DEFAULT:
1409 log = self.messages.login_log
1410
1411 user = None
1412
1413
1414 if self.settings.login_form == self:
1415 form = SQLFORM(
1416 table_user,
1417 fields=[username, passfield],
1418 hidden=dict(_next=next),
1419 showid=self.settings.showid,
1420 submit_button=self.messages.submit_button,
1421 delete_label=self.messages.delete_label,
1422 formstyle=self.settings.formstyle
1423 )
1424
1425 if self.settings.remember_me_form:
1426
1427 addrow(form,XML(" "),
1428 DIV(XML(" "),
1429 INPUT(_type='checkbox',
1430 _class='checkbox',
1431 _id="auth_user_remember",
1432 _name="remember",
1433 ),
1434 XML(" "),
1435 LABEL(
1436 self.messages.label_remember_me,
1437 _for="auth_user_remember",
1438 )),"",
1439 self.settings.formstyle,
1440 'auth_user_remember__row')
1441
1442 captcha = self.settings.login_captcha or \
1443 (self.settings.login_captcha!=False and self.settings.captcha)
1444 if captcha:
1445 addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle,'captcha__row')
1446 accepted_form = False
1447 if form.accepts(request, session,
1448 formname='login', dbio=False,
1449 onvalidation=onvalidation,
1450 hideerror=self.settings.hideerror):
1451 accepted_form = True
1452
1453 user = self.db(table_user[username] == form.vars[username]).select().first()
1454 if user:
1455
1456 temp_user = user
1457 if temp_user.registration_key == 'pending':
1458 response.flash = self.messages.registration_pending
1459 return form
1460 elif temp_user.registration_key in ('disabled','blocked'):
1461 response.flash = self.messages.login_disabled
1462 return form
1463 elif temp_user.registration_key!=None and temp_user.registration_key.strip():
1464 response.flash = \
1465 self.messages.registration_verifying
1466 return form
1467
1468 user = None
1469 for login_method in self.settings.login_methods:
1470 if login_method != self and \
1471 login_method(request.vars[username],
1472 request.vars[passfield]):
1473 if not self in self.settings.login_methods:
1474
1475 form.vars[passfield] = None
1476 user = self.get_or_create_user(form.vars)
1477 break
1478 if not user:
1479
1480 if self.settings.login_methods[0] == self:
1481
1482 if temp_user[passfield] == form.vars.get(passfield, ''):
1483
1484 user = temp_user
1485 else:
1486
1487 if not self.settings.alternate_requires_registration:
1488
1489 for login_method in self.settings.login_methods:
1490 if login_method != self and \
1491 login_method(request.vars[username],
1492 request.vars[passfield]):
1493 if not self in self.settings.login_methods:
1494
1495 form.vars[passfield] = None
1496 user = self.get_or_create_user(form.vars)
1497 break
1498 if not user:
1499 if self.settings.login_failed_log:
1500 self.log_event(self.settings.login_failed_log % request.post_vars)
1501
1502 session.flash = self.messages.invalid_login
1503 redirect(self.url(args=request.args,vars=request.get_vars))
1504
1505 else:
1506
1507 cas = self.settings.login_form
1508 cas_user = cas.get_user()
1509
1510 if cas_user:
1511 cas_user[passfield] = None
1512 user = self.get_or_create_user(cas_user)
1513 elif hasattr(cas,'login_form'):
1514 return cas.login_form()
1515 else:
1516
1517 next = self.url('user',args='login',vars=dict(_next=next))
1518 redirect(cas.login_url(next))
1519
1520
1521
1522 if user:
1523 user = Storage(table_user._filter_fields(user, id=True))
1524
1525 if request.vars.has_key("remember"):
1526
1527 session.auth = Storage(
1528 user = user,
1529 last_visit = request.now,
1530 expiration = self.settings.long_expiration,
1531 remember = True,
1532 )
1533 else:
1534
1535 session.auth = Storage(
1536 user = user,
1537 last_visit = request.now,
1538 expiration = self.settings.expiration,
1539 remember = False,
1540 )
1541
1542 self.user = user
1543 session.flash = self.messages.logged_in
1544 if log and self.user:
1545 self.log_event(log % self.user)
1546
1547
1548 if self.settings.login_form == self:
1549 if accepted_form:
1550 callback(onaccept,form)
1551 if isinstance(next, (list, tuple)):
1552
1553 next = next[0]
1554 if next and not next[0] == '/' and next[:4] != 'http':
1555 next = self.url(next.replace('[id]', str(form.vars.id)))
1556 redirect(next)
1557 table_user[username].requires = old_requires
1558 return form
1559 else:
1560 redirect(next)
1561
1563 """
1564 logout and redirects to login
1565
1566 .. method:: Auth.logout ([next=DEFAULT[, onlogout=DEFAULT[,
1567 log=DEFAULT]]])
1568
1569 """
1570
1571 if next == DEFAULT:
1572 next = self.settings.logout_next
1573 if onlogout == DEFAULT:
1574 onlogout = self.settings.logout_onlogout
1575 if onlogout:
1576 onlogout(self.user)
1577 if log == DEFAULT:
1578 log = self.messages.logout_log
1579 if log and self.user:
1580 self.log_event(log % self.user)
1581
1582 if self.settings.login_form != self:
1583 cas = self.settings.login_form
1584 cas_user = cas.get_user()
1585 if cas_user:
1586 next = cas.logout_url(next)
1587
1588 self.environment.session.auth = None
1589 self.environment.session.flash = self.messages.logged_out
1590 if next:
1591 redirect(next)
1592
1600 """
1601 returns a registration form
1602
1603 .. method:: Auth.register([next=DEFAULT [, onvalidation=DEFAULT
1604 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1605
1606 """
1607
1608 table_user = self.settings.table_user
1609 request = self.environment.request
1610 response = self.environment.response
1611 session = self.environment.session
1612 if self.is_logged_in():
1613 redirect(self.settings.logged_url)
1614 if next == DEFAULT:
1615 next = request.get_vars._next \
1616 or request.post_vars._next \
1617 or self.settings.register_next
1618 if onvalidation == DEFAULT:
1619 onvalidation = self.settings.register_onvalidation
1620 if onaccept == DEFAULT:
1621 onaccept = self.settings.register_onaccept
1622 if log == DEFAULT:
1623 log = self.messages.register_log
1624
1625 passfield = self.settings.password_field
1626 formstyle = self.settings.formstyle
1627 form = SQLFORM(table_user,
1628 fields = self.settings.register_fields,
1629 hidden=dict(_next=next),
1630 showid=self.settings.showid,
1631 submit_button=self.messages.submit_button,
1632 delete_label=self.messages.delete_label,
1633 formstyle=formstyle
1634 )
1635 for i, row in enumerate(form[0].components):
1636 item = row.element('input',_name=passfield)
1637 if item:
1638 form.custom.widget.password_two = \
1639 INPUT(_name="password_two", _type="password",
1640 requires=IS_EXPR('value==%s' % \
1641 repr(request.vars.get(passfield, None)),
1642 error_message=self.messages.mismatched_password))
1643
1644 addrow(form, self.messages.verify_password + ':',
1645 form.custom.widget.password_two,
1646 self.messages.verify_password_comment,
1647 formstyle,
1648 '%s_%s__row' % (table_user, 'password_two'),
1649 position=i+1)
1650 break
1651 captcha = self.settings.register_captcha or self.settings.captcha
1652 if captcha:
1653 addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row')
1654
1655 table_user.registration_key.default = key = web2py_uuid()
1656 if form.accepts(request, session, formname='register',
1657 onvalidation=onvalidation,hideerror=self.settings.hideerror):
1658 description = self.messages.group_description % form.vars
1659 if self.settings.create_user_groups:
1660 group_id = self.add_group("user_%s" % form.vars.id, description)
1661 self.add_membership(group_id, form.vars.id)
1662 if self.settings.registration_requires_verification:
1663 if not self.settings.mailer or \
1664 not self.settings.mailer.send(to=form.vars.email,
1665 subject=self.messages.verify_email_subject,
1666 message=self.messages.verify_email
1667 % dict(key=key)):
1668 self.db.rollback()
1669 response.flash = self.messages.unable_send_email
1670 return form
1671 session.flash = self.messages.email_sent
1672 elif self.settings.registration_requires_approval:
1673 table_user[form.vars.id] = dict(registration_key='pending')
1674 session.flash = self.messages.registration_pending
1675 else:
1676 table_user[form.vars.id] = dict(registration_key='')
1677 session.flash = self.messages.registration_successful
1678 table_user = self.settings.table_user
1679 if 'username' in table_user.fields:
1680 username = 'username'
1681 else:
1682 username = 'email'
1683 user = self.db(table_user[username] == form.vars[username]).select().first()
1684 user = Storage(table_user._filter_fields(user, id=True))
1685 session.auth = Storage(user=user, last_visit=request.now,
1686 expiration=self.settings.expiration)
1687 self.user = user
1688 session.flash = self.messages.logged_in
1689 if log:
1690 self.log_event(log % form.vars)
1691 callback(onaccept,form)
1692 if not next:
1693 next = self.url(args = request.args)
1694 elif isinstance(next, (list, tuple)):
1695 next = next[0]
1696 elif next and not next[0] == '/' and next[:4] != 'http':
1697 next = self.url(next.replace('[id]', str(form.vars.id)))
1698 redirect(next)
1699 return form
1700
1702 """
1703 checks if the user is logged in and returns True/False.
1704 if so user is in auth.user as well as in session.auth.user
1705 """
1706
1707 if self.user:
1708 return True
1709 return False
1710
1717 """
1718 action user to verify the registration email, XXXXXXXXXXXXXXXX
1719
1720 .. method:: Auth.verify_email([next=DEFAULT [, onvalidation=DEFAULT
1721 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1722
1723 """
1724
1725 key = self.environment.request.args[-1]
1726 table_user = self.settings.table_user
1727 user = self.db(table_user.registration_key == key).select().first()
1728 if not user:
1729 raise HTTP(404)
1730 if self.settings.registration_requires_approval:
1731 user.update_record(registration_key = 'pending')
1732 self.environment.session.flash = self.messages.registration_pending
1733 else:
1734 user.update_record(registration_key = '')
1735 self.environment.session.flash = self.messages.email_verified
1736 if log == DEFAULT:
1737 log = self.messages.verify_email_log
1738 if next == DEFAULT:
1739 next = self.settings.verify_email_next
1740 if onaccept == DEFAULT:
1741 onaccept = self.settings.verify_email_onaccept
1742 if log:
1743 self.log_event(log % user)
1744 callback(onaccept,user)
1745 redirect(next)
1746
1754 """
1755 returns a form to retrieve the user username
1756 (only if there is a username field)
1757
1758 .. method:: Auth.retrieve_username([next=DEFAULT
1759 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
1760
1761 """
1762
1763 table_user = self.settings.table_user
1764 if not 'username' in table_user.fields:
1765 raise HTTP(404)
1766 request = self.environment.request
1767 response = self.environment.response
1768 session = self.environment.session
1769 captcha = self.settings.retrieve_username_captcha or \
1770 (self.settings.retrieve_username_captcha!=False and self.settings.captcha)
1771 if not self.settings.mailer:
1772 response.flash = self.messages.function_disabled
1773 return ''
1774 if next == DEFAULT:
1775 next = request.get_vars._next \
1776 or request.post_vars._next \
1777 or self.settings.retrieve_username_next
1778 if onvalidation == DEFAULT:
1779 onvalidation = self.settings.retrieve_username_onvalidation
1780 if onaccept == DEFAULT:
1781 onaccept = self.settings.retrieve_username_onaccept
1782 if log == DEFAULT:
1783 log = self.messages.retrieve_username_log
1784 old_requires = table_user.email.requires
1785 table_user.email.requires = [IS_IN_DB(self.db, table_user.email,
1786 error_message=self.messages.invalid_email)]
1787 form = SQLFORM(table_user,
1788 fields=['email'],
1789 hidden=dict(_next=next),
1790 showid=self.settings.showid,
1791 submit_button=self.messages.submit_button,
1792 delete_label=self.messages.delete_label,
1793 formstyle=self.settings.formstyle
1794 )
1795 if captcha:
1796 addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row')
1797
1798 if form.accepts(request, session,
1799 formname='retrieve_username', dbio=False,
1800 onvalidation=onvalidation,hideerror=self.settings.hideerror):
1801 user = self.db(table_user.email == form.vars.email).select().first()
1802 if not user:
1803 self.environment.session.flash = \
1804 self.messages.invalid_email
1805 redirect(self.url(args=request.args))
1806 username = user.username
1807 self.settings.mailer.send(to=form.vars.email,
1808 subject=self.messages.retrieve_username_subject,
1809 message=self.messages.retrieve_username
1810 % dict(username=username))
1811 session.flash = self.messages.email_sent
1812 if log:
1813 self.log_event(log % user)
1814 callback(onaccept,form)
1815 if not next:
1816 next = self.url(args = request.args)
1817 elif isinstance(next, (list, tuple)):
1818 next = next[0]
1819 elif next and not next[0] == '/' and next[:4] != 'http':
1820 next = self.url(next.replace('[id]', str(form.vars.id)))
1821 redirect(next)
1822 table_user.email.requires = old_requires
1823 return form
1824
1826 import string
1827 import random
1828 password = ''
1829 specials=r'!#$*'
1830 for i in range(0,3):
1831 password += random.choice(string.lowercase)
1832 password += random.choice(string.uppercase)
1833 password += random.choice(string.digits)
1834 password += random.choice(specials)
1835 return ''.join(random.sample(password,len(password)))
1836
1844 """
1845 returns a form to reset the user password (deprecated)
1846
1847 .. method:: Auth.reset_password_deprecated([next=DEFAULT
1848 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
1849
1850 """
1851
1852 table_user = self.settings.table_user
1853 request = self.environment.request
1854 response = self.environment.response
1855 session = self.environment.session
1856 if not self.settings.mailer:
1857 response.flash = self.messages.function_disabled
1858 return ''
1859 if next == DEFAULT:
1860 next = request.get_vars._next \
1861 or request.post_vars._next \
1862 or self.settings.retrieve_password_next
1863 if onvalidation == DEFAULT:
1864 onvalidation = self.settings.retrieve_password_onvalidation
1865 if onaccept == DEFAULT:
1866 onaccept = self.settings.retrieve_password_onaccept
1867 if log == DEFAULT:
1868 log = self.messages.retrieve_password_log
1869 old_requires = table_user.email.requires
1870 table_user.email.requires = [IS_IN_DB(self.db, table_user.email,
1871 error_message=self.messages.invalid_email)]
1872 form = SQLFORM(table_user,
1873 fields=['email'],
1874 hidden=dict(_next=next),
1875 showid=self.settings.showid,
1876 submit_button=self.messages.submit_button,
1877 delete_label=self.messages.delete_label,
1878 formstyle=self.settings.formstyle
1879 )
1880 if form.accepts(request, session,
1881 formname='retrieve_password', dbio=False,
1882 onvalidation=onvalidation,hideerror=self.settings.hideerror):
1883 user = self.db(table_user.email == form.vars.email).select().first()
1884 if not user:
1885 self.environment.session.flash = \
1886 self.messages.invalid_email
1887 redirect(self.url(args=request.args))
1888 elif user.registration_key in ('pending','disabled','blocked'):
1889 self.environment.session.flash = \
1890 self.messages.registration_pending
1891 redirect(self.url(args=request.args))
1892 password = self.random_password()
1893 passfield = self.settings.password_field
1894 d = {passfield: table_user[passfield].validate(password)[0],
1895 'registration_key': ''}
1896 user.update_record(**d)
1897 if self.settings.mailer and \
1898 self.settings.mailer.send(to=form.vars.email,
1899 subject=self.messages.retrieve_password_subject,
1900 message=self.messages.retrieve_password \
1901 % dict(password=password)):
1902 session.flash = self.messages.email_sent
1903 else:
1904 session.flash = self.messages.unable_to_send_email
1905 if log:
1906 self.log_event(log % user)
1907 callback(onaccept,form)
1908 if not next:
1909 next = self.url(args = request.args)
1910 elif isinstance(next, (list, tuple)):
1911 next = next[0]
1912 elif next and not next[0] == '/' and next[:4] != 'http':
1913 next = self.url(next.replace('[id]', str(form.vars.id)))
1914 redirect(next)
1915 table_user.email.requires = old_requires
1916 return form
1917
1925 """
1926 returns a form to reset the user password
1927
1928 .. method:: Auth.reset_password([next=DEFAULT
1929 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
1930
1931 """
1932
1933 table_user = self.settings.table_user
1934 request = self.environment.request
1935
1936 session = self.environment.session
1937
1938 if next == DEFAULT:
1939 next = request.get_vars._next \
1940 or request.post_vars._next \
1941 or self.settings.reset_password_next
1942
1943 try:
1944 key = request.vars.key or request.args[-1]
1945 t0 = int(key.split('-')[0])
1946 if time.time()-t0 > 60*60*24: raise Exception
1947 user = self.db(table_user.reset_password_key == key).select().first()
1948 if not user: raise Exception
1949 except Exception:
1950 session.flash = self.messages.invalid_reset_password
1951 redirect(next)
1952 passfield = self.settings.password_field
1953 form = SQLFORM.factory(
1954 Field('new_password', 'password',
1955 label=self.messages.new_password,
1956 requires=self.settings.table_user[passfield].requires),
1957 Field('new_password2', 'password',
1958 label=self.messages.verify_password,
1959 requires=[IS_EXPR('value==%s' % repr(request.vars.new_password),
1960 self.messages.mismatched_password)]),
1961 submit_button=self.messages.submit_button,
1962 formstyle=self.settings.formstyle,
1963 )
1964 if form.accepts(request,session,hideerror=self.settings.hideerror):
1965 user.update_record(**{passfield:form.vars.new_password,
1966 'registration_key':'',
1967 'reset_password_key':''})
1968 session.flash = self.messages.password_changed
1969 redirect(next)
1970 return form
1971
1979 """
1980 returns a form to reset the user password
1981
1982 .. method:: Auth.reset_password([next=DEFAULT
1983 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
1984
1985 """
1986
1987 table_user = self.settings.table_user
1988 request = self.environment.request
1989 response = self.environment.response
1990 session = self.environment.session
1991 captcha = self.settings.retrieve_password_captcha or \
1992 (self.settings.retrieve_password_captcha!=False and self.settings.captcha)
1993
1994 if next == DEFAULT:
1995 next = request.get_vars._next \
1996 or request.post_vars._next \
1997 or self.settings.request_reset_password_next
1998
1999 if not self.settings.mailer:
2000 response.flash = self.messages.function_disabled
2001 return ''
2002 if onvalidation == DEFAULT:
2003 onvalidation = self.settings.reset_password_onvalidation
2004 if onaccept == DEFAULT:
2005 onaccept = self.settings.reset_password_onaccept
2006 if log == DEFAULT:
2007 log = self.messages.reset_password_log
2008
2009 table_user.email.requires = [
2010 IS_EMAIL(error_message=self.messages.invalid_email),
2011 IS_IN_DB(self.db, table_user.email,
2012 error_message=self.messages.invalid_email)]
2013 form = SQLFORM(table_user,
2014 fields=['email'],
2015 hidden=dict(_next=next),
2016 showid=self.settings.showid,
2017 submit_button=self.messages.submit_button,
2018 delete_label=self.messages.delete_label,
2019 formstyle=self.settings.formstyle
2020 )
2021 if captcha:
2022 addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle,'captcha__row')
2023 if form.accepts(request, session,
2024 formname='reset_password', dbio=False,
2025 onvalidation=onvalidation,
2026 hideerror=self.settings.hideerror):
2027 user = self.db(table_user.email == form.vars.email).select().first()
2028 if not user:
2029 session.flash = self.messages.invalid_email
2030 redirect(self.url(args=request.args))
2031 elif user.registration_key in ('pending','disabled','blocked'):
2032 session.flash = self.messages.registration_pending
2033 redirect(self.url(args=request.args))
2034 reset_password_key = str(int(time.time()))+'-' + web2py_uuid()
2035
2036 if self.settings.mailer.send(to=form.vars.email,
2037 subject=self.messages.reset_password_subject,
2038 message=self.messages.reset_password % \
2039 dict(key=reset_password_key)):
2040 session.flash = self.messages.email_sent
2041 user.update_record(reset_password_key=reset_password_key)
2042 else:
2043 session.flash = self.messages.unable_to_send_email
2044 if log:
2045 self.log_event(log % user)
2046 callback(onaccept,form)
2047 if not next:
2048 next = self.url(args = request.args)
2049 elif isinstance(next, (list, tuple)):
2050 next = next[0]
2051 elif next and not next[0] == '/' and next[:4] != 'http':
2052 next = self.url(next.replace('[id]', str(form.vars.id)))
2053 redirect(next)
2054
2055 return form
2056
2068
2076 """
2077 returns a form that lets the user change password
2078
2079 .. method:: Auth.change_password([next=DEFAULT[, onvalidation=DEFAULT[,
2080 onaccept=DEFAULT[, log=DEFAULT]]]])
2081 """
2082
2083 if not self.is_logged_in():
2084 redirect(self.settings.login_url)
2085 db = self.db
2086 table_user = self.settings.table_user
2087 usern = self.settings.table_user_name
2088 s = db(table_user.id == self.user.id)
2089
2090 request = self.environment.request
2091 session = self.environment.session
2092 if next == DEFAULT:
2093 next = request.get_vars._next \
2094 or request.post_vars._next \
2095 or self.settings.change_password_next
2096 if onvalidation == DEFAULT:
2097 onvalidation = self.settings.change_password_onvalidation
2098 if onaccept == DEFAULT:
2099 onaccept = self.settings.change_password_onaccept
2100 if log == DEFAULT:
2101 log = self.messages.change_password_log
2102 passfield = self.settings.password_field
2103 form = SQLFORM.factory(
2104 Field('old_password', 'password',
2105 label=self.messages.old_password,
2106 requires=validators(
2107 table_user[passfield].requires,
2108 IS_IN_DB(s, '%s.%s' % (usern, passfield),
2109 error_message=self.messages.invalid_password))),
2110 Field('new_password', 'password',
2111 label=self.messages.new_password,
2112 requires=table_user[passfield].requires),
2113 Field('new_password2', 'password',
2114 label=self.messages.verify_password,
2115 requires=[IS_EXPR('value==%s' % repr(request.vars.new_password),
2116 self.messages.mismatched_password)]),
2117 submit_button=self.messages.submit_button,
2118 formstyle = self.settings.formstyle
2119 )
2120 if form.accepts(request, session,
2121 formname='change_password',
2122 onvalidation=onvalidation,
2123 hideerror=self.settings.hideerror):
2124 d = {passfield: form.vars.new_password}
2125 s.update(**d)
2126 session.flash = self.messages.password_changed
2127 if log:
2128 self.log_event(log % self.user)
2129 callback(onaccept,form)
2130 if not next:
2131 next = self.url(args=request.args)
2132 elif isinstance(next, (list, tuple)):
2133 next = next[0]
2134 elif next and not next[0] == '/' and next[:4] != 'http':
2135 next = self.url(next.replace('[id]', str(form.vars.id)))
2136 redirect(next)
2137 return form
2138
2146 """
2147 returns a form that lets the user change his/her profile
2148
2149 .. method:: Auth.profile([next=DEFAULT [, onvalidation=DEFAULT
2150 [, onaccept=DEFAULT [, log=DEFAULT]]]])
2151
2152 """
2153
2154 table_user = self.settings.table_user
2155 if not self.is_logged_in():
2156 redirect(self.settings.login_url)
2157 passfield = self.settings.password_field
2158 self.settings.table_user[passfield].writable = False
2159 request = self.environment.request
2160 session = self.environment.session
2161 if next == DEFAULT:
2162 next = request.get_vars._next \
2163 or request.post_vars._next \
2164 or self.settings.profile_next
2165 if onvalidation == DEFAULT:
2166 onvalidation = self.settings.profile_onvalidation
2167 if onaccept == DEFAULT:
2168 onaccept = self.settings.profile_onaccept
2169 if log == DEFAULT:
2170 log = self.messages.profile_log
2171 form = SQLFORM(
2172 table_user,
2173 self.user.id,
2174 fields = self.settings.profile_fields,
2175 hidden = dict(_next=next),
2176 showid = self.settings.showid,
2177 submit_button = self.messages.submit_button,
2178 delete_label = self.messages.delete_label,
2179 upload = self.settings.download_url,
2180 formstyle = self.settings.formstyle
2181 )
2182 if form.accepts(request, session,
2183 formname='profile',
2184 onvalidation=onvalidation,hideerror=self.settings.hideerror):
2185 self.user.update(table_user._filter_fields(form.vars))
2186 session.flash = self.messages.profile_updated
2187 if log:
2188 self.log_event(log % self.user)
2189 callback(onaccept,form)
2190 if not next:
2191 next = self.url(args=request.args)
2192 elif isinstance(next, (list, tuple)):
2193 next = next[0]
2194 elif next and not next[0] == '/' and next[:4] != 'http':
2195 next = self.url(next.replace('[id]', str(form.vars.id)))
2196 redirect(next)
2197 return form
2198
2200 return self.environment.session.auth.impersonator
2201
2203 """
2204 usage: POST TO http://..../impersonate request.post_vars.user_id=<id>
2205 set request.post_vars.user_id to 0 to restore original user.
2206
2207 requires impersonator is logged in and
2208 has_permission('impersonate', 'auth_user', user_id)
2209 """
2210 request = self.environment.request
2211 session = self.environment.session
2212 auth = session.auth
2213 if not self.is_logged_in() or not self.environment.request.post_vars:
2214 raise HTTP(401, "Not Authorized")
2215 current_id = auth.user.id
2216 requested_id = user_id
2217 if user_id == DEFAULT:
2218 user_id = self.environment.request.post_vars.user_id
2219 if user_id and user_id != self.user.id and user_id != '0':
2220 if not self.has_permission('impersonate',
2221 self.settings.table_user_name,
2222 user_id):
2223 raise HTTP(403, "Forbidden")
2224 user = self.settings.table_user(user_id)
2225 if not user:
2226 raise HTTP(401, "Not Authorized")
2227 auth.impersonator = cPickle.dumps(session)
2228 auth.user.update(
2229 self.settings.table_user._filter_fields(user, True))
2230 self.user = auth.user
2231 if self.settings.login_onaccept:
2232 form = Storage(dict(vars=self.user))
2233 self.settings.login_onaccept(form)
2234 log = self.messages.impersonate_log
2235 if log:
2236 self.log_event(log % dict(id=current_id,other_id=auth.user.id))
2237 elif user_id in (0, '0') and self.is_impersonating():
2238 session.clear()
2239 session.update(cPickle.loads(auth.impersonator))
2240 self.user = session.auth.user
2241 if requested_id == DEFAULT and not request.post_vars:
2242 return SQLFORM.factory(Field('user_id','integer'))
2243 return self.user
2244
2246 """
2247 displays the groups and their roles for the logged in user
2248 """
2249
2250 if not self.is_logged_in():
2251 redirect(self.settings.login_url)
2252 memberships = self.db(self.settings.table_membership.user_id
2253 == self.user.id).select()
2254 table = TABLE()
2255 for membership in memberships:
2256 groups = self.db(self.settings.table_group.id
2257 == membership.group_id).select()
2258 if groups:
2259 group = groups[0]
2260 table.append(TR(H3(group.role, '(%s)' % group.id)))
2261 table.append(TR(P(group.description)))
2262 if not memberships:
2263 return None
2264 return table
2265
2267 """
2268 you can change the view for this page to make it look as you like
2269 """
2270
2271 return 'ACCESS DENIED'
2272
2274 """
2275 decorator that prevents access to action if not logged in
2276 """
2277
2278 def decorator(action):
2279
2280 def f(*a, **b):
2281
2282 if self.settings.allow_basic_login_only and not self.basic():
2283 if self.environment.request.is_restful:
2284 raise HTTP(403,"Not authorized")
2285 return call_or_redirect(self.settings.on_failed_authorization)
2286
2287 if not condition:
2288 if self.environment.request.is_restful:
2289 raise HTTP(403,"Not authorized")
2290 if not self.basic() and not self.is_logged_in():
2291 request = self.environment.request
2292 next = URL(r=request,args=request.args,
2293 vars=request.get_vars)
2294 self.environment.session.flash = self.environment.response.flash
2295 return call_or_redirect(self.settings.on_failed_authentication,
2296 self.settings.login_url + \
2297 '?_next='+urllib.quote(next))
2298 else:
2299 self.environment.session.flash = \
2300 self.messages.access_denied
2301 return call_or_redirect(self.settings.on_failed_authorization)
2302 return action(*a, **b)
2303 f.__doc__ = action.__doc__
2304 f.__name__ = action.__name__
2305 f.__dict__.update(action.__dict__)
2306 return f
2307
2308 return decorator
2309
2311 """
2312 decorator that prevents access to action if not logged in
2313 """
2314
2315 def decorator(action):
2316
2317 def f(*a, **b):
2318
2319 if self.settings.allow_basic_login_only and not self.basic():
2320 if self.environment.request.is_restful:
2321 raise HTTP(403,"Not authorized")
2322 return call_or_redirect(self.settings.on_failed_authorization)
2323
2324 if not self.basic() and not self.is_logged_in():
2325 if self.environment.request.is_restful:
2326 raise HTTP(403,"Not authorized")
2327 request = self.environment.request
2328 next = URL(r=request,args=request.args,
2329 vars=request.get_vars)
2330 self.environment.session.flash = self.environment.response.flash
2331 return call_or_redirect(self.settings.on_failed_authentication,
2332 self.settings.login_url + \
2333 '?_next='+urllib.quote(next)
2334 )
2335 return action(*a, **b)
2336 f.__doc__ = action.__doc__
2337 f.__name__ = action.__name__
2338 f.__dict__.update(action.__dict__)
2339 return f
2340
2341 return decorator
2342
2344 """
2345 decorator that prevents access to action if not logged in or
2346 if user logged in is not a member of group_id.
2347 If role is provided instead of group_id then the
2348 group_id is calculated.
2349 """
2350
2351 def decorator(action):
2352 def f(*a, **b):
2353 if self.settings.allow_basic_login_only and not self.basic():
2354 if self.environment.request.is_restful:
2355 raise HTTP(403,"Not authorized")
2356 return call_or_redirect(self.settings.on_failed_authorization)
2357
2358 if not self.basic() and not self.is_logged_in():
2359 if self.environment.request.is_restful:
2360 raise HTTP(403,"Not authorized")
2361 request = self.environment.request
2362 next = URL(r=request,args=request.args,
2363 vars=request.get_vars)
2364 self.environment.session.flash = self.environment.response.flash
2365 return call_or_redirect(self.settings.on_failed_authentication,
2366 self.settings.login_url + \
2367 '?_next='+urllib.quote(next)
2368 )
2369 if not self.has_membership(group_id=group_id, role=role):
2370 self.environment.session.flash = \
2371 self.messages.access_denied
2372 return call_or_redirect(self.settings.on_failed_authorization)
2373 return action(*a, **b)
2374 f.__doc__ = action.__doc__
2375 f.__name__ = action.__name__
2376 f.__dict__.update(action.__dict__)
2377 return f
2378
2379 return decorator
2380
2387 """
2388 decorator that prevents access to action if not logged in or
2389 if user logged in is not a member of any group (role) that
2390 has 'name' access to 'table_name', 'record_id'.
2391 """
2392
2393 def decorator(action):
2394
2395 def f(*a, **b):
2396 if self.settings.allow_basic_login_only and not self.basic():
2397 if self.environment.request.is_restful:
2398 raise HTTP(403,"Not authorized")
2399 return call_or_redirect(self.settings.on_failed_authorization)
2400
2401 if not self.basic() and not self.is_logged_in():
2402 if self.environment.request.is_restful:
2403 raise HTTP(403,"Not authorized")
2404 request = self.environment.request
2405 next = URL(r=request,args=request.args,
2406 vars=request.get_vars)
2407 self.environment.session.flash = self.environment.response.flash
2408 return call_or_redirect(self.settings.on_failed_authentication,
2409 self.settings.login_url +
2410 '?_next='+urllib.quote(next)
2411 )
2412
2413 if not self.has_permission(name, table_name, record_id):
2414 self.environment.session.flash = \
2415 self.messages.access_denied
2416 return call_or_redirect(self.settings.on_failed_authorization)
2417 return action(*a, **b)
2418 f.__doc__ = action.__doc__
2419 f.__name__ = action.__name__
2420 f.__dict__.update(action.__dict__)
2421 return f
2422
2423 return decorator
2424
2426 """
2427 creates a group associated to a role
2428 """
2429
2430 group_id = self.settings.table_group.insert(role=role,
2431 description=description)
2432 log = self.messages.add_group_log
2433 if log:
2434 self.log_event(log % dict(group_id=group_id, role=role))
2435 return group_id
2436
2438 """
2439 deletes a group
2440 """
2441
2442 self.db(self.settings.table_group.id == group_id).delete()
2443 self.db(self.settings.table_membership.group_id
2444 == group_id).delete()
2445 self.db(self.settings.table_permission.group_id
2446 == group_id).delete()
2447 log = self.messages.del_group_log
2448 if log:
2449 self.log_event(log % dict(group_id=group_id))
2450
2452 """
2453 returns the group_id of the group specified by the role
2454 """
2455 rows = self.db(self.settings.table_group.role == role).select()
2456 if not rows:
2457 return None
2458 return rows[0].id
2459
2461 """
2462 returns the group_id of the group uniquely associated to this user
2463 i.e. role=user:[user_id]
2464 """
2465 if not user_id and self.user:
2466 user_id = self.user.id
2467 role = 'user_%s' % user_id
2468 return self.id_group(role)
2469
2471 """
2472 checks if user is member of group_id or role
2473 """
2474
2475 group_id = group_id or self.id_group(role)
2476 try:
2477 group_id = int(group_id)
2478 except:
2479 group_id = self.id_group(group_id)
2480 if not user_id and self.user:
2481 user_id = self.user.id
2482 membership = self.settings.table_membership
2483 if self.db((membership.user_id == user_id)
2484 & (membership.group_id == group_id)).select():
2485 r = True
2486 else:
2487 r = False
2488 log = self.messages.has_membership_log
2489 if log:
2490 self.log_event(log % dict(user_id=user_id,
2491 group_id=group_id, check=r))
2492 return r
2493
2495 """
2496 gives user_id membership of group_id or role
2497 if user_id==None than user_id is that of current logged in user
2498 """
2499
2500 group_id = group_id or self.id_group(role)
2501 try:
2502 group_id = int(group_id)
2503 except:
2504 group_id = self.id_group(group_id)
2505 if not user_id and self.user:
2506 user_id = self.user.id
2507 membership = self.settings.table_membership
2508 record = membership(user_id = user_id,group_id = group_id)
2509 if record:
2510 return record.id
2511 else:
2512 id = membership.insert(group_id=group_id, user_id=user_id)
2513 log = self.messages.add_membership_log
2514 if log:
2515 self.log_event(log % dict(user_id=user_id,
2516 group_id=group_id))
2517 return id
2518
2520 """
2521 revokes membership from group_id to user_id
2522 if user_id==None than user_id is that of current logged in user
2523 """
2524
2525 group_id = group_id or self.id_group(role)
2526 if not user_id and self.user:
2527 user_id = self.user.id
2528 membership = self.settings.table_membership
2529 log = self.messages.del_membership_log
2530 if log:
2531 self.log_event(log % dict(user_id=user_id,
2532 group_id=group_id))
2533 return self.db(membership.user_id
2534 == user_id)(membership.group_id
2535 == group_id).delete()
2536
2537 - def has_permission(
2538 self,
2539 name='any',
2540 table_name='',
2541 record_id=0,
2542 user_id=None,
2543 group_id=None,
2544 ):
2545 """
2546 checks if user_id or current logged in user is member of a group
2547 that has 'name' permission on 'table_name' and 'record_id'
2548 if group_id is passed, it checks whether the group has the permission
2549 """
2550
2551 if not user_id and not group_id and self.user:
2552 user_id = self.user.id
2553 if user_id:
2554 membership = self.settings.table_membership
2555 rows = self.db(membership.user_id
2556 == user_id).select(membership.group_id)
2557 groups = set([row.group_id for row in rows])
2558 if group_id and not group_id in groups:
2559 return False
2560 else:
2561 groups = set([group_id])
2562 permission = self.settings.table_permission
2563 rows = self.db(permission.name == name)(permission.table_name
2564 == str(table_name))(permission.record_id
2565 == record_id).select(permission.group_id)
2566 groups_required = set([row.group_id for row in rows])
2567 if record_id:
2568 rows = self.db(permission.name
2569 == name)(permission.table_name
2570 == str(table_name))(permission.record_id
2571 == 0).select(permission.group_id)
2572 groups_required = groups_required.union(set([row.group_id
2573 for row in rows]))
2574 if groups.intersection(groups_required):
2575 r = True
2576 else:
2577 r = False
2578 log = self.messages.has_permission_log
2579 if log and user_id:
2580 self.log_event(log % dict(user_id=user_id, name=name,
2581 table_name=table_name, record_id=record_id))
2582 return r
2583
2584 - def add_permission(
2585 self,
2586 group_id,
2587 name='any',
2588 table_name='',
2589 record_id=0,
2590 ):
2591 """
2592 gives group_id 'name' access to 'table_name' and 'record_id'
2593 """
2594
2595 permission = self.settings.table_permission
2596 if group_id == 0:
2597 group_id = self.user_group()
2598 id = permission.insert(group_id=group_id, name=name,
2599 table_name=str(table_name),
2600 record_id=long(record_id))
2601 log = self.messages.add_permission_log
2602 if log:
2603 self.log_event(log % dict(permission_id=id, group_id=group_id,
2604 name=name, table_name=table_name,
2605 record_id=record_id))
2606 return id
2607
2608 - def del_permission(
2609 self,
2610 group_id,
2611 name='any',
2612 table_name='',
2613 record_id=0,
2614 ):
2615 """
2616 revokes group_id 'name' access to 'table_name' and 'record_id'
2617 """
2618
2619 permission = self.settings.table_permission
2620 log = self.messages.del_permission_log
2621 if log:
2622 self.log_event(log % dict(group_id=group_id, name=name,
2623 table_name=table_name, record_id=record_id))
2624 return self.db(permission.group_id == group_id)(permission.name
2625 == name)(permission.table_name
2626 == str(table_name))(permission.record_id
2627 == long(record_id)).delete()
2628
2630 """
2631 returns a query with all accessible records for user_id or
2632 the current logged in user
2633 this method does not work on GAE because uses JOIN and IN
2634
2635 example::
2636
2637 db(auth.accessible_query('read', db.mytable)).select(db.mytable.ALL)
2638
2639 """
2640 if not user_id:
2641 user_id = self.user.id
2642 if self.has_permission(name, table, 0, user_id):
2643 return table.id > 0
2644 db = self.db
2645 membership = self.settings.table_membership
2646 permission = self.settings.table_permission
2647 return table.id.belongs(db(membership.user_id == user_id)\
2648 (membership.group_id == permission.group_id)\
2649 (permission.name == name)\
2650 (permission.table_name == table)\
2651 ._select(permission.record_id))
2652
2653
2654 -class Crud(object):
2655
2656 - def url(self, f=None, args=[], vars={}):
2657 """
2658 this should point to the controller that exposes
2659 download and crud
2660 """
2661 return self.environment.URL(r=self.environment.request,
2662 c=self.settings.controller,
2663 f=f, args=args, vars=vars)
2664
2665 - def __init__(self, environment, db, controller='default'):
2666 self.environment = Storage(environment)
2667 self.db = db
2668 self.settings = Settings()
2669 self.settings.auth = None
2670 self.settings.logger = None
2671
2672 self.settings.create_next = None
2673 self.settings.update_next = None
2674 self.settings.controller = controller
2675 self.settings.delete_next = self.url()
2676 self.settings.download_url = self.url('download')
2677 self.settings.create_onvalidation = StorageList()
2678 self.settings.update_onvalidation = StorageList()
2679 self.settings.delete_onvalidation = StorageList()
2680 self.settings.create_onaccept = StorageList()
2681 self.settings.update_onaccept = StorageList()
2682 self.settings.update_ondelete = StorageList()
2683 self.settings.delete_onaccept = StorageList()
2684 self.settings.update_deletable = True
2685 self.settings.showid = False
2686 self.settings.keepvalues = False
2687 self.settings.create_captcha = None
2688 self.settings.update_captcha = None
2689 self.settings.captcha = None
2690 self.settings.formstyle = 'table3cols'
2691 self.settings.hideerror = False
2692 self.settings.detect_record_change = True
2693 self.settings.lock_keys = True
2694
2695 self.messages = Messages(self.environment.T)
2696 self.messages.submit_button = 'Submit'
2697 self.messages.delete_label = 'Check to delete:'
2698 self.messages.record_created = 'Record Created'
2699 self.messages.record_updated = 'Record Updated'
2700 self.messages.record_deleted = 'Record Deleted'
2701
2702 self.messages.update_log = 'Record %(id)s updated'
2703 self.messages.create_log = 'Record %(id)s created'
2704 self.messages.read_log = 'Record %(id)s read'
2705 self.messages.delete_log = 'Record %(id)s deleted'
2706
2707 self.messages.lock_keys = True
2708
2710
2711 args = self.environment.request.args
2712 if len(args) < 1:
2713 redirect(self.url(args='tables'))
2714 elif args[0] == 'tables':
2715 return self.tables()
2716 elif args[0] == 'create':
2717 return self.create(args(1))
2718 elif args[0] == 'select':
2719 return self.select(args(1),linkto=self.url(args='read'))
2720 elif args[0] == 'read':
2721 return self.read(args(1), args(2))
2722 elif args[0] == 'update':
2723 return self.update(args(1), args(2))
2724 elif args[0] == 'delete':
2725 return self.delete(args(1), args(2))
2726 else:
2727 raise HTTP(404)
2728
2732
2734 if not self.settings.auth:
2735 return True
2736 try:
2737 record_id = record.id
2738 except:
2739 record_id = record
2740 return self.settings.auth.has_permission(name, str(table), record_id)
2741
2746
2747
2748 @staticmethod
2749 - def archive(form,archive_table=None,current_record='current_record'):
2750 """
2751 If you have a table (db.mytable) that needs full revision history you can just do::
2752
2753 form=crud.update(db.mytable,myrecord,onaccept=crud.archive)
2754
2755 crud.archive will define a new table "mytable_archive" and store the
2756 previous record in the newly created table including a reference
2757 to the current record.
2758
2759 If you want to access such table you need to define it yourself in a model::
2760
2761 db.define_table('mytable_archive',
2762 Field('current_record',db.mytable),
2763 db.mytable)
2764
2765 Notice such table includes all fields of db.mytable plus one: current_record.
2766 crud.archive does not timestamp the stored record unless your original table
2767 has a fields like::
2768
2769 db.define_table(...,
2770 Field('saved_on','datetime',
2771 default=request.now,update=request.now,writable=False),
2772 Field('saved_by',auth.user,
2773 default=auth.user_id,update=auth.user_id,writable=False),
2774
2775 there is nothing special about these fields since they are filled before
2776 the record is archived.
2777
2778 If you want to change the archive table name and the name of the reference field
2779 you can do, for example::
2780
2781 db.define_table('myhistory',
2782 Field('parent_record',db.mytable),
2783 db.mytable)
2784
2785 and use it as::
2786
2787 form=crud.update(db.mytable,myrecord,
2788 onaccept=lambda form:crud.archive(form,
2789 archive_table=db.myhistory,
2790 current_record='parent_record'))
2791
2792 """
2793 old_record = form.record
2794 if not old_record:
2795 return None
2796 table = form.table
2797 if not archive_table:
2798 archive_table_name = '%s_archive' % table
2799 if archive_table_name in table._db:
2800 archive_table = table._db[archive_table_name]
2801 else:
2802 archive_table = table._db.define_table(archive_table_name,
2803 Field(current_record,table),
2804 table)
2805 new_record = {current_record:old_record.id}
2806 for fieldname in archive_table.fields:
2807 if not fieldname in ['id',current_record] and fieldname in old_record:
2808 new_record[fieldname]=old_record[fieldname]
2809 id = archive_table.insert(**new_record)
2810 return id
2811
2812 - def update(
2813 self,
2814 table,
2815 record,
2816 next=DEFAULT,
2817 onvalidation=DEFAULT,
2818 onaccept=DEFAULT,
2819 ondelete=DEFAULT,
2820 log=DEFAULT,
2821 message=DEFAULT,
2822 deletable=DEFAULT,
2823 formname=DEFAULT,
2824 ):
2825 """
2826 .. method:: Crud.update(table, record, [next=DEFAULT
2827 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT
2828 [, message=DEFAULT[, deletable=DEFAULT]]]]]])
2829
2830 """
2831 if not (isinstance(table, self.db.Table) or table in self.db.tables) \
2832 or (isinstance(record, str) and not str(record).isdigit()):
2833 raise HTTP(404)
2834 if not isinstance(table, self.db.Table):
2835 table = self.db[table]
2836 try:
2837 record_id = record.id
2838 except:
2839 record_id = record or 0
2840 if record_id and not self.has_permission('update', table, record_id):
2841 redirect(self.settings.auth.settings.on_failed_authorization)
2842 if not record_id \
2843 and not self.has_permission('create', table, record_id):
2844 redirect(self.settings.auth.settings.on_failed_authorization)
2845
2846 request = self.environment.request
2847 response = self.environment.response
2848 session = self.environment.session
2849 if request.extension == 'json' and request.vars.json:
2850 request.vars.update(simplejson.loads(request.vars.json))
2851 if next == DEFAULT:
2852 next = request.get_vars._next \
2853 or request.post_vars._next \
2854 or self.settings.update_next
2855 if onvalidation == DEFAULT:
2856 onvalidation = self.settings.update_onvalidation
2857 if onaccept == DEFAULT:
2858 onaccept = self.settings.update_onaccept
2859 if ondelete == DEFAULT:
2860 ondelete = self.settings.update_ondelete
2861 if log == DEFAULT:
2862 log = self.messages.update_log
2863 if deletable == DEFAULT:
2864 deletable = self.settings.update_deletable
2865 if message == DEFAULT:
2866 message = self.messages.record_updated
2867 form = SQLFORM(
2868 table,
2869 record,
2870 hidden=dict(_next=next),
2871 showid=self.settings.showid,
2872 submit_button=self.messages.submit_button,
2873 delete_label=self.messages.delete_label,
2874 deletable=deletable,
2875 upload=self.settings.download_url,
2876 formstyle=self.settings.formstyle
2877 )
2878 self.accepted = False
2879 self.deleted = False
2880 captcha = self.settings.update_captcha or \
2881 self.settings.captcha
2882 if record and captcha:
2883 addrow(form, captcha.label, captcha, captcha.comment,
2884 self.settings.formstyle,'captcha__row')
2885 captcha = self.settings.create_captcha or \
2886 self.settings.captcha
2887 if not record and captcha:
2888 addrow(form, captcha.label, captcha, captcha.comment,
2889 self.settings.formstyle,'captcha__row')
2890 if not request.extension in ('html','load'):
2891 (_session, _formname) = (None, None)
2892 else:
2893 (_session, _formname) = \
2894 (session, '%s/%s' % (table._tablename, form.record_id))
2895 if formname!=DEFAULT:
2896 _formname = formname
2897 keepvalues = self.settings.keepvalues
2898 if request.vars.delete_this_record:
2899 keepvalues = False
2900 if isinstance(onvalidation,StorageList):
2901 onvalidation=onvalidation.get(table._tablename, [])
2902 if form.accepts(request, _session, formname=_formname,
2903 onvalidation=onvalidation, keepvalues=keepvalues,
2904 hideerror=self.settings.hideerror,
2905 detect_record_change = self.settings.detect_record_change):
2906 self.accepted = True
2907 response.flash = message
2908 if log:
2909 self.log_event(log % form.vars)
2910 if request.vars.delete_this_record:
2911 self.deleted = True
2912 message = self.messages.record_deleted
2913 callback(ondelete,form,table._tablename)
2914 response.flash = message
2915 callback(onaccept,form,table._tablename)
2916 if not request.extension in ('html','load'):
2917 raise HTTP(200, 'RECORD CREATED/UPDATED')
2918 if isinstance(next, (list, tuple)):
2919 next = next[0]
2920 if next:
2921 if next[0] != '/' and next[:4] != 'http':
2922 next = URL(r=request,
2923 f=next.replace('[id]', str(form.vars.id)))
2924 session.flash = response.flash
2925 redirect(next)
2926 elif not request.extension in ('html','load'):
2927 raise HTTP(401)
2928 return form
2929
2940 """
2941 .. method:: Crud.create(table, [next=DEFAULT [, onvalidation=DEFAULT
2942 [, onaccept=DEFAULT [, log=DEFAULT[, message=DEFAULT]]]]])
2943 """
2944
2945 if next == DEFAULT:
2946 next = self.settings.create_next
2947 if onvalidation == DEFAULT:
2948 onvalidation = self.settings.create_onvalidation
2949 if onaccept == DEFAULT:
2950 onaccept = self.settings.create_onaccept
2951 if log == DEFAULT:
2952 log = self.messages.create_log
2953 if message == DEFAULT:
2954 message = self.messages.record_created
2955 return self.update(
2956 table,
2957 None,
2958 next=next,
2959 onvalidation=onvalidation,
2960 onaccept=onaccept,
2961 log=log,
2962 message=message,
2963 deletable=False,
2964 formname=formname,
2965 )
2966
2967 - def read(self, table, record):
2968 if not (isinstance(table, self.db.Table) or table in self.db.tables) \
2969 or (isinstance(record, str) and not str(record).isdigit()):
2970 raise HTTP(404)
2971 if not isinstance(table, self.db.Table):
2972 table = self.db[table]
2973 if not self.has_permission('read', table, record):
2974 redirect(self.settings.auth.settings.on_failed_authorization)
2975 form = SQLFORM(
2976 table,
2977 record,
2978 readonly=True,
2979 comments=False,
2980 upload=self.settings.download_url,
2981 showid=self.settings.showid,
2982 formstyle=self.settings.formstyle
2983 )
2984 if not self.environment.request.extension in ('html','load'):
2985 return table._filter_fields(form.record, id=True)
2986 return form
2987
2988 - def delete(
2989 self,
2990 table,
2991 record_id,
2992 next=DEFAULT,
2993 message=DEFAULT,
2994 ):
2995 """
2996 .. method:: Crud.delete(table, record_id, [next=DEFAULT
2997 [, message=DEFAULT]])
2998 """
2999 if not (isinstance(table, self.db.Table) or table in self.db.tables) \
3000 or not str(record_id).isdigit():
3001 raise HTTP(404)
3002 if not isinstance(table, self.db.Table):
3003 table = self.db[table]
3004 if not self.has_permission('delete', table, record_id):
3005 redirect(self.settings.auth.settings.on_failed_authorization)
3006 request = self.environment.request
3007 session = self.environment.session
3008 if next == DEFAULT:
3009 next = request.get_vars._next \
3010 or request.post_vars._next \
3011 or self.settings.delete_next
3012 if message == DEFAULT:
3013 message = self.messages.record_deleted
3014 record = table[record_id]
3015 if record:
3016 callback(self.settings.delete_onvalidation,record)
3017 del table[record_id]
3018 callback(self.settings.delete_onaccept,record,table._tablename)
3019 session.flash = message
3020 if next:
3021 redirect(next)
3022
3023 - def select(
3024 self,
3025 table,
3026 query=None,
3027 fields=None,
3028 orderby=None,
3029 limitby=None,
3030 headers={},
3031 **attr
3032 ):
3033 request = self.environment.request
3034 if not (isinstance(table, self.db.Table) or table in self.db.tables):
3035 raise HTTP(404)
3036 if not self.has_permission('select', table):
3037 redirect(self.settings.auth.settings.on_failed_authorization)
3038
3039
3040 if not isinstance(table, self.db.Table):
3041 table = self.db[table]
3042 if not query:
3043 query = table.id > 0
3044 if not fields:
3045 fields = [field for field in table if field.readable]
3046 rows = self.db(query).select(*fields, **dict(orderby=orderby,
3047 limitby=limitby))
3048 if not rows:
3049 return None
3050 if not 'upload' in attr:
3051 attr['upload'] = self.url('download')
3052 if not request.extension in ('html','load'):
3053 return rows.as_list()
3054 if not headers:
3055 headers = dict((str(k),k.label) for k in table)
3056 return SQLTABLE(rows, headers=headers, **attr)
3057
3064
3065 - def get_query(self, field, op, value, refsearch=False):
3066 try:
3067 if refsearch: format = self.get_format(field)
3068 if op == 'equals':
3069 if not refsearch:
3070 return field == value
3071 else:
3072 return lambda row: row[field.name][format] == value
3073 elif op == 'not equal':
3074 if not refsearch:
3075 return field != value
3076 else:
3077 return lambda row: row[field.name][format] != value
3078 elif op == 'greater than':
3079 if not refsearch:
3080 return field > value
3081 else:
3082 return lambda row: row[field.name][format] > value
3083 elif op == 'less than':
3084 if not refsearch:
3085 return field < value
3086 else:
3087 return lambda row: row[field.name][format] < value
3088 elif op == 'starts with':
3089 if not refsearch:
3090 return field.like(value+'%')
3091 else:
3092 return lambda row: str(row[field.name][format]).startswith(value)
3093 elif op == 'ends with':
3094 if not refsearch:
3095 return field.like('%'+value)
3096 else:
3097 return lambda row: str(row[field.name][format]).endswith(value)
3098 elif op == 'contains':
3099 if not refsearch:
3100 return field.like('%'+value+'%')
3101 else:
3102 return lambda row: value in row[field.name][format]
3103 except:
3104 return None
3105
3106
3107 - def search(self, *tables, **args):
3108 """
3109 Creates a search form and its results for a table
3110 Example usage:
3111 form, results = crud.search(db.test,
3112 queries = ['equals', 'not equal', 'contains'],
3113 query_labels={'equals':'Equals',
3114 'not equal':'Not equal'},
3115 fields = [db.test.id, db.test.children],
3116 field_labels = {'id':'ID','children':'Children'},
3117 zero='Please choose',
3118 query = (db.test.id > 0)&(db.test.id != 3) )
3119 """
3120 table = tables[0]
3121 fields = args.get('fields', table.fields)
3122 request = self.environment.request
3123 db = self.db
3124 if not (isinstance(table, db.Table) or table in db.tables):
3125 raise HTTP(404)
3126 tbl = TABLE()
3127 selected = []; refsearch = []; results = []
3128 ops = args.get('queries', [])
3129 zero = args.get('zero', '')
3130 if not ops:
3131 ops = ['equals', 'not equal', 'greater than',
3132 'less than', 'starts with',
3133 'ends with', 'contains']
3134 ops.insert(0,zero)
3135 query_labels = args.get('query_labels', {})
3136 query = args.get('query',table.id > 0)
3137 field_labels = args.get('field_labels',{})
3138 for field in fields:
3139 field = table[field]
3140 if not field.readable: continue
3141 fieldname = field.name
3142 chkval = request.vars.get('chk' + fieldname, None)
3143 txtval = request.vars.get('txt' + fieldname, None)
3144 opval = request.vars.get('op' + fieldname, None)
3145 row = TR(TD(INPUT(_type = "checkbox", _name = "chk" + fieldname,
3146 _disabled = (field.type == 'id'),
3147 value = (field.type == 'id' or chkval == 'on'))),
3148 TD(field_labels.get(fieldname,field.label)),
3149 TD(SELECT([OPTION(query_labels.get(op,op),
3150 _value=op) for op in ops],
3151 _name = "op" + fieldname,
3152 value = opval)),
3153 TD(INPUT(_type = "text", _name = "txt" + fieldname,
3154 _value = txtval, _id='txt' + fieldname,
3155 _class = str(field.type))))
3156 tbl.append(row)
3157 if request.post_vars and (chkval or field.type=='id'):
3158 if txtval and opval != '':
3159 if field.type[0:10] == 'reference ':
3160 refsearch.append(self.get_query(field,
3161 opval, txtval, refsearch=True))
3162 else:
3163 value, error = field.validate(txtval)
3164 if not error:
3165
3166 query &= self.get_query(field, opval, value)
3167 else:
3168 row[3].append(DIV(error,_class='error'))
3169 selected.append(field)
3170 form = FORM(tbl,INPUT(_type="submit"))
3171 if selected:
3172 try:
3173 results = db(query).select(*selected)
3174 for r in refsearch:
3175 results = results.find(r)
3176 except:
3177 results = None
3178 return form, results
3179
3180
3181 urllib2.install_opener(urllib2.build_opener(urllib2.HTTPCookieProcessor()))
3182
3183 -def fetch(url, data=None, headers={},
3184 cookie=Cookie.SimpleCookie(),
3185 user_agent='Mozilla/5.0'):
3186 if data != None:
3187 data = urllib.urlencode(data)
3188 if user_agent: headers['User-agent'] = user_agent
3189 headers['Cookie'] = ' '.join(['%s=%s;'%(c.key,c.value) for c in cookie.values()])
3190 try:
3191 from google.appengine.api import urlfetch
3192 except ImportError:
3193 req = urllib2.Request(url, data, headers)
3194 html = urllib2.urlopen(req).read()
3195 else:
3196 method = ((data==None) and urlfetch.GET) or urlfetch.POST
3197 while url is not None:
3198 response = urlfetch.fetch(url=url, payload=data,
3199 method=method, headers=headers,
3200 allow_truncated=False,follow_redirects=False,
3201 deadline=10)
3202
3203 data = None
3204 method = urlfetch.GET
3205
3206 cookie.load(response.headers.get('set-cookie', ''))
3207 url = response.headers.get('location')
3208 html = response.content
3209 return html
3210
3211 regex_geocode = \
3212 re.compile('\<coordinates\>(?P<la>[^,]*),(?P<lo>[^,]*).*?\</coordinates\>')
3213
3214
3216 try:
3217 a = urllib.quote(address)
3218 txt = fetch('http://maps.google.com/maps/geo?q=%s&output=xml'
3219 % a)
3220 item = regex_geocode.search(txt)
3221 (la, lo) = (float(item.group('la')), float(item.group('lo')))
3222 return (la, lo)
3223 except:
3224 return (0.0, 0.0)
3225
3226
3228 c = f.func_code.co_argcount
3229 n = f.func_code.co_varnames[:c]
3230 b = dict([(k, v) for k, v in b.items() if k in n])
3231 if len(b) == c:
3232 return f(**b)
3233 elif len(a) >= c:
3234 return f(*a[:c])
3235 raise HTTP(404, "Object does not exist")
3236
3237
3239
3241 self.environment = environment
3242 self.run_procedures = {}
3243 self.csv_procedures = {}
3244 self.xml_procedures = {}
3245 self.rss_procedures = {}
3246 self.json_procedures = {}
3247 self.jsonrpc_procedures = {}
3248 self.xmlrpc_procedures = {}
3249 self.amfrpc_procedures = {}
3250 self.amfrpc3_procedures = {}
3251 self.soap_procedures = {}
3252
3254 """
3255 example::
3256
3257 service = Service(globals())
3258 @service.run
3259 def myfunction(a, b):
3260 return a + b
3261 def call():
3262 return service()
3263
3264 Then call it with::
3265
3266 wget http://..../app/default/call/run/myfunction?a=3&b=4
3267
3268 """
3269 self.run_procedures[f.__name__] = f
3270 return f
3271
3273 """
3274 example::
3275
3276 service = Service(globals())
3277 @service.csv
3278 def myfunction(a, b):
3279 return a + b
3280 def call():
3281 return service()
3282
3283 Then call it with::
3284
3285 wget http://..../app/default/call/csv/myfunction?a=3&b=4
3286
3287 """
3288 self.run_procedures[f.__name__] = f
3289 return f
3290
3292 """
3293 example::
3294
3295 service = Service(globals())
3296 @service.xml
3297 def myfunction(a, b):
3298 return a + b
3299 def call():
3300 return service()
3301
3302 Then call it with::
3303
3304 wget http://..../app/default/call/xml/myfunction?a=3&b=4
3305
3306 """
3307 self.run_procedures[f.__name__] = f
3308 return f
3309
3311 """
3312 example::
3313
3314 service = Service(globals())
3315 @service.rss
3316 def myfunction():
3317 return dict(title=..., link=..., description=...,
3318 created_on=..., entries=[dict(title=..., link=...,
3319 description=..., created_on=...])
3320 def call():
3321 return service()
3322
3323 Then call it with::
3324
3325 wget http://..../app/default/call/rss/myfunction
3326
3327 """
3328 self.rss_procedures[f.__name__] = f
3329 return f
3330
3331 - def json(self, f):
3332 """
3333 example::
3334
3335 service = Service(globals())
3336 @service.json
3337 def myfunction(a, b):
3338 return [{a: b}]
3339 def call():
3340 return service()
3341
3342 Then call it with::
3343
3344 wget http://..../app/default/call/json/myfunction?a=hello&b=world
3345
3346 """
3347 self.json_procedures[f.__name__] = f
3348 return f
3349
3351 """
3352 example::
3353
3354 service = Service(globals())
3355 @service.jsonrpc
3356 def myfunction(a, b):
3357 return a + b
3358 def call():
3359 return service()
3360
3361 Then call it with::
3362
3363 wget http://..../app/default/call/jsonrpc/myfunction?a=hello&b=world
3364
3365 """
3366 self.jsonrpc_procedures[f.__name__] = f
3367 return f
3368
3370 """
3371 example::
3372
3373 service = Service(globals())
3374 @service.xmlrpc
3375 def myfunction(a, b):
3376 return a + b
3377 def call():
3378 return service()
3379
3380 The call it with::
3381
3382 wget http://..../app/default/call/xmlrpc/myfunction?a=hello&b=world
3383
3384 """
3385 self.xmlrpc_procedures[f.__name__] = f
3386 return f
3387
3389 """
3390 example::
3391
3392 service = Service(globals())
3393 @service.amfrpc
3394 def myfunction(a, b):
3395 return a + b
3396 def call():
3397 return service()
3398
3399 The call it with::
3400
3401 wget http://..../app/default/call/amfrpc/myfunction?a=hello&b=world
3402
3403 """
3404 self.amfrpc_procedures[f.__name__] = f
3405 return f
3406
3407 - def amfrpc3(self, domain='default'):
3408 """
3409 example::
3410
3411 service = Service(globals())
3412 @service.amfrpc3('domain')
3413 def myfunction(a, b):
3414 return a + b
3415 def call():
3416 return service()
3417
3418 The call it with::
3419
3420 wget http://..../app/default/call/amfrpc3/myfunction?a=hello&b=world
3421
3422 """
3423 if not isinstance(domain, str):
3424 raise SyntaxError, "AMF3 requires a domain for function"
3425
3426 def _amfrpc3(f):
3427 if domain:
3428 self.amfrpc3_procedures[domain+'.'+f.__name__] = f
3429 else:
3430 self.amfrpc3_procedures[f.__name__] = f
3431 return f
3432 return _amfrpc3
3433
3434 - def soap(self, name=None, returns=None, args=None,doc=None):
3435 """
3436 example::
3437
3438 service = Service(globals())
3439 @service.soap('MyFunction',returns={'result':int},args={'a':int,'b':int,})
3440 def myfunction(a, b):
3441 return a + b
3442 def call():
3443 return service()
3444
3445 The call it with::
3446
3447 from gluon.contrib.pysimplesoap.client import SoapClient
3448 client = SoapClient(wsdl="http://..../app/default/call/soap?WSDL")
3449 response = client.MyFunction(a=1,b=2)
3450 return response['result']
3451
3452 Exposes online generated documentation and xml example messages at:
3453 - http://..../app/default/call/soap
3454 """
3455
3456 def _soap(f):
3457 self.soap_procedures[name or f.__name__] = f, returns, args, doc
3458 return f
3459 return _soap
3460
3462 request = self.environment['request']
3463 if not args:
3464 args = request.args
3465 if args and args[0] in self.run_procedures:
3466 return str(universal_caller(self.run_procedures[args[0]],
3467 *args[1:], **dict(request.vars)))
3468 self.error()
3469
3471 request = self.environment['request']
3472 response = self.environment['response']
3473 response.headers['Content-Type'] = 'text/x-csv'
3474 if not args:
3475 args = request.args
3476
3477 def none_exception(value):
3478 if isinstance(value, unicode):
3479 return value.encode('utf8')
3480 if hasattr(value, 'isoformat'):
3481 return value.isoformat()[:19].replace('T', ' ')
3482 if value == None:
3483 return '<NULL>'
3484 return value
3485 if args and args[0] in self.run_procedures:
3486 r = universal_caller(self.run_procedures[args[0]],
3487 *args[1:], **dict(request.vars))
3488 s = cStringIO.StringIO()
3489 if hasattr(r, 'export_to_csv_file'):
3490 r.export_to_csv_file(s)
3491 elif r and isinstance(r[0], (dict, Storage)):
3492 import csv
3493 writer = csv.writer(s)
3494 writer.writerow(r[0].keys())
3495 for line in r:
3496 writer.writerow([none_exception(v) \
3497 for v in line.values()])
3498 else:
3499 import csv
3500 writer = csv.writer(s)
3501 for line in r:
3502 writer.writerow(line)
3503 return s.getvalue()
3504 self.error()
3505
3507 request = self.environment['request']
3508 response = self.environment['response']
3509 response.headers['Content-Type'] = 'text/xml'
3510 if not args:
3511 args = request.args
3512 if args and args[0] in self.run_procedures:
3513 s = universal_caller(self.run_procedures[args[0]],
3514 *args[1:], **dict(request.vars))
3515 if hasattr(s, 'as_list'):
3516 s = s.as_list()
3517 return serializers.xml(s)
3518 self.error()
3519
3521 request = self.environment['request']
3522 response = self.environment['response']
3523 if not args:
3524 args = request.args
3525 if args and args[0] in self.rss_procedures:
3526 feed = universal_caller(self.rss_procedures[args[0]],
3527 *args[1:], **dict(request.vars))
3528 else:
3529 self.error()
3530 response.headers['Content-Type'] = 'application/rss+xml'
3531 return serializers.rss(feed)
3532
3534 request = self.environment['request']
3535 response = self.environment['response']
3536 response.headers['Content-Type'] = 'text/x-json'
3537 if not args:
3538 args = request.args
3539 d = dict(request.vars)
3540 if args and args[0] in self.json_procedures:
3541 s = universal_caller(self.json_procedures[args[0]],*args[1:],**d)
3542 if hasattr(s, 'as_list'):
3543 s = s.as_list()
3544 return response.json(s)
3545 self.error()
3546
3549 self.code,self.info = code,info
3550
3552 import contrib.simplejson as simplejson
3553 def return_response(id, result):
3554 return simplejson.dumps({'version': '1.1',
3555 'id': id, 'result': result, 'error': None})
3556
3557 def return_error(id, code, message):
3558 return simplejson.dumps({'id': id,
3559 'version': '1.1',
3560 'error': {'name': 'JSONRPCError',
3561 'code': code, 'message': message}
3562 })
3563
3564 request = self.environment['request']
3565 methods = self.jsonrpc_procedures
3566 data = simplejson.loads(request.body.read())
3567 id, method, params = data['id'], data['method'], data.get('params','')
3568 if not method in methods:
3569 return return_error(id, 100, 'method "%s" does not exist' % method)
3570 try:
3571 s = methods[method](*params)
3572 if hasattr(s, 'as_list'):
3573 s = s.as_list()
3574 return return_response(id, s)
3575 except Service.JsonRpcException, e:
3576 return return_error(id, e.code, e.info)
3577 except BaseException:
3578 etype, eval, etb = sys.exc_info()
3579 return return_error(id, 100, '%s: %s' % (etype.__name__, eval))
3580 except:
3581 etype, eval, etb = sys.exc_info()
3582 return return_error(id, 100, 'Exception %s: %s' % (etype, eval))
3583
3585 request = self.environment['request']
3586 response = self.environment['response']
3587 services = self.xmlrpc_procedures.values()
3588 return response.xmlrpc(request, services)
3589
3591 try:
3592 import pyamf
3593 import pyamf.remoting.gateway
3594 except:
3595 return "pyamf not installed or not in Python sys.path"
3596 request = self.environment['request']
3597 response = self.environment['response']
3598 if version == 3:
3599 services = self.amfrpc3_procedures
3600 base_gateway = pyamf.remoting.gateway.BaseGateway(services)
3601 pyamf_request = pyamf.remoting.decode(request.body)
3602 else:
3603 services = self.amfrpc_procedures
3604 base_gateway = pyamf.remoting.gateway.BaseGateway(services)
3605 context = pyamf.get_context(pyamf.AMF0)
3606 pyamf_request = pyamf.remoting.decode(request.body, context)
3607 pyamf_response = pyamf.remoting.Envelope(pyamf_request.amfVersion,
3608 pyamf_request.clientType)
3609 for name, message in pyamf_request:
3610 pyamf_response[name] = base_gateway.getProcessor(message)(message)
3611 response.headers['Content-Type'] = pyamf.remoting.CONTENT_TYPE
3612 if version==3:
3613 return pyamf.remoting.encode(pyamf_response).getvalue()
3614 else:
3615 return pyamf.remoting.encode(pyamf_response, context).getvalue()
3616
3618 try:
3619 from contrib.pysimplesoap.server import SoapDispatcher
3620 except:
3621 return "pysimplesoap not installed in contrib"
3622 request = self.environment['request']
3623 response = self.environment['response']
3624 procedures = self.soap_procedures
3625
3626 location = "%s://%s%s" % (
3627 request.env.wsgi_url_scheme,
3628 request.env.http_host,
3629 URL(r=request,f="call/soap",vars={}))
3630 namespace = 'namespace' in response and response.namespace or location
3631 documentation = response.description or ''
3632 dispatcher = SoapDispatcher(
3633 name = response.title,
3634 location = location,
3635 action = location,
3636 namespace = namespace,
3637 prefix='pys',
3638 documentation = documentation,
3639 ns = True)
3640 for method, (function, returns, args, doc) in procedures.items():
3641 dispatcher.register_function(method, function, returns, args, doc)
3642 if request.env.request_method == 'POST':
3643
3644 response.headers['Content-Type'] = 'text/xml'
3645 return dispatcher.dispatch(request.body.read())
3646 elif 'WSDL' in request.vars:
3647
3648 response.headers['Content-Type'] = 'text/xml'
3649 return dispatcher.wsdl()
3650 elif 'op' in request.vars:
3651
3652 response.headers['Content-Type'] = 'text/html'
3653 method = request.vars['op']
3654 sample_req_xml, sample_res_xml, doc = dispatcher.help(method)
3655 body = [H1("Welcome to Web2Py SOAP webservice gateway"),
3656 A("See all webservice operations",
3657 _href=URL(r=request,f="call/soap",vars={})),
3658 H2(method),
3659 P(doc),
3660 UL(LI("Location: %s" % dispatcher.location),
3661 LI("Namespace: %s" % dispatcher.namespace),
3662 LI("SoapAction: %s" % dispatcher.action),
3663 ),
3664 H3("Sample SOAP XML Request Message:"),
3665 CODE(sample_req_xml,language="xml"),
3666 H3("Sample SOAP XML Response Message:"),
3667 CODE(sample_res_xml,language="xml"),
3668 ]
3669 return {'body': body}
3670 else:
3671
3672 response.headers['Content-Type'] = 'text/html'
3673 body = [H1("Welcome to Web2Py SOAP webservice gateway"),
3674 P(response.description),
3675 P("The following operations are available"),
3676 A("See WSDL for webservice description",
3677 _href=URL(r=request,f="call/soap",vars={"WSDL":None})),
3678 UL([LI(A("%s: %s" % (method, doc or ''),
3679 _href=URL(r=request,f="call/soap",vars={'op': method})))
3680 for method, doc in dispatcher.list_methods()]),
3681 ]
3682 return {'body': body}
3683
3685 """
3686 register services with:
3687 service = Service(globals())
3688 @service.run
3689 @service.rss
3690 @service.json
3691 @service.jsonrpc
3692 @service.xmlrpc
3693 @service.jsonrpc
3694 @service.amfrpc
3695 @service.amfrpc3('domain')
3696 @service.soap('Method', returns={'Result':int}, args={'a':int,'b':int,})
3697
3698 expose services with
3699
3700 def call(): return service()
3701
3702 call services with
3703 http://..../app/default/call/run?[parameters]
3704 http://..../app/default/call/rss?[parameters]
3705 http://..../app/default/call/json?[parameters]
3706 http://..../app/default/call/jsonrpc
3707 http://..../app/default/call/xmlrpc
3708 http://..../app/default/call/amfrpc
3709 http://..../app/default/call/amfrpc3
3710 http://..../app/default/call/soap
3711 """
3712
3713 request = self.environment['request']
3714 if len(request.args) < 1:
3715 raise HTTP(404, "Not Found")
3716 arg0 = request.args(0)
3717 if arg0 == 'run':
3718 return self.serve_run(request.args[1:])
3719 elif arg0 == 'rss':
3720 return self.serve_rss(request.args[1:])
3721 elif arg0 == 'csv':
3722 return self.serve_csv(request.args[1:])
3723 elif arg0 == 'xml':
3724 return self.serve_xml(request.args[1:])
3725 elif arg0 == 'json':
3726 return self.serve_json(request.args[1:])
3727 elif arg0 == 'jsonrpc':
3728 return self.serve_jsonrpc()
3729 elif arg0 == 'xmlrpc':
3730 return self.serve_xmlrpc()
3731 elif arg0 == 'amfrpc':
3732 return self.serve_amfrpc()
3733 elif arg0 == 'amfrpc3':
3734 return self.serve_amfrpc(3)
3735 elif arg0 == 'soap':
3736 return self.serve_soap()
3737 else:
3738 self.error()
3739
3741 raise HTTP(404, "Object does not exist")
3742
3743
3745 """
3746 Executes a task on completion of the called action. For example:
3747
3748 from gluon.tools import completion
3749 @completion(lambda d: logging.info(repr(d)))
3750 def index():
3751 return dict(message='hello')
3752
3753 It logs the output of the function every time input is called.
3754 The argument of completion is executed in a new thread.
3755 """
3756 def _completion(f):
3757 def __completion(*a,**b):
3758 d = None
3759 try:
3760 d = f(*a,**b)
3761 return d
3762 finally:
3763 thread.start_new_thread(callback,(d,))
3764 return __completion
3765 return _completion
3766
3768 try:
3769 dt = datetime.datetime.now() - d
3770 except:
3771 return ''
3772 if dt.days >= 2*365:
3773 return T('%d years ago') % int(dt.days / 365)
3774 elif dt.days >= 365:
3775 return T('1 year ago')
3776 elif dt.days >= 60:
3777 return T('%d months ago') % int(dt.days / 30)
3778 elif dt.days > 21:
3779 return T('1 month ago')
3780 elif dt.days >= 14:
3781 return T('%d weeks ago') % int(dt.days / 7)
3782 elif dt.days >= 7:
3783 return T('1 week ago')
3784 elif dt.days > 1:
3785 return T('%d days ago') % dt.days
3786 elif dt.days == 1:
3787 return T('1 day ago')
3788 elif dt.seconds >= 2*60*60:
3789 return T('%d hours ago') % int(dt.seconds / 3600)
3790 elif dt.seconds >= 60*60:
3791 return T('1 hour ago')
3792 elif dt.seconds >= 2*60:
3793 return T('%d minutes ago') % int(dt.seconds / 60)
3794 elif dt.seconds >= 60:
3795 return T('1 minute ago')
3796 elif dt.seconds > 1:
3797 return T('%d seconds ago') % dt.seconds
3798 elif dt.seconds == 1:
3799 return T('1 second ago')
3800 else:
3801 return T('now')
3802
3811 lock1=thread.allocate_lock()
3812 lock2=thread.allocate_lock()
3813 lock1.acquire()
3814 thread.start_new_thread(f,())
3815 a=PluginManager()
3816 a.x=5
3817 lock1.release()
3818 lock2.acquire()
3819 return a.x
3820
3822 """
3823
3824 Plugin Manager is similar to a storage object but it is a single level singleton
3825 this means that multiple instances within the same thread share the same attributes
3826 Its constructor is also special. The first argument is the name of the plugin you are defining.
3827 The named arguments are parameters needed by the plugin with default values.
3828 If the parameters were previous defined, the old values are used.
3829
3830 For example:
3831
3832 ### in some general configuration file:
3833 >>> plugins = PluginManager()
3834 >>> plugins.me.param1=3
3835
3836 ### within the plugin model
3837 >>> _ = PluginManager('me',param1=5,param2=6,param3=7)
3838
3839 ### where the plugin is used
3840 >>> print plugins.me.param1
3841 3
3842 >>> print plugins.me.param2
3843 6
3844 >>> plugins.me.param3 = 8
3845 >>> print plugins.me.param3
3846 8
3847
3848 Here are some tests:
3849
3850 >>> a=PluginManager()
3851 >>> a.x=6
3852 >>> b=PluginManager('check')
3853 >>> print b.x
3854 6
3855 >>> b=PluginManager() # reset settings
3856 >>> print b.x
3857 <Storage {}>
3858 >>> b.x=7
3859 >>> print a.x
3860 7
3861 >>> a.y.z=8
3862 >>> print b.y.z
3863 8
3864 >>> test_thread_separation()
3865 5
3866 >>> plugins=PluginManager('me',db='mydb')
3867 >>> print plugins.me.db
3868 mydb
3869 >>> print 'me' in plugins
3870 True
3871 >>> print plugins.me.installed
3872 True
3873 """
3874 instances = {}
3888 - def __init__(self,plugin=None,**defaults):
3895 if not key in self.__dict__:
3896 self.__dict__[key] = Storage()
3897 return self.__dict__[key]
3899 return self.__dict__.keys()
3901 return key in self.__dict__
3902
3903 if __name__ == '__main__':
3904 import doctest
3905 doctest.testmod()
3906