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)\
810 > request.now:
811 self.user = auth.user
812 auth.last_visit = request.now
813 else:
814 self.user = None
815 session.auth = None
816 self.settings = Settings()
817
818
819
820
821
822 self.settings.hideerror = False
823 self.settings.actions_disabled = []
824 self.settings.reset_password_requires_verification = False
825 self.settings.registration_requires_verification = False
826 self.settings.registration_requires_approval = False
827 self.settings.alternate_requires_registration = False
828 self.settings.create_user_groups = True
829
830 self.settings.controller = controller
831 self.settings.login_url = self.url('user', args='login')
832 self.settings.logged_url = self.url('user', args='profile')
833 self.settings.download_url = self.url('download')
834 self.settings.mailer = None
835 self.settings.login_captcha = None
836 self.settings.register_captcha = None
837 self.settings.retrieve_username_captcha = None
838 self.settings.retrieve_password_captcha = None
839 self.settings.captcha = None
840 self.settings.expiration = 3600
841 self.settings.long_expiration = 3600*30*24
842 self.settings.remember_me_form = True
843 self.settings.allow_basic_login = False
844 self.settings.allow_basic_login_only = False
845 self.settings.on_failed_authorization = \
846 self.url('user',args='not_authorized')
847
848 self.settings.on_failed_authentication = lambda x: redirect(x)
849
850 self.settings.formstyle = 'table3cols'
851
852
853
854 self.settings.password_field = 'password'
855 self.settings.table_user_name = 'auth_user'
856 self.settings.table_group_name = 'auth_group'
857 self.settings.table_membership_name = 'auth_membership'
858 self.settings.table_permission_name = 'auth_permission'
859 self.settings.table_event_name = 'auth_event'
860
861
862
863 self.settings.table_user = None
864 self.settings.table_group = None
865 self.settings.table_membership = None
866 self.settings.table_permission = None
867 self.settings.table_event = None
868
869
870
871 self.settings.showid = False
872
873
874
875 self.settings.login_next = self.url('index')
876 self.settings.login_onvalidation = []
877 self.settings.login_onaccept = []
878 self.settings.login_methods = [self]
879 self.settings.login_form = self
880 self.settings.login_email_validate = True
881 self.settings.login_userfield = None
882
883 self.settings.logout_next = self.url('index')
884 self.settings.logout_onlogout = None
885
886 self.settings.register_next = self.url('index')
887 self.settings.register_onvalidation = []
888 self.settings.register_onaccept = []
889 self.settings.register_fields = None
890
891 self.settings.verify_email_next = self.url('user', args='login')
892 self.settings.verify_email_onaccept = []
893
894 self.settings.profile_next = self.url('index')
895 self.settings.profile_onvalidation = []
896 self.settings.profile_onaccept = []
897 self.settings.profile_fields = None
898 self.settings.retrieve_username_next = self.url('index')
899 self.settings.retrieve_password_next = self.url('index')
900 self.settings.request_reset_password_next = self.url('user', args='login')
901 self.settings.reset_password_next = self.url('user', args='login')
902 self.settings.change_password_next = self.url('index')
903
904 self.settings.retrieve_password_onvalidation = []
905 self.settings.reset_password_onvalidation = []
906
907 self.settings.hmac_key = None
908 self.settings.lock_keys = True
909
910
911
912 self.messages = Messages(self.environment.T)
913 self.messages.submit_button = 'Submit'
914 self.messages.verify_password = 'Verify Password'
915 self.messages.delete_label = 'Check to delete:'
916 self.messages.function_disabled = 'Function disabled'
917 self.messages.access_denied = 'Insufficient privileges'
918 self.messages.registration_verifying = 'Registration needs verification'
919 self.messages.registration_pending = 'Registration is pending approval'
920 self.messages.login_disabled = 'Login disabled by administrator'
921 self.messages.logged_in = 'Logged in'
922 self.messages.email_sent = 'Email sent'
923 self.messages.unable_to_send_email = 'Unable to send email'
924 self.messages.email_verified = 'Email verified'
925 self.messages.logged_out = 'Logged out'
926 self.messages.registration_successful = 'Registration successful'
927 self.messages.invalid_email = 'Invalid email'
928 self.messages.unable_send_email = 'Unable to send email'
929 self.messages.invalid_login = 'Invalid login'
930 self.messages.invalid_user = 'Invalid user'
931 self.messages.invalid_password = 'Invalid password'
932 self.messages.is_empty = "Cannot be empty"
933 self.messages.mismatched_password = "Password fields don't match"
934 self.messages.verify_email = \
935 'Click on the link http://...verify_email/%(key)s to verify your email'
936 self.messages.verify_email_subject = 'Email verification'
937 self.messages.username_sent = 'Your username was emailed to you'
938 self.messages.new_password_sent = 'A new password was emailed to you'
939 self.messages.password_changed = 'Password changed'
940 self.messages.retrieve_username = 'Your username is: %(username)s'
941 self.messages.retrieve_username_subject = 'Username retrieve'
942 self.messages.retrieve_password = 'Your password is: %(password)s'
943 self.messages.retrieve_password_subject = 'Password retrieve'
944 self.messages.reset_password = \
945 'Click on the link http://...reset_password/%(key)s to reset your password'
946 self.messages.reset_password_subject = 'Password reset'
947 self.messages.invalid_reset_password = 'Invalid reset password'
948 self.messages.profile_updated = 'Profile updated'
949 self.messages.new_password = 'New password'
950 self.messages.old_password = 'Old password'
951 self.messages.group_description = \
952 'Group uniquely assigned to user %(id)s'
953
954 self.messages.register_log = 'User %(id)s Registered'
955 self.messages.login_log = 'User %(id)s Logged-in'
956 self.messages.login_failed_log = None
957 self.messages.logout_log = 'User %(id)s Logged-out'
958 self.messages.profile_log = 'User %(id)s Profile updated'
959 self.messages.verify_email_log = 'User %(id)s Verification email sent'
960 self.messages.retrieve_username_log = 'User %(id)s Username retrieved'
961 self.messages.retrieve_password_log = 'User %(id)s Password retrieved'
962 self.messages.reset_password_log = 'User %(id)s Password reset'
963 self.messages.change_password_log = 'User %(id)s Password changed'
964 self.messages.add_group_log = 'Group %(group_id)s created'
965 self.messages.del_group_log = 'Group %(group_id)s deleted'
966 self.messages.add_membership_log = None
967 self.messages.del_membership_log = None
968 self.messages.has_membership_log = None
969 self.messages.add_permission_log = None
970 self.messages.del_permission_log = None
971 self.messages.has_permission_log = None
972 self.messages.impersonate_log = 'User %(id)s is impersonating %(other_id)s'
973
974 self.messages.label_first_name = 'First name'
975 self.messages.label_last_name = 'Last name'
976 self.messages.label_username = 'Username'
977 self.messages.label_email = 'E-mail'
978 self.messages.label_password = 'Password'
979 self.messages.label_registration_key = 'Registration key'
980 self.messages.label_reset_password_key = 'Reset Password key'
981 self.messages.label_registration_id = 'Registration identifier'
982 self.messages.label_role = 'Role'
983 self.messages.label_description = 'Description'
984 self.messages.label_user_id = 'User ID'
985 self.messages.label_group_id = 'Group ID'
986 self.messages.label_name = 'Name'
987 self.messages.label_table_name = 'Table name'
988 self.messages.label_record_id = 'Record ID'
989 self.messages.label_time_stamp = 'Timestamp'
990 self.messages.label_client_ip = 'Client IP'
991 self.messages.label_origin = 'Origin'
992 self.messages.label_remember_me = "Remember me (for 30 days)"
993 self.messages['T'] = self.environment.T
994 self.messages.verify_password_comment = 'please input your password again'
995 self.messages.lock_keys = True
996
997
998 response = self.environment.response
999 if auth and auth.remember:
1000
1001
1002
1003
1004
1005
1006 response.cookies[response.session_id_name]["expires"] = auth.expiration
1007
1009 "accessor for auth.user_id"
1010 return self.user and self.user.id or None
1011 user_id = property(_get_user_id, doc="user.id or None")
1012
1013 - def _HTTP(self, *a, **b):
1014 """
1015 only used in lambda: self._HTTP(404)
1016 """
1017
1018 raise HTTP(*a, **b)
1019
1021 """
1022 usage:
1023
1024 def authentication(): return dict(form=auth())
1025 """
1026
1027 request = self.environment.request
1028 args = request.args
1029 if not args:
1030 redirect(self.url(args='login',vars=request.vars))
1031 elif args[0] in self.settings.actions_disabled:
1032 raise HTTP(404)
1033 if args[0] == 'login':
1034 return self.login()
1035 elif args[0] == 'logout':
1036 return self.logout()
1037 elif args[0] == 'register':
1038 return self.register()
1039 elif args[0] == 'verify_email':
1040 return self.verify_email()
1041 elif args[0] == 'retrieve_username':
1042 return self.retrieve_username()
1043 elif args[0] == 'retrieve_password':
1044 return self.retrieve_password()
1045 elif args[0] == 'reset_password':
1046 return self.reset_password()
1047 elif args[0] == 'request_reset_password':
1048 return self.request_reset_password()
1049 elif args[0] == 'change_password':
1050 return self.change_password()
1051 elif args[0] == 'profile':
1052 return self.profile()
1053 elif args[0] == 'groups':
1054 return self.groups()
1055 elif args[0] == 'impersonate':
1056 return self.impersonate()
1057 elif args[0] == 'not_authorized':
1058 return self.not_authorized()
1059 else:
1060 raise HTTP(404)
1061
1062 - def navbar(self,prefix='Welcome',action=None):
1063 request = self.environment.request
1064 T = self.environment.T
1065 if isinstance(prefix,str):
1066 prefix = T(prefix)
1067 if not action:
1068 action=URL(request.application,request.controller,'user')
1069 if prefix:
1070 prefix = prefix.strip()+' '
1071 if self.user_id:
1072 logout=A(T('logout'),_href=action+'/logout')
1073 profile=A(T('profile'),_href=action+'/profile')
1074 password=A(T('password'),_href=action+'/change_password')
1075 bar = SPAN(prefix,self.user.first_name,' [ ', logout, ']',_class='auth_navbar')
1076 if not 'profile' in self.settings.actions_disabled:
1077 bar.insert(4, ' | ')
1078 bar.insert(5, profile)
1079 if not 'change_password' in self.settings.actions_disabled:
1080 bar.insert(-1, ' | ')
1081 bar.insert(-1, password)
1082 else:
1083 login=A(T('login'),_href=action+'/login')
1084 register=A(T('register'),_href=action+'/register')
1085 retrieve_username=A(T('forgot username?'),
1086 _href=action+'/retrieve_username')
1087 lost_password=A(T('lost password?'),
1088 _href=action+'/request_reset_password')
1089 bar = SPAN('[ ',login,' ]',_class='auth_navbar')
1090
1091 if not 'register' in self.settings.actions_disabled:
1092 bar.insert(2, ' | ')
1093 bar.insert(3, register)
1094 if 'username' in self.settings.table_user.fields() and \
1095 not 'retrieve_username' in self.settings.actions_disabled:
1096 bar.insert(-1, ' | ')
1097 bar.insert(-1, retrieve_username)
1098 if not 'request_reset_password' in self.settings.actions_disabled:
1099 bar.insert(-1, ' | ')
1100 bar.insert(-1, lost_password)
1101 return bar
1102
1104
1105 if type(migrate).__name__ == 'str':
1106 return (migrate + tablename + '.table')
1107 elif migrate == False:
1108 return False
1109 else:
1110 return True
1111
1112 - def define_tables(self, username=False, migrate=True, fake_migrate=False):
1113 """
1114 to be called unless tables are defined manually
1115
1116 usages::
1117
1118 # defines all needed tables and table files
1119 # 'myprefix_auth_user.table', ...
1120 auth.define_tables(migrate='myprefix_')
1121
1122 # defines all needed tables without migration/table files
1123 auth.define_tables(migrate=False)
1124
1125 """
1126
1127 db = self.db
1128 if not self.settings.table_user_name in db.tables:
1129 passfield = self.settings.password_field
1130 if username:
1131 table = db.define_table(
1132 self.settings.table_user_name,
1133 Field('first_name', length=128, default='',
1134 label=self.messages.label_first_name),
1135 Field('last_name', length=128, default='',
1136 label=self.messages.label_last_name),
1137 Field('username', length=128, default='',
1138 label=self.messages.label_username),
1139 Field('email', length=512, default='',
1140 label=self.messages.label_email),
1141 Field(passfield, 'password', length=512,
1142 readable=False, label=self.messages.label_password),
1143 Field('registration_key', length=512,
1144 writable=False, readable=False, default='',
1145 label=self.messages.label_registration_key),
1146 Field('reset_password_key', length=512,
1147 writable=False, readable=False, default='',
1148 label=self.messages.label_reset_password_key),
1149 Field('registration_id', length=512,
1150 writable=False, readable=False, default='',
1151 label=self.messages.label_registration_id),
1152 migrate=\
1153 self.__get_migrate(self.settings.table_user_name, migrate),
1154 fake_migrate=fake_migrate,
1155 format='%(username)s')
1156 table.username.requires = IS_NOT_IN_DB(db, table.username)
1157 else:
1158 table = db.define_table(
1159 self.settings.table_user_name,
1160 Field('first_name', length=128, default='',
1161 label=self.messages.label_first_name),
1162 Field('last_name', length=128, default='',
1163 label=self.messages.label_last_name),
1164 Field('email', length=512, default='',
1165 label=self.messages.label_email),
1166 Field(passfield, 'password', length=512,
1167 readable=False, label=self.messages.label_password),
1168 Field('registration_key', length=512,
1169 writable=False, readable=False, default='',
1170 label=self.messages.label_registration_key),
1171 Field('reset_password_key', length=512,
1172 writable=False, readable=False, default='',
1173 label=self.messages.label_reset_password_key),
1174 migrate=\
1175 self.__get_migrate(self.settings.table_user_name, migrate),
1176 fake_migrate=fake_migrate,
1177 format='%(first_name)s %(last_name)s (%(id)s)')
1178 table.first_name.requires = \
1179 IS_NOT_EMPTY(error_message=self.messages.is_empty)
1180 table.last_name.requires = \
1181 IS_NOT_EMPTY(error_message=self.messages.is_empty)
1182 table[passfield].requires = [CRYPT(key=self.settings.hmac_key)]
1183 table.email.requires = \
1184 [IS_EMAIL(error_message=self.messages.invalid_email),
1185 IS_NOT_IN_DB(db, table.email)]
1186 table.registration_key.default = ''
1187 self.settings.table_user = db[self.settings.table_user_name]
1188 if not self.settings.table_group_name in db.tables:
1189 table = db.define_table(
1190 self.settings.table_group_name,
1191 Field('role', length=512, default='',
1192 label=self.messages.label_role),
1193 Field('description', 'text',
1194 label=self.messages.label_description),
1195 migrate=self.__get_migrate(
1196 self.settings.table_group_name, migrate),
1197 fake_migrate=fake_migrate,
1198 format = '%(role)s (%(id)s)')
1199 table.role.requires = IS_NOT_IN_DB(db, '%s.role'
1200 % self.settings.table_group_name)
1201 self.settings.table_group = db[self.settings.table_group_name]
1202 if not self.settings.table_membership_name in db.tables:
1203 table = db.define_table(
1204 self.settings.table_membership_name,
1205 Field('user_id', self.settings.table_user,
1206 label=self.messages.label_user_id),
1207 Field('group_id', self.settings.table_group,
1208 label=self.messages.label_group_id),
1209 migrate=self.__get_migrate(
1210 self.settings.table_membership_name, migrate),
1211 fake_migrate=fake_migrate)
1212 table.user_id.requires = IS_IN_DB(db, '%s.id' %
1213 self.settings.table_user_name,
1214 '%(first_name)s %(last_name)s (%(id)s)')
1215 table.group_id.requires = IS_IN_DB(db, '%s.id' %
1216 self.settings.table_group_name,
1217 '%(role)s (%(id)s)')
1218 self.settings.table_membership = db[self.settings.table_membership_name]
1219 if not self.settings.table_permission_name in db.tables:
1220 table = db.define_table(
1221 self.settings.table_permission_name,
1222 Field('group_id', self.settings.table_group,
1223 label=self.messages.label_group_id),
1224 Field('name', default='default', length=512,
1225 label=self.messages.label_name),
1226 Field('table_name', length=512,
1227 label=self.messages.label_table_name),
1228 Field('record_id', 'integer',
1229 label=self.messages.label_record_id),
1230 migrate=self.__get_migrate(
1231 self.settings.table_permission_name, migrate),
1232 fake_migrate=fake_migrate)
1233 table.group_id.requires = IS_IN_DB(db, '%s.id' %
1234 self.settings.table_group_name,
1235 '%(role)s (%(id)s)')
1236 table.name.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1237 table.table_name.requires = IS_IN_SET(self.db.tables)
1238 table.record_id.requires = IS_INT_IN_RANGE(0, 10 ** 9)
1239 self.settings.table_permission = db[self.settings.table_permission_name]
1240 if not self.settings.table_event_name in db.tables:
1241 table = db.define_table(
1242 self.settings.table_event_name,
1243 Field('time_stamp', 'datetime',
1244 default=self.environment.request.now,
1245 label=self.messages.label_time_stamp),
1246 Field('client_ip',
1247 default=self.environment.request.client,
1248 label=self.messages.label_client_ip),
1249 Field('user_id', self.settings.table_user, default=None,
1250 label=self.messages.label_user_id),
1251 Field('origin', default='auth', length=512,
1252 label=self.messages.label_origin),
1253 Field('description', 'text', default='',
1254 label=self.messages.label_description),
1255 migrate=self.__get_migrate(
1256 self.settings.table_event_name, migrate),
1257 fake_migrate=fake_migrate)
1258 table.user_id.requires = IS_IN_DB(db, '%s.id' %
1259 self.settings.table_user_name,
1260 '%(first_name)s %(last_name)s (%(id)s)')
1261 table.origin.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1262 table.description.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1263 self.settings.table_event = db[self.settings.table_event_name]
1264 def lazy_user (auth = self): return auth.user_id
1265 now = self.environment.request.now
1266 self.signature = db.Table(self.db,'auth_signature',
1267 Field('is_active','boolean',default=True),
1268 Field('created_on','datetime',default=now,
1269 writable=False,readable=False),
1270 Field('created_by',self.settings.table_user,default=lazy_user,
1271 writable=False,readable=False),
1272 Field('modified_on','datetime',update=now,default=now,
1273 writable=False,readable=False),
1274 Field('modified_by',self.settings.table_user,
1275 default=lazy_user,update=lazy_user,
1276 writable=False,readable=False))
1277
1278
1279 - def log_event(self, description, origin='auth'):
1280 """
1281 usage::
1282
1283 auth.log_event(description='this happened', origin='auth')
1284 """
1285
1286 if self.is_logged_in():
1287 user_id = self.user.id
1288 else:
1289 user_id = None
1290 self.settings.table_event.insert(description=description,
1291 origin=origin, user_id=user_id)
1292
1294 """
1295 Used for alternate login methods:
1296 If the user exists already then password is updated.
1297 If the user doesn't yet exist, then they are created.
1298 """
1299 table_user = self.settings.table_user
1300 if 'registration_id' in table_user.fields() and 'registration_id' in keys:
1301 username = 'registration_id'
1302 elif 'username' in table_user.fields():
1303 username = 'username'
1304 elif 'email' in table_user.fields():
1305 username = 'email'
1306 else:
1307 raise SyntaxError, "user must have username or email"
1308 passfield = self.settings.password_field
1309 user = self.db(table_user[username] == keys[username]).select().first()
1310 if user:
1311 if passfield in keys and keys[passfield]:
1312 user.update_record(**{passfield: keys[passfield],
1313 'registration_key': ''})
1314 else:
1315 d = {username: keys[username],
1316 'first_name': keys.get('first_name', keys[username]),
1317 'last_name': keys.get('last_name', ''),
1318 'registration_key': ''}
1319 keys = dict([(k, v) for (k, v) in keys.items() \
1320 if k in table_user.fields])
1321 d.update(keys)
1322 user_id = table_user.insert(**d)
1323 user = self.user = table_user[user_id]
1324 if self.settings.create_user_groups:
1325 group_id = self.add_group("user_%s" % user_id)
1326 self.add_membership(group_id, user_id)
1327 return user
1328
1330 if not self.settings.allow_basic_login:
1331 return False
1332 basic = self.environment.request.env.http_authorization
1333 if not basic or not basic[:6].lower() == 'basic ':
1334 return False
1335 (username, password) = base64.b64decode(basic[6:]).split(':')
1336 return self.login_bare(username, password)
1337
1339 """
1340 logins user
1341 """
1342
1343 request = self.environment.request
1344 session = self.environment.session
1345 table_user = self.settings.table_user
1346 if self.settings.login_userfield:
1347 userfield = self.settings.login_userfield
1348 elif 'username' in table_user.fields:
1349 userfield = 'username'
1350 else:
1351 userfield = 'email'
1352 passfield = self.settings.password_field
1353 user = self.db(table_user[userfield] == username).select().first()
1354 password = table_user[passfield].validate(password)[0]
1355 if user:
1356 if not user.registration_key and user[passfield] == password:
1357 user = Storage(table_user._filter_fields(user, id=True))
1358 session.auth = Storage(user=user, last_visit=request.now,
1359 expiration=self.settings.expiration)
1360 self.user = user
1361 return user
1362 return False
1363
1371 """
1372 returns a login form
1373
1374 .. method:: Auth.login([next=DEFAULT [, onvalidation=DEFAULT
1375 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1376
1377 """
1378
1379 table_user = self.settings.table_user
1380 if self.settings.login_userfield:
1381 username = self.settings.login_userfield
1382 elif 'username' in table_user.fields:
1383 username = 'username'
1384 else:
1385 username = 'email'
1386 if 'username' in table_user.fields or not self.settings.login_email_validate:
1387 tmpvalidator = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1388 else:
1389 tmpvalidator = IS_EMAIL(error_message=self.messages.invalid_email)
1390 old_requires = table_user[username].requires
1391 table_user[username].requires = tmpvalidator
1392 request = self.environment.request
1393 response = self.environment.response
1394 session = self.environment.session
1395 passfield = self.settings.password_field
1396 if next == DEFAULT:
1397 next = request.get_vars._next \
1398 or request.post_vars._next \
1399 or self.settings.login_next
1400 if onvalidation == DEFAULT:
1401 onvalidation = self.settings.login_onvalidation
1402 if onaccept == DEFAULT:
1403 onaccept = self.settings.login_onaccept
1404 if log == DEFAULT:
1405 log = self.messages.login_log
1406
1407 user = None
1408
1409
1410 if self.settings.login_form == self:
1411 form = SQLFORM(
1412 table_user,
1413 fields=[username, passfield],
1414 hidden=dict(_next=next),
1415 showid=self.settings.showid,
1416 submit_button=self.messages.submit_button,
1417 delete_label=self.messages.delete_label,
1418 formstyle=self.settings.formstyle
1419 )
1420
1421 if self.settings.remember_me_form:
1422
1423 addrow(form,XML(" "),
1424 DIV(XML(" "),
1425 INPUT(_type='checkbox',
1426 _class='checkbox',
1427 _id="auth_user_remember",
1428 _name="remember",
1429 ),
1430 XML(" "),
1431 LABEL(
1432 self.messages.label_remember_me,
1433 _for="auth_user_remember",
1434 )),"",
1435 self.settings.formstyle,
1436 'auth_user_remember__row')
1437
1438 captcha = self.settings.login_captcha or \
1439 (self.settings.login_captcha!=False and self.settings.captcha)
1440 if captcha:
1441 addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle,'captcha__row')
1442 accepted_form = False
1443 if form.accepts(request, session,
1444 formname='login', dbio=False,
1445 onvalidation=onvalidation,
1446 hideerror=self.settings.hideerror):
1447 accepted_form = True
1448
1449 user = self.db(table_user[username] == form.vars[username]).select().first()
1450 if user:
1451
1452 temp_user = user
1453 if temp_user.registration_key == 'pending':
1454 response.flash = self.messages.registration_pending
1455 return form
1456 elif temp_user.registration_key in ('disabled','blocked'):
1457 response.flash = self.messages.login_disabled
1458 return form
1459 elif temp_user.registration_key!=None and temp_user.registration_key.strip():
1460 response.flash = \
1461 self.messages.registration_verifying
1462 return form
1463
1464 user = None
1465 for login_method in self.settings.login_methods:
1466 if login_method != self and \
1467 login_method(request.vars[username],
1468 request.vars[passfield]):
1469 if not self in self.settings.login_methods:
1470
1471 form.vars[passfield] = None
1472 user = self.get_or_create_user(form.vars)
1473 break
1474 if not user:
1475
1476 if self.settings.login_methods[0] == self:
1477
1478 if temp_user[passfield] == form.vars.get(passfield, ''):
1479
1480 user = temp_user
1481 else:
1482
1483 if not self.settings.alternate_requires_registration:
1484
1485 for login_method in self.settings.login_methods:
1486 if login_method != self and \
1487 login_method(request.vars[username],
1488 request.vars[passfield]):
1489 if not self in self.settings.login_methods:
1490
1491 form.vars[passfield] = None
1492 user = self.get_or_create_user(form.vars)
1493 break
1494 if not user:
1495 if self.settings.login_failed_log:
1496 self.log_event(self.settings.login_failed_log % request.post_vars)
1497
1498 session.flash = self.messages.invalid_login
1499 redirect(self.url(args=request.args,vars=request.get_vars))
1500
1501 else:
1502
1503 cas = self.settings.login_form
1504 cas_user = cas.get_user()
1505
1506 if cas_user:
1507 cas_user[passfield] = None
1508 user = self.get_or_create_user(cas_user)
1509 elif hasattr(cas,'login_form'):
1510 return cas.login_form()
1511 else:
1512
1513 next = self.url('user',args='login',vars=dict(_next=next))
1514 redirect(cas.login_url(next))
1515
1516
1517
1518 if user:
1519 user = Storage(table_user._filter_fields(user, id=True))
1520
1521 if request.vars.has_key("remember"):
1522
1523 session.auth = Storage(
1524 user = user,
1525 last_visit = request.now,
1526 expiration = self.settings.long_expiration,
1527 remember = True,
1528 )
1529 else:
1530
1531 session.auth = Storage(
1532 user = user,
1533 last_visit = request.now,
1534 expiration = self.settings.expiration,
1535 remember = False,
1536 )
1537
1538 self.user = user
1539 session.flash = self.messages.logged_in
1540 if log and self.user:
1541 self.log_event(log % self.user)
1542
1543
1544 if self.settings.login_form == self:
1545 if accepted_form:
1546 callback(onaccept,form)
1547 if isinstance(next, (list, tuple)):
1548
1549 next = next[0]
1550 if next and not next[0] == '/' and next[:4] != 'http':
1551 next = self.url(next.replace('[id]', str(form.vars.id)))
1552 redirect(next)
1553 table_user[username].requires = old_requires
1554 return form
1555 else:
1556 redirect(next)
1557
1559 """
1560 logout and redirects to login
1561
1562 .. method:: Auth.logout ([next=DEFAULT[, onlogout=DEFAULT[,
1563 log=DEFAULT]]])
1564
1565 """
1566
1567 if next == DEFAULT:
1568 next = self.settings.logout_next
1569 if onlogout == DEFAULT:
1570 onlogout = self.settings.logout_onlogout
1571 if onlogout:
1572 onlogout(self.user)
1573 if log == DEFAULT:
1574 log = self.messages.logout_log
1575 if log and self.user:
1576 self.log_event(log % self.user)
1577
1578 if self.settings.login_form != self:
1579 cas = self.settings.login_form
1580 cas_user = cas.get_user()
1581 if cas_user:
1582 next = cas.logout_url(next)
1583
1584 self.environment.session.auth = None
1585 self.environment.session.flash = self.messages.logged_out
1586 if next:
1587 redirect(next)
1588
1596 """
1597 returns a registration form
1598
1599 .. method:: Auth.register([next=DEFAULT [, onvalidation=DEFAULT
1600 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1601
1602 """
1603
1604 table_user = self.settings.table_user
1605 request = self.environment.request
1606 response = self.environment.response
1607 session = self.environment.session
1608 if self.is_logged_in():
1609 redirect(self.settings.logged_url)
1610 if next == DEFAULT:
1611 next = request.get_vars._next \
1612 or request.post_vars._next \
1613 or self.settings.register_next
1614 if onvalidation == DEFAULT:
1615 onvalidation = self.settings.register_onvalidation
1616 if onaccept == DEFAULT:
1617 onaccept = self.settings.register_onaccept
1618 if log == DEFAULT:
1619 log = self.messages.register_log
1620
1621 passfield = self.settings.password_field
1622 formstyle = self.settings.formstyle
1623 form = SQLFORM(table_user,
1624 fields = self.settings.register_fields,
1625 hidden=dict(_next=next),
1626 showid=self.settings.showid,
1627 submit_button=self.messages.submit_button,
1628 delete_label=self.messages.delete_label,
1629 formstyle=formstyle
1630 )
1631 for i, row in enumerate(form[0].components):
1632 item = row.element('input',_name=passfield)
1633 if item:
1634 form.custom.widget.password_two = \
1635 INPUT(_name="password_two", _type="password",
1636 requires=IS_EXPR('value==%s' % \
1637 repr(request.vars.get(passfield, None)),
1638 error_message=self.messages.mismatched_password))
1639
1640 addrow(form, self.messages.verify_password + ':',
1641 form.custom.widget.password_two,
1642 self.messages.verify_password_comment,
1643 formstyle,
1644 '%s_%s__row' % (table_user, 'password_two'),
1645 position=i+1)
1646 break
1647 captcha = self.settings.register_captcha or self.settings.captcha
1648 if captcha:
1649 addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row')
1650
1651 table_user.registration_key.default = key = web2py_uuid()
1652 if form.accepts(request, session, formname='register',
1653 onvalidation=onvalidation,hideerror=self.settings.hideerror):
1654 description = self.messages.group_description % form.vars
1655 if self.settings.create_user_groups:
1656 group_id = self.add_group("user_%s" % form.vars.id, description)
1657 self.add_membership(group_id, form.vars.id)
1658 if self.settings.registration_requires_verification:
1659 if not self.settings.mailer or \
1660 not self.settings.mailer.send(to=form.vars.email,
1661 subject=self.messages.verify_email_subject,
1662 message=self.messages.verify_email
1663 % dict(key=key)):
1664 self.db.rollback()
1665 response.flash = self.messages.unable_send_email
1666 return form
1667 session.flash = self.messages.email_sent
1668 elif self.settings.registration_requires_approval:
1669 table_user[form.vars.id] = dict(registration_key='pending')
1670 session.flash = self.messages.registration_pending
1671 else:
1672 table_user[form.vars.id] = dict(registration_key='')
1673 session.flash = self.messages.registration_successful
1674 table_user = self.settings.table_user
1675 if 'username' in table_user.fields:
1676 username = 'username'
1677 else:
1678 username = 'email'
1679 user = self.db(table_user[username] == form.vars[username]).select().first()
1680 user = Storage(table_user._filter_fields(user, id=True))
1681 session.auth = Storage(user=user, last_visit=request.now,
1682 expiration=self.settings.expiration)
1683 self.user = user
1684 session.flash = self.messages.logged_in
1685 if log:
1686 self.log_event(log % form.vars)
1687 callback(onaccept,form)
1688 if not next:
1689 next = self.url(args = request.args)
1690 elif isinstance(next, (list, tuple)):
1691 next = next[0]
1692 elif next and not next[0] == '/' and next[:4] != 'http':
1693 next = self.url(next.replace('[id]', str(form.vars.id)))
1694 redirect(next)
1695 return form
1696
1698 """
1699 checks if the user is logged in and returns True/False.
1700 if so user is in auth.user as well as in session.auth.user
1701 """
1702
1703 if self.user:
1704 return True
1705 return False
1706
1713 """
1714 action user to verify the registration email, XXXXXXXXXXXXXXXX
1715
1716 .. method:: Auth.verify_email([next=DEFAULT [, onvalidation=DEFAULT
1717 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1718
1719 """
1720
1721 key = self.environment.request.args[-1]
1722 table_user = self.settings.table_user
1723 user = self.db(table_user.registration_key == key).select().first()
1724 if not user:
1725 raise HTTP(404)
1726 if self.settings.registration_requires_approval:
1727 user.update_record(registration_key = 'pending')
1728 self.environment.session.flash = self.messages.registration_pending
1729 else:
1730 user.update_record(registration_key = '')
1731 self.environment.session.flash = self.messages.email_verified
1732 if log == DEFAULT:
1733 log = self.messages.verify_email_log
1734 if next == DEFAULT:
1735 next = self.settings.verify_email_next
1736 if onaccept == DEFAULT:
1737 onaccept = self.settings.verify_email_onaccept
1738 if log:
1739 self.log_event(log % user)
1740 callback(onaccept,user)
1741 redirect(next)
1742
1750 """
1751 returns a form to retrieve the user username
1752 (only if there is a username field)
1753
1754 .. method:: Auth.retrieve_username([next=DEFAULT
1755 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
1756
1757 """
1758
1759 table_user = self.settings.table_user
1760 if not 'username' in table_user.fields:
1761 raise HTTP(404)
1762 request = self.environment.request
1763 response = self.environment.response
1764 session = self.environment.session
1765 captcha = self.settings.retrieve_username_captcha or \
1766 (self.settings.retrieve_username_captcha!=False and self.settings.captcha)
1767 if not self.settings.mailer:
1768 response.flash = self.messages.function_disabled
1769 return ''
1770 if next == DEFAULT:
1771 next = request.get_vars._next \
1772 or request.post_vars._next \
1773 or self.settings.retrieve_username_next
1774 if onvalidation == DEFAULT:
1775 onvalidation = self.settings.retrieve_username_onvalidation
1776 if onaccept == DEFAULT:
1777 onaccept = self.settings.retrieve_username_onaccept
1778 if log == DEFAULT:
1779 log = self.messages.retrieve_username_log
1780 old_requires = table_user.email.requires
1781 table_user.email.requires = [IS_IN_DB(self.db, table_user.email,
1782 error_message=self.messages.invalid_email)]
1783 form = SQLFORM(table_user,
1784 fields=['email'],
1785 hidden=dict(_next=next),
1786 showid=self.settings.showid,
1787 submit_button=self.messages.submit_button,
1788 delete_label=self.messages.delete_label,
1789 formstyle=self.settings.formstyle
1790 )
1791 if captcha:
1792 addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row')
1793
1794 if form.accepts(request, session,
1795 formname='retrieve_username', dbio=False,
1796 onvalidation=onvalidation,hideerror=self.settings.hideerror):
1797 user = self.db(table_user.email == form.vars.email).select().first()
1798 if not user:
1799 self.environment.session.flash = \
1800 self.messages.invalid_email
1801 redirect(self.url(args=request.args))
1802 username = user.username
1803 self.settings.mailer.send(to=form.vars.email,
1804 subject=self.messages.retrieve_username_subject,
1805 message=self.messages.retrieve_username
1806 % dict(username=username))
1807 session.flash = self.messages.email_sent
1808 if log:
1809 self.log_event(log % user)
1810 callback(onaccept,form)
1811 if not next:
1812 next = self.url(args = request.args)
1813 elif isinstance(next, (list, tuple)):
1814 next = next[0]
1815 elif next and not next[0] == '/' and next[:4] != 'http':
1816 next = self.url(next.replace('[id]', str(form.vars.id)))
1817 redirect(next)
1818 table_user.email.requires = old_requires
1819 return form
1820
1822 import string
1823 import random
1824 password = ''
1825 specials=r'!#$*'
1826 for i in range(0,3):
1827 password += random.choice(string.lowercase)
1828 password += random.choice(string.uppercase)
1829 password += random.choice(string.digits)
1830 password += random.choice(specials)
1831 return ''.join(random.sample(password,len(password)))
1832
1840 """
1841 returns a form to reset the user password (deprecated)
1842
1843 .. method:: Auth.reset_password_deprecated([next=DEFAULT
1844 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
1845
1846 """
1847
1848 table_user = self.settings.table_user
1849 request = self.environment.request
1850 response = self.environment.response
1851 session = self.environment.session
1852 if not self.settings.mailer:
1853 response.flash = self.messages.function_disabled
1854 return ''
1855 if next == DEFAULT:
1856 next = request.get_vars._next \
1857 or request.post_vars._next \
1858 or self.settings.retrieve_password_next
1859 if onvalidation == DEFAULT:
1860 onvalidation = self.settings.retrieve_password_onvalidation
1861 if onaccept == DEFAULT:
1862 onaccept = self.settings.retrieve_password_onaccept
1863 if log == DEFAULT:
1864 log = self.messages.retrieve_password_log
1865 old_requires = table_user.email.requires
1866 table_user.email.requires = [IS_IN_DB(self.db, table_user.email,
1867 error_message=self.messages.invalid_email)]
1868 form = SQLFORM(table_user,
1869 fields=['email'],
1870 hidden=dict(_next=next),
1871 showid=self.settings.showid,
1872 submit_button=self.messages.submit_button,
1873 delete_label=self.messages.delete_label,
1874 formstyle=self.settings.formstyle
1875 )
1876 if form.accepts(request, session,
1877 formname='retrieve_password', dbio=False,
1878 onvalidation=onvalidation,hideerror=self.settings.hideerror):
1879 user = self.db(table_user.email == form.vars.email).select().first()
1880 if not user:
1881 self.environment.session.flash = \
1882 self.messages.invalid_email
1883 redirect(self.url(args=request.args))
1884 elif user.registration_key in ('pending','disabled','blocked'):
1885 self.environment.session.flash = \
1886 self.messages.registration_pending
1887 redirect(self.url(args=request.args))
1888 password = self.random_password()
1889 passfield = self.settings.password_field
1890 d = {passfield: table_user[passfield].validate(password)[0],
1891 'registration_key': ''}
1892 user.update_record(**d)
1893 if self.settings.mailer and \
1894 self.settings.mailer.send(to=form.vars.email,
1895 subject=self.messages.retrieve_password_subject,
1896 message=self.messages.retrieve_password \
1897 % dict(password=password)):
1898 session.flash = self.messages.email_sent
1899 else:
1900 session.flash = self.messages.unable_to_send_email
1901 if log:
1902 self.log_event(log % user)
1903 callback(onaccept,form)
1904 if not next:
1905 next = self.url(args = request.args)
1906 elif isinstance(next, (list, tuple)):
1907 next = next[0]
1908 elif next and not next[0] == '/' and next[:4] != 'http':
1909 next = self.url(next.replace('[id]', str(form.vars.id)))
1910 redirect(next)
1911 table_user.email.requires = old_requires
1912 return form
1913
1921 """
1922 returns a form to reset the user password
1923
1924 .. method:: Auth.reset_password([next=DEFAULT
1925 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
1926
1927 """
1928
1929 table_user = self.settings.table_user
1930 request = self.environment.request
1931
1932 session = self.environment.session
1933
1934 if next == DEFAULT:
1935 next = request.get_vars._next \
1936 or request.post_vars._next \
1937 or self.settings.reset_password_next
1938
1939 try:
1940 key = request.vars.key or request.args[-1]
1941 t0 = int(key.split('-')[0])
1942 if time.time()-t0 > 60*60*24: raise Exception
1943 user = self.db(table_user.reset_password_key == key).select().first()
1944 if not user: raise Exception
1945 except Exception:
1946 session.flash = self.messages.invalid_reset_password
1947 redirect(next)
1948 passfield = self.settings.password_field
1949 form = SQLFORM.factory(
1950 Field('new_password', 'password',
1951 label=self.messages.new_password,
1952 requires=self.settings.table_user[passfield].requires),
1953 Field('new_password2', 'password',
1954 label=self.messages.verify_password,
1955 requires=[IS_EXPR('value==%s' % repr(request.vars.new_password),
1956 self.messages.mismatched_password)]),
1957 submit_button=self.messages.submit_button,
1958 formstyle=self.settings.formstyle,
1959 )
1960 if form.accepts(request,session,hideerror=self.settings.hideerror):
1961 user.update_record(**{passfield:form.vars.new_password,
1962 'registration_key':'',
1963 'reset_password_key':''})
1964 session.flash = self.messages.password_changed
1965 redirect(next)
1966 return form
1967
1975 """
1976 returns a form to reset the user password
1977
1978 .. method:: Auth.reset_password([next=DEFAULT
1979 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
1980
1981 """
1982
1983 table_user = self.settings.table_user
1984 request = self.environment.request
1985 response = self.environment.response
1986 session = self.environment.session
1987 captcha = self.settings.retrieve_password_captcha or \
1988 (self.settings.retrieve_password_captcha!=False and self.settings.captcha)
1989
1990 if next == DEFAULT:
1991 next = request.get_vars._next \
1992 or request.post_vars._next \
1993 or self.settings.request_reset_password_next
1994
1995 if not self.settings.mailer:
1996 response.flash = self.messages.function_disabled
1997 return ''
1998 if onvalidation == DEFAULT:
1999 onvalidation = self.settings.reset_password_onvalidation
2000 if onaccept == DEFAULT:
2001 onaccept = self.settings.reset_password_onaccept
2002 if log == DEFAULT:
2003 log = self.messages.reset_password_log
2004
2005 table_user.email.requires = [
2006 IS_EMAIL(error_message=self.messages.invalid_email),
2007 IS_IN_DB(self.db, table_user.email,
2008 error_message=self.messages.invalid_email)]
2009 form = SQLFORM(table_user,
2010 fields=['email'],
2011 hidden=dict(_next=next),
2012 showid=self.settings.showid,
2013 submit_button=self.messages.submit_button,
2014 delete_label=self.messages.delete_label,
2015 formstyle=self.settings.formstyle
2016 )
2017 if captcha:
2018 addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle,'captcha__row')
2019 if form.accepts(request, session,
2020 formname='reset_password', dbio=False,
2021 onvalidation=onvalidation,
2022 hideerror=self.settings.hideerror):
2023 user = self.db(table_user.email == form.vars.email).select().first()
2024 if not user:
2025 session.flash = self.messages.invalid_email
2026 redirect(self.url(args=request.args))
2027 elif user.registration_key in ('pending','disabled','blocked'):
2028 session.flash = self.messages.registration_pending
2029 redirect(self.url(args=request.args))
2030 reset_password_key = str(int(time.time()))+'-' + web2py_uuid()
2031
2032 if self.settings.mailer.send(to=form.vars.email,
2033 subject=self.messages.reset_password_subject,
2034 message=self.messages.reset_password % \
2035 dict(key=reset_password_key)):
2036 session.flash = self.messages.email_sent
2037 user.update_record(reset_password_key=reset_password_key)
2038 else:
2039 session.flash = self.messages.unable_to_send_email
2040 if log:
2041 self.log_event(log % user)
2042 callback(onaccept,form)
2043 if not next:
2044 next = self.url(args = request.args)
2045 elif isinstance(next, (list, tuple)):
2046 next = next[0]
2047 elif next and not next[0] == '/' and next[:4] != 'http':
2048 next = self.url(next.replace('[id]', str(form.vars.id)))
2049 redirect(next)
2050
2051 return form
2052
2064
2072 """
2073 returns a form that lets the user change password
2074
2075 .. method:: Auth.change_password([next=DEFAULT[, onvalidation=DEFAULT[,
2076 onaccept=DEFAULT[, log=DEFAULT]]]])
2077 """
2078
2079 if not self.is_logged_in():
2080 redirect(self.settings.login_url)
2081 db = self.db
2082 table_user = self.settings.table_user
2083 usern = self.settings.table_user_name
2084 s = db(table_user.id == self.user.id)
2085
2086 request = self.environment.request
2087 session = self.environment.session
2088 if next == DEFAULT:
2089 next = request.get_vars._next \
2090 or request.post_vars._next \
2091 or self.settings.change_password_next
2092 if onvalidation == DEFAULT:
2093 onvalidation = self.settings.change_password_onvalidation
2094 if onaccept == DEFAULT:
2095 onaccept = self.settings.change_password_onaccept
2096 if log == DEFAULT:
2097 log = self.messages.change_password_log
2098 passfield = self.settings.password_field
2099 form = SQLFORM.factory(
2100 Field('old_password', 'password',
2101 label=self.messages.old_password,
2102 requires=validators(
2103 table_user[passfield].requires,
2104 IS_IN_DB(s, '%s.%s' % (usern, passfield),
2105 error_message=self.messages.invalid_password))),
2106 Field('new_password', 'password',
2107 label=self.messages.new_password,
2108 requires=table_user[passfield].requires),
2109 Field('new_password2', 'password',
2110 label=self.messages.verify_password,
2111 requires=[IS_EXPR('value==%s' % repr(request.vars.new_password),
2112 self.messages.mismatched_password)]),
2113 submit_button=self.messages.submit_button,
2114 formstyle = self.settings.formstyle
2115 )
2116 if form.accepts(request, session,
2117 formname='change_password',
2118 onvalidation=onvalidation,
2119 hideerror=self.settings.hideerror):
2120 d = {passfield: form.vars.new_password}
2121 s.update(**d)
2122 session.flash = self.messages.password_changed
2123 if log:
2124 self.log_event(log % self.user)
2125 callback(onaccept,form)
2126 if not next:
2127 next = self.url(args=request.args)
2128 elif isinstance(next, (list, tuple)):
2129 next = next[0]
2130 elif next and not next[0] == '/' and next[:4] != 'http':
2131 next = self.url(next.replace('[id]', str(form.vars.id)))
2132 redirect(next)
2133 return form
2134
2142 """
2143 returns a form that lets the user change his/her profile
2144
2145 .. method:: Auth.profile([next=DEFAULT [, onvalidation=DEFAULT
2146 [, onaccept=DEFAULT [, log=DEFAULT]]]])
2147
2148 """
2149
2150 table_user = self.settings.table_user
2151 if not self.is_logged_in():
2152 redirect(self.settings.login_url)
2153 passfield = self.settings.password_field
2154 self.settings.table_user[passfield].writable = False
2155 request = self.environment.request
2156 session = self.environment.session
2157 if next == DEFAULT:
2158 next = request.get_vars._next \
2159 or request.post_vars._next \
2160 or self.settings.profile_next
2161 if onvalidation == DEFAULT:
2162 onvalidation = self.settings.profile_onvalidation
2163 if onaccept == DEFAULT:
2164 onaccept = self.settings.profile_onaccept
2165 if log == DEFAULT:
2166 log = self.messages.profile_log
2167 form = SQLFORM(
2168 table_user,
2169 self.user.id,
2170 fields = self.settings.profile_fields,
2171 hidden = dict(_next=next),
2172 showid = self.settings.showid,
2173 submit_button = self.messages.submit_button,
2174 delete_label = self.messages.delete_label,
2175 upload = self.settings.download_url,
2176 formstyle = self.settings.formstyle
2177 )
2178 if form.accepts(request, session,
2179 formname='profile',
2180 onvalidation=onvalidation,hideerror=self.settings.hideerror):
2181 self.user.update(table_user._filter_fields(form.vars))
2182 session.flash = self.messages.profile_updated
2183 if log:
2184 self.log_event(log % self.user)
2185 callback(onaccept,form)
2186 if not next:
2187 next = self.url(args=request.args)
2188 elif isinstance(next, (list, tuple)):
2189 next = next[0]
2190 elif next and not next[0] == '/' and next[:4] != 'http':
2191 next = self.url(next.replace('[id]', str(form.vars.id)))
2192 redirect(next)
2193 return form
2194
2196 return self.environment.session.auth.impersonator
2197
2199 """
2200 usage: POST TO http://..../impersonate request.post_vars.user_id=<id>
2201 set request.post_vars.user_id to 0 to restore original user.
2202
2203 requires impersonator is logged in and
2204 has_permission('impersonate', 'auth_user', user_id)
2205 """
2206 request = self.environment.request
2207 session = self.environment.session
2208 auth = session.auth
2209 if not self.is_logged_in() or not self.environment.request.post_vars:
2210 raise HTTP(401, "Not Authorized")
2211 current_id = auth.user.id
2212 requested_id = user_id
2213 if user_id == DEFAULT:
2214 user_id = self.environment.request.post_vars.user_id
2215 if user_id and user_id != self.user.id and user_id != '0':
2216 if not self.has_permission('impersonate',
2217 self.settings.table_user_name,
2218 user_id):
2219 raise HTTP(403, "Forbidden")
2220 user = self.settings.table_user(user_id)
2221 if not user:
2222 raise HTTP(401, "Not Authorized")
2223 auth.impersonator = cPickle.dumps(session)
2224 auth.user.update(
2225 self.settings.table_user._filter_fields(user, True))
2226 self.user = auth.user
2227 if self.settings.login_onaccept:
2228 form = Storage(dict(vars=self.user))
2229 self.settings.login_onaccept(form)
2230 log = self.messages.impersonate_log
2231 if log:
2232 self.log_event(log % dict(id=current_id,other_id=auth.user.id))
2233 elif user_id in (0, '0') and self.is_impersonating():
2234 session.clear()
2235 session.update(cPickle.loads(auth.impersonator))
2236 self.user = session.auth.user
2237 if requested_id == DEFAULT and not request.post_vars:
2238 return SQLFORM.factory(Field('user_id','integer'))
2239 return self.user
2240
2242 """
2243 displays the groups and their roles for the logged in user
2244 """
2245
2246 if not self.is_logged_in():
2247 redirect(self.settings.login_url)
2248 memberships = self.db(self.settings.table_membership.user_id
2249 == self.user.id).select()
2250 table = TABLE()
2251 for membership in memberships:
2252 groups = self.db(self.settings.table_group.id
2253 == membership.group_id).select()
2254 if groups:
2255 group = groups[0]
2256 table.append(TR(H3(group.role, '(%s)' % group.id)))
2257 table.append(TR(P(group.description)))
2258 if not memberships:
2259 return None
2260 return table
2261
2263 """
2264 you can change the view for this page to make it look as you like
2265 """
2266
2267 return 'ACCESS DENIED'
2268
2270 """
2271 decorator that prevents access to action if not logged in
2272 """
2273
2274 def decorator(action):
2275
2276 def f(*a, **b):
2277
2278 if self.settings.allow_basic_login_only and not self.basic():
2279 if self.environment.request.is_restful:
2280 raise HTTP(403,"Not authorized")
2281 return call_or_redirect(self.settings.on_failed_authorization)
2282
2283 if not condition:
2284 if self.environment.request.is_restful:
2285 raise HTTP(403,"Not authorized")
2286 if not self.basic() and not self.is_logged_in():
2287 request = self.environment.request
2288 next = URL(r=request,args=request.args,
2289 vars=request.get_vars)
2290 self.environment.session.flash = self.environment.response.flash
2291 return call_or_redirect(self.settings.on_failed_authentication,
2292 self.settings.login_url + \
2293 '?_next='+urllib.quote(next))
2294 else:
2295 self.environment.session.flash = \
2296 self.messages.access_denied
2297 return call_or_redirect(self.settings.on_failed_authorization)
2298 return action(*a, **b)
2299 f.__doc__ = action.__doc__
2300 f.__name__ = action.__name__
2301 f.__dict__.update(action.__dict__)
2302 return f
2303
2304 return decorator
2305
2307 """
2308 decorator that prevents access to action if not logged in
2309 """
2310
2311 def decorator(action):
2312
2313 def f(*a, **b):
2314
2315 if self.settings.allow_basic_login_only and not self.basic():
2316 if self.environment.request.is_restful:
2317 raise HTTP(403,"Not authorized")
2318 return call_or_redirect(self.settings.on_failed_authorization)
2319
2320 if not self.basic() and not self.is_logged_in():
2321 if self.environment.request.is_restful:
2322 raise HTTP(403,"Not authorized")
2323 request = self.environment.request
2324 next = URL(r=request,args=request.args,
2325 vars=request.get_vars)
2326 self.environment.session.flash = self.environment.response.flash
2327 return call_or_redirect(self.settings.on_failed_authentication,
2328 self.settings.login_url + \
2329 '?_next='+urllib.quote(next)
2330 )
2331 return action(*a, **b)
2332 f.__doc__ = action.__doc__
2333 f.__name__ = action.__name__
2334 f.__dict__.update(action.__dict__)
2335 return f
2336
2337 return decorator
2338
2340 """
2341 decorator that prevents access to action if not logged in or
2342 if user logged in is not a member of group_id.
2343 If role is provided instead of group_id then the
2344 group_id is calculated.
2345 """
2346
2347 def decorator(action):
2348 def f(*a, **b):
2349 if self.settings.allow_basic_login_only and not self.basic():
2350 if self.environment.request.is_restful:
2351 raise HTTP(403,"Not authorized")
2352 return call_or_redirect(self.settings.on_failed_authorization)
2353
2354 if not self.basic() and not self.is_logged_in():
2355 if self.environment.request.is_restful:
2356 raise HTTP(403,"Not authorized")
2357 request = self.environment.request
2358 next = URL(r=request,args=request.args,
2359 vars=request.get_vars)
2360 self.environment.session.flash = self.environment.response.flash
2361 return call_or_redirect(self.settings.on_failed_authentication,
2362 self.settings.login_url + \
2363 '?_next='+urllib.quote(next)
2364 )
2365 if not self.has_membership(group_id=group_id, role=role):
2366 self.environment.session.flash = \
2367 self.messages.access_denied
2368 return call_or_redirect(self.settings.on_failed_authorization)
2369 return action(*a, **b)
2370 f.__doc__ = action.__doc__
2371 f.__name__ = action.__name__
2372 f.__dict__.update(action.__dict__)
2373 return f
2374
2375 return decorator
2376
2383 """
2384 decorator that prevents access to action if not logged in or
2385 if user logged in is not a member of any group (role) that
2386 has 'name' access to 'table_name', 'record_id'.
2387 """
2388
2389 def decorator(action):
2390
2391 def f(*a, **b):
2392 if self.settings.allow_basic_login_only and not self.basic():
2393 if self.environment.request.is_restful:
2394 raise HTTP(403,"Not authorized")
2395 return call_or_redirect(self.settings.on_failed_authorization)
2396
2397 if not self.basic() and not self.is_logged_in():
2398 if self.environment.request.is_restful:
2399 raise HTTP(403,"Not authorized")
2400 request = self.environment.request
2401 next = URL(r=request,args=request.args,
2402 vars=request.get_vars)
2403 self.environment.session.flash = self.environment.response.flash
2404 return call_or_redirect(self.settings.on_failed_authentication,
2405 self.settings.login_url +
2406 '?_next='+urllib.quote(next)
2407 )
2408
2409 if not self.has_permission(name, table_name, record_id):
2410 self.environment.session.flash = \
2411 self.messages.access_denied
2412 return call_or_redirect(self.settings.on_failed_authorization)
2413 return action(*a, **b)
2414 f.__doc__ = action.__doc__
2415 f.__name__ = action.__name__
2416 f.__dict__.update(action.__dict__)
2417 return f
2418
2419 return decorator
2420
2422 """
2423 creates a group associated to a role
2424 """
2425
2426 group_id = self.settings.table_group.insert(role=role,
2427 description=description)
2428 log = self.messages.add_group_log
2429 if log:
2430 self.log_event(log % dict(group_id=group_id, role=role))
2431 return group_id
2432
2434 """
2435 deletes a group
2436 """
2437
2438 self.db(self.settings.table_group.id == group_id).delete()
2439 self.db(self.settings.table_membership.group_id
2440 == group_id).delete()
2441 self.db(self.settings.table_permission.group_id
2442 == group_id).delete()
2443 log = self.messages.del_group_log
2444 if log:
2445 self.log_event(log % dict(group_id=group_id))
2446
2448 """
2449 returns the group_id of the group specified by the role
2450 """
2451 rows = self.db(self.settings.table_group.role == role).select()
2452 if not rows:
2453 return None
2454 return rows[0].id
2455
2457 """
2458 returns the group_id of the group uniquely associated to this user
2459 i.e. role=user:[user_id]
2460 """
2461 if not user_id and self.user:
2462 user_id = self.user.id
2463 role = 'user_%s' % user_id
2464 return self.id_group(role)
2465
2467 """
2468 checks if user is member of group_id or role
2469 """
2470
2471 group_id = group_id or self.id_group(role)
2472 try:
2473 group_id = int(group_id)
2474 except:
2475 group_id = self.id_group(group_id)
2476 if not user_id and self.user:
2477 user_id = self.user.id
2478 membership = self.settings.table_membership
2479 if self.db((membership.user_id == user_id)
2480 & (membership.group_id == group_id)).select():
2481 r = True
2482 else:
2483 r = False
2484 log = self.messages.has_membership_log
2485 if log:
2486 self.log_event(log % dict(user_id=user_id,
2487 group_id=group_id, check=r))
2488 return r
2489
2491 """
2492 gives user_id membership of group_id or role
2493 if user_id==None than user_id is that of current logged in user
2494 """
2495
2496 group_id = group_id or self.id_group(role)
2497 try:
2498 group_id = int(group_id)
2499 except:
2500 group_id = self.id_group(group_id)
2501 if not user_id and self.user:
2502 user_id = self.user.id
2503 membership = self.settings.table_membership
2504 record = membership(user_id = user_id,group_id = group_id)
2505 if record:
2506 return record.id
2507 else:
2508 id = membership.insert(group_id=group_id, user_id=user_id)
2509 log = self.messages.add_membership_log
2510 if log:
2511 self.log_event(log % dict(user_id=user_id,
2512 group_id=group_id))
2513 return id
2514
2516 """
2517 revokes membership from group_id to user_id
2518 if user_id==None than user_id is that of current logged in user
2519 """
2520
2521 group_id = group_id or self.id_group(role)
2522 if not user_id and self.user:
2523 user_id = self.user.id
2524 membership = self.settings.table_membership
2525 log = self.messages.del_membership_log
2526 if log:
2527 self.log_event(log % dict(user_id=user_id,
2528 group_id=group_id))
2529 return self.db(membership.user_id
2530 == user_id)(membership.group_id
2531 == group_id).delete()
2532
2533 - def has_permission(
2534 self,
2535 name='any',
2536 table_name='',
2537 record_id=0,
2538 user_id=None,
2539 group_id=None,
2540 ):
2541 """
2542 checks if user_id or current logged in user is member of a group
2543 that has 'name' permission on 'table_name' and 'record_id'
2544 if group_id is passed, it checks whether the group has the permission
2545 """
2546
2547 if not user_id and not group_id and self.user:
2548 user_id = self.user.id
2549 if user_id:
2550 membership = self.settings.table_membership
2551 rows = self.db(membership.user_id
2552 == user_id).select(membership.group_id)
2553 groups = set([row.group_id for row in rows])
2554 if group_id and not group_id in groups:
2555 return False
2556 else:
2557 groups = set([group_id])
2558 permission = self.settings.table_permission
2559 rows = self.db(permission.name == name)(permission.table_name
2560 == str(table_name))(permission.record_id
2561 == record_id).select(permission.group_id)
2562 groups_required = set([row.group_id for row in rows])
2563 if record_id:
2564 rows = self.db(permission.name
2565 == name)(permission.table_name
2566 == str(table_name))(permission.record_id
2567 == 0).select(permission.group_id)
2568 groups_required = groups_required.union(set([row.group_id
2569 for row in rows]))
2570 if groups.intersection(groups_required):
2571 r = True
2572 else:
2573 r = False
2574 log = self.messages.has_permission_log
2575 if log and user_id:
2576 self.log_event(log % dict(user_id=user_id, name=name,
2577 table_name=table_name, record_id=record_id))
2578 return r
2579
2580 - def add_permission(
2581 self,
2582 group_id,
2583 name='any',
2584 table_name='',
2585 record_id=0,
2586 ):
2587 """
2588 gives group_id 'name' access to 'table_name' and 'record_id'
2589 """
2590
2591 permission = self.settings.table_permission
2592 if group_id == 0:
2593 group_id = self.user_group()
2594 id = permission.insert(group_id=group_id, name=name,
2595 table_name=str(table_name),
2596 record_id=long(record_id))
2597 log = self.messages.add_permission_log
2598 if log:
2599 self.log_event(log % dict(permission_id=id, group_id=group_id,
2600 name=name, table_name=table_name,
2601 record_id=record_id))
2602 return id
2603
2604 - def del_permission(
2605 self,
2606 group_id,
2607 name='any',
2608 table_name='',
2609 record_id=0,
2610 ):
2611 """
2612 revokes group_id 'name' access to 'table_name' and 'record_id'
2613 """
2614
2615 permission = self.settings.table_permission
2616 log = self.messages.del_permission_log
2617 if log:
2618 self.log_event(log % dict(group_id=group_id, name=name,
2619 table_name=table_name, record_id=record_id))
2620 return self.db(permission.group_id == group_id)(permission.name
2621 == name)(permission.table_name
2622 == str(table_name))(permission.record_id
2623 == long(record_id)).delete()
2624
2626 """
2627 returns a query with all accessible records for user_id or
2628 the current logged in user
2629 this method does not work on GAE because uses JOIN and IN
2630
2631 example::
2632
2633 db(auth.accessible_query('read', db.mytable)).select(db.mytable.ALL)
2634
2635 """
2636 if not user_id:
2637 user_id = self.user.id
2638 if self.has_permission(name, table, 0, user_id):
2639 return table.id > 0
2640 db = self.db
2641 membership = self.settings.table_membership
2642 permission = self.settings.table_permission
2643 return table.id.belongs(db(membership.user_id == user_id)\
2644 (membership.group_id == permission.group_id)\
2645 (permission.name == name)\
2646 (permission.table_name == table)\
2647 ._select(permission.record_id))
2648
2649
2650 -class Crud(object):
2651
2652 - def url(self, f=None, args=[], vars={}):
2653 """
2654 this should point to the controller that exposes
2655 download and crud
2656 """
2657 return self.environment.URL(r=self.environment.request,
2658 c=self.settings.controller,
2659 f=f, args=args, vars=vars)
2660
2661 - def __init__(self, environment, db, controller='default'):
2662 self.environment = Storage(environment)
2663 self.db = db
2664 self.settings = Settings()
2665 self.settings.auth = None
2666 self.settings.logger = None
2667
2668 self.settings.create_next = None
2669 self.settings.update_next = None
2670 self.settings.controller = controller
2671 self.settings.delete_next = self.url()
2672 self.settings.download_url = self.url('download')
2673 self.settings.create_onvalidation = StorageList()
2674 self.settings.update_onvalidation = StorageList()
2675 self.settings.delete_onvalidation = StorageList()
2676 self.settings.create_onaccept = StorageList()
2677 self.settings.update_onaccept = StorageList()
2678 self.settings.update_ondelete = StorageList()
2679 self.settings.delete_onaccept = StorageList()
2680 self.settings.update_deletable = True
2681 self.settings.showid = False
2682 self.settings.keepvalues = False
2683 self.settings.create_captcha = None
2684 self.settings.update_captcha = None
2685 self.settings.captcha = None
2686 self.settings.formstyle = 'table3cols'
2687 self.settings.hideerror = False
2688 self.settings.detect_record_change = True
2689 self.settings.lock_keys = True
2690
2691 self.messages = Messages(self.environment.T)
2692 self.messages.submit_button = 'Submit'
2693 self.messages.delete_label = 'Check to delete:'
2694 self.messages.record_created = 'Record Created'
2695 self.messages.record_updated = 'Record Updated'
2696 self.messages.record_deleted = 'Record Deleted'
2697
2698 self.messages.update_log = 'Record %(id)s updated'
2699 self.messages.create_log = 'Record %(id)s created'
2700 self.messages.read_log = 'Record %(id)s read'
2701 self.messages.delete_log = 'Record %(id)s deleted'
2702
2703 self.messages.lock_keys = True
2704
2706
2707 args = self.environment.request.args
2708 if len(args) < 1:
2709 redirect(self.url(args='tables'))
2710 elif args[0] == 'tables':
2711 return self.tables()
2712 elif args[0] == 'create':
2713 return self.create(args(1))
2714 elif args[0] == 'select':
2715 return self.select(args(1),linkto=self.url(args='read'))
2716 elif args[0] == 'read':
2717 return self.read(args(1), args(2))
2718 elif args[0] == 'update':
2719 return self.update(args(1), args(2))
2720 elif args[0] == 'delete':
2721 return self.delete(args(1), args(2))
2722 else:
2723 raise HTTP(404)
2724
2728
2730 if not self.settings.auth:
2731 return True
2732 try:
2733 record_id = record.id
2734 except:
2735 record_id = record
2736 return self.settings.auth.has_permission(name, str(table), record_id)
2737
2742
2743
2744 @staticmethod
2745 - def archive(form,archive_table=None,current_record='current_record'):
2746 """
2747 If you have a table (db.mytable) that needs full revision history you can just do::
2748
2749 form=crud.update(db.mytable,myrecord,onaccept=crud.archive)
2750
2751 crud.archive will define a new table "mytable_archive" and store the
2752 previous record in the newly created table including a reference
2753 to the current record.
2754
2755 If you want to access such table you need to define it yourself in a model::
2756
2757 db.define_table('mytable_archive',
2758 Field('current_record',db.mytable),
2759 db.mytable)
2760
2761 Notice such table includes all fields of db.mytable plus one: current_record.
2762 crud.archive does not timestamp the stored record unless your original table
2763 has a fields like::
2764
2765 db.define_table(...,
2766 Field('saved_on','datetime',
2767 default=request.now,update=request.now,writable=False),
2768 Field('saved_by',auth.user,
2769 default=auth.user_id,update=auth.user_id,writable=False),
2770
2771 there is nothing special about these fields since they are filled before
2772 the record is archived.
2773
2774 If you want to change the archive table name and the name of the reference field
2775 you can do, for example::
2776
2777 db.define_table('myhistory',
2778 Field('parent_record',db.mytable),
2779 db.mytable)
2780
2781 and use it as::
2782
2783 form=crud.update(db.mytable,myrecord,
2784 onaccept=lambda form:crud.archive(form,
2785 archive_table=db.myhistory,
2786 current_record='parent_record'))
2787
2788 """
2789 old_record = form.record
2790 if not old_record:
2791 return None
2792 table = form.table
2793 if not archive_table:
2794 archive_table_name = '%s_archive' % table
2795 if archive_table_name in table._db:
2796 archive_table = table._db[archive_table_name]
2797 else:
2798 archive_table = table._db.define_table(archive_table_name,
2799 Field(current_record,table),
2800 table)
2801 new_record = {current_record:old_record.id}
2802 for fieldname in archive_table.fields:
2803 if not fieldname in ['id',current_record] and fieldname in old_record:
2804 new_record[fieldname]=old_record[fieldname]
2805 id = archive_table.insert(**new_record)
2806 return id
2807
2808 - def update(
2809 self,
2810 table,
2811 record,
2812 next=DEFAULT,
2813 onvalidation=DEFAULT,
2814 onaccept=DEFAULT,
2815 ondelete=DEFAULT,
2816 log=DEFAULT,
2817 message=DEFAULT,
2818 deletable=DEFAULT,
2819 formname=DEFAULT,
2820 ):
2821 """
2822 .. method:: Crud.update(table, record, [next=DEFAULT
2823 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT
2824 [, message=DEFAULT[, deletable=DEFAULT]]]]]])
2825
2826 """
2827 if not (isinstance(table, self.db.Table) or table in self.db.tables) \
2828 or (isinstance(record, str) and not str(record).isdigit()):
2829 raise HTTP(404)
2830 if not isinstance(table, self.db.Table):
2831 table = self.db[table]
2832 try:
2833 record_id = record.id
2834 except:
2835 record_id = record or 0
2836 if record_id and not self.has_permission('update', table, record_id):
2837 redirect(self.settings.auth.settings.on_failed_authorization)
2838 if not record_id \
2839 and not self.has_permission('create', table, record_id):
2840 redirect(self.settings.auth.settings.on_failed_authorization)
2841
2842 request = self.environment.request
2843 response = self.environment.response
2844 session = self.environment.session
2845 if request.extension == 'json' and request.vars.json:
2846 request.vars.update(simplejson.loads(request.vars.json))
2847 if next == DEFAULT:
2848 next = request.get_vars._next \
2849 or request.post_vars._next \
2850 or self.settings.update_next
2851 if onvalidation == DEFAULT:
2852 onvalidation = self.settings.update_onvalidation
2853 if onaccept == DEFAULT:
2854 onaccept = self.settings.update_onaccept
2855 if ondelete == DEFAULT:
2856 ondelete = self.settings.update_ondelete
2857 if log == DEFAULT:
2858 log = self.messages.update_log
2859 if deletable == DEFAULT:
2860 deletable = self.settings.update_deletable
2861 if message == DEFAULT:
2862 message = self.messages.record_updated
2863 form = SQLFORM(
2864 table,
2865 record,
2866 hidden=dict(_next=next),
2867 showid=self.settings.showid,
2868 submit_button=self.messages.submit_button,
2869 delete_label=self.messages.delete_label,
2870 deletable=deletable,
2871 upload=self.settings.download_url,
2872 formstyle=self.settings.formstyle
2873 )
2874 self.accepted = False
2875 self.deleted = False
2876 captcha = self.settings.update_captcha or \
2877 self.settings.captcha
2878 if record and captcha:
2879 addrow(form, captcha.label, captcha, captcha.comment,
2880 self.settings.formstyle,'captcha__row')
2881 captcha = self.settings.create_captcha or \
2882 self.settings.captcha
2883 if not record and captcha:
2884 addrow(form, captcha.label, captcha, captcha.comment,
2885 self.settings.formstyle,'captcha__row')
2886 if not request.extension in ('html','load'):
2887 (_session, _formname) = (None, None)
2888 else:
2889 (_session, _formname) = \
2890 (session, '%s/%s' % (table._tablename, form.record_id))
2891 if formname!=DEFAULT:
2892 _formname = formname
2893 keepvalues = self.settings.keepvalues
2894 if request.vars.delete_this_record:
2895 keepvalues = False
2896 if isinstance(onvalidation,StorageList):
2897 onvalidation=onvalidation.get(table._tablename, [])
2898 if form.accepts(request, _session, formname=_formname,
2899 onvalidation=onvalidation, keepvalues=keepvalues,
2900 hideerror=self.settings.hideerror,
2901 detect_record_change = self.settings.detect_record_change):
2902 self.accepted = True
2903 response.flash = message
2904 if log:
2905 self.log_event(log % form.vars)
2906 if request.vars.delete_this_record:
2907 self.deleted = True
2908 message = self.messages.record_deleted
2909 callback(ondelete,form,table._tablename)
2910 response.flash = message
2911 callback(onaccept,form,table._tablename)
2912 if not request.extension in ('html','load'):
2913 raise HTTP(200, 'RECORD CREATED/UPDATED')
2914 if isinstance(next, (list, tuple)):
2915 next = next[0]
2916 if next:
2917 if next[0] != '/' and next[:4] != 'http':
2918 next = URL(r=request,
2919 f=next.replace('[id]', str(form.vars.id)))
2920 session.flash = response.flash
2921 redirect(next)
2922 elif not request.extension in ('html','load'):
2923 raise HTTP(401)
2924 return form
2925
2936 """
2937 .. method:: Crud.create(table, [next=DEFAULT [, onvalidation=DEFAULT
2938 [, onaccept=DEFAULT [, log=DEFAULT[, message=DEFAULT]]]]])
2939 """
2940
2941 if next == DEFAULT:
2942 next = self.settings.create_next
2943 if onvalidation == DEFAULT:
2944 onvalidation = self.settings.create_onvalidation
2945 if onaccept == DEFAULT:
2946 onaccept = self.settings.create_onaccept
2947 if log == DEFAULT:
2948 log = self.messages.create_log
2949 if message == DEFAULT:
2950 message = self.messages.record_created
2951 return self.update(
2952 table,
2953 None,
2954 next=next,
2955 onvalidation=onvalidation,
2956 onaccept=onaccept,
2957 log=log,
2958 message=message,
2959 deletable=False,
2960 formname=formname,
2961 )
2962
2963 - def read(self, table, record):
2964 if not (isinstance(table, self.db.Table) or table in self.db.tables) \
2965 or (isinstance(record, str) and not str(record).isdigit()):
2966 raise HTTP(404)
2967 if not isinstance(table, self.db.Table):
2968 table = self.db[table]
2969 if not self.has_permission('read', table, record):
2970 redirect(self.settings.auth.settings.on_failed_authorization)
2971 form = SQLFORM(
2972 table,
2973 record,
2974 readonly=True,
2975 comments=False,
2976 upload=self.settings.download_url,
2977 showid=self.settings.showid,
2978 formstyle=self.settings.formstyle
2979 )
2980 if not self.environment.request.extension in ('html','load'):
2981 return table._filter_fields(form.record, id=True)
2982 return form
2983
2984 - def delete(
2985 self,
2986 table,
2987 record_id,
2988 next=DEFAULT,
2989 message=DEFAULT,
2990 ):
2991 """
2992 .. method:: Crud.delete(table, record_id, [next=DEFAULT
2993 [, message=DEFAULT]])
2994 """
2995 if not (isinstance(table, self.db.Table) or table in self.db.tables) \
2996 or not str(record_id).isdigit():
2997 raise HTTP(404)
2998 if not isinstance(table, self.db.Table):
2999 table = self.db[table]
3000 if not self.has_permission('delete', table, record_id):
3001 redirect(self.settings.auth.settings.on_failed_authorization)
3002 request = self.environment.request
3003 session = self.environment.session
3004 if next == DEFAULT:
3005 next = request.get_vars._next \
3006 or request.post_vars._next \
3007 or self.settings.delete_next
3008 if message == DEFAULT:
3009 message = self.messages.record_deleted
3010 record = table[record_id]
3011 if record:
3012 callback(self.settings.delete_onvalidation,record)
3013 del table[record_id]
3014 callback(self.settings.delete_onaccept,record,table._tablename)
3015 session.flash = message
3016 if next:
3017 redirect(next)
3018
3019 - def select(
3020 self,
3021 table,
3022 query=None,
3023 fields=None,
3024 orderby=None,
3025 limitby=None,
3026 headers={},
3027 **attr
3028 ):
3029 request = self.environment.request
3030 if not (isinstance(table, self.db.Table) or table in self.db.tables):
3031 raise HTTP(404)
3032 if not self.has_permission('select', table):
3033 redirect(self.settings.auth.settings.on_failed_authorization)
3034
3035
3036 if not isinstance(table, self.db.Table):
3037 table = self.db[table]
3038 if not query:
3039 query = table.id > 0
3040 if not fields:
3041 fields = [field for field in table if field.readable]
3042 rows = self.db(query).select(*fields, **dict(orderby=orderby,
3043 limitby=limitby))
3044 if not rows:
3045 return None
3046 if not 'upload' in attr:
3047 attr['upload'] = self.url('download')
3048 if not request.extension in ('html','load'):
3049 return rows.as_list()
3050 if not headers:
3051 headers = dict((str(k),k.label) for k in table)
3052 return SQLTABLE(rows, headers=headers, **attr)
3053
3060
3061 - def get_query(self, field, op, value, refsearch=False):
3062 try:
3063 if refsearch: format = self.get_format(field)
3064 if op == 'equals':
3065 if not refsearch:
3066 return field == value
3067 else:
3068 return lambda row: row[field.name][format] == value
3069 elif op == 'not equal':
3070 if not refsearch:
3071 return field != value
3072 else:
3073 return lambda row: row[field.name][format] != value
3074 elif op == 'greater than':
3075 if not refsearch:
3076 return field > value
3077 else:
3078 return lambda row: row[field.name][format] > value
3079 elif op == 'less than':
3080 if not refsearch:
3081 return field < value
3082 else:
3083 return lambda row: row[field.name][format] < value
3084 elif op == 'starts with':
3085 if not refsearch:
3086 return field.like(value+'%')
3087 else:
3088 return lambda row: str(row[field.name][format]).startswith(value)
3089 elif op == 'ends with':
3090 if not refsearch:
3091 return field.like('%'+value)
3092 else:
3093 return lambda row: str(row[field.name][format]).endswith(value)
3094 elif op == 'contains':
3095 if not refsearch:
3096 return field.like('%'+value+'%')
3097 else:
3098 return lambda row: value in row[field.name][format]
3099 except:
3100 return None
3101
3102
3103 - def search(self, *tables, **args):
3104 """
3105 Creates a search form and its results for a table
3106 Example usage:
3107 form, results = crud.search(db.test,
3108 queries = ['equals', 'not equal', 'contains'],
3109 query_labels={'equals':'Equals',
3110 'not equal':'Not equal'},
3111 fields = [db.test.id, db.test.children],
3112 field_labels = {'id':'ID','children':'Children'},
3113 zero='Please choose',
3114 query = (db.test.id > 0)&(db.test.id != 3) )
3115 """
3116 table = tables[0]
3117 fields = args.get('fields', table.fields)
3118 request = self.environment.request
3119 db = self.db
3120 if not (isinstance(table, db.Table) or table in db.tables):
3121 raise HTTP(404)
3122 tbl = TABLE()
3123 selected = []; refsearch = []; results = []
3124 ops = args.get('queries', [])
3125 zero = args.get('zero', '')
3126 if not ops:
3127 ops = ['equals', 'not equal', 'greater than',
3128 'less than', 'starts with',
3129 'ends with', 'contains']
3130 ops.insert(0,zero)
3131 query_labels = args.get('query_labels', {})
3132 query = args.get('query',table.id > 0)
3133 field_labels = args.get('field_labels',{})
3134 for field in fields:
3135 field = table[field]
3136 if not field.readable: continue
3137 fieldname = field.name
3138 chkval = request.vars.get('chk' + fieldname, None)
3139 txtval = request.vars.get('txt' + fieldname, None)
3140 opval = request.vars.get('op' + fieldname, None)
3141 row = TR(TD(INPUT(_type = "checkbox", _name = "chk" + fieldname,
3142 _disabled = (field.type == 'id'),
3143 value = (field.type == 'id' or chkval == 'on'))),
3144 TD(field_labels.get(fieldname,field.label)),
3145 TD(SELECT([OPTION(query_labels.get(op,op),
3146 _value=op) for op in ops],
3147 _name = "op" + fieldname,
3148 value = opval)),
3149 TD(INPUT(_type = "text", _name = "txt" + fieldname,
3150 _value = txtval, _id='txt' + fieldname,
3151 _class = str(field.type))))
3152 tbl.append(row)
3153 if request.post_vars and (chkval or field.type=='id'):
3154 if txtval and opval != '':
3155 if field.type[0:10] == 'reference ':
3156 refsearch.append(self.get_query(field,
3157 opval, txtval, refsearch=True))
3158 else:
3159 value, error = field.validate(txtval)
3160 if not error:
3161
3162 query &= self.get_query(field, opval, value)
3163 else:
3164 row[3].append(DIV(error,_class='error'))
3165 selected.append(field)
3166 form = FORM(tbl,INPUT(_type="submit"))
3167 if selected:
3168 try:
3169 results = db(query).select(*selected)
3170 for r in refsearch:
3171 results = results.find(r)
3172 except:
3173 results = None
3174 return form, results
3175
3176
3177 urllib2.install_opener(urllib2.build_opener(urllib2.HTTPCookieProcessor()))
3178
3179 -def fetch(url, data=None, headers={},
3180 cookie=Cookie.SimpleCookie(),
3181 user_agent='Mozilla/5.0'):
3182 if data != None:
3183 data = urllib.urlencode(data)
3184 if user_agent: headers['User-agent'] = user_agent
3185 headers['Cookie'] = ' '.join(['%s=%s;'%(c.key,c.value) for c in cookie.values()])
3186 try:
3187 from google.appengine.api import urlfetch
3188 except ImportError:
3189 req = urllib2.Request(url, data, headers)
3190 html = urllib2.urlopen(req).read()
3191 else:
3192 method = ((data==None) and urlfetch.GET) or urlfetch.POST
3193 while url is not None:
3194 response = urlfetch.fetch(url=url, payload=data,
3195 method=method, headers=headers,
3196 allow_truncated=False,follow_redirects=False,
3197 deadline=10)
3198
3199 data = None
3200 method = urlfetch.GET
3201
3202 cookie.load(response.headers.get('set-cookie', ''))
3203 url = response.headers.get('location')
3204 html = response.content
3205 return html
3206
3207 regex_geocode = \
3208 re.compile('\<coordinates\>(?P<la>[^,]*),(?P<lo>[^,]*).*?\</coordinates\>')
3209
3210
3212 try:
3213 a = urllib.quote(address)
3214 txt = fetch('http://maps.google.com/maps/geo?q=%s&output=xml'
3215 % a)
3216 item = regex_geocode.search(txt)
3217 (la, lo) = (float(item.group('la')), float(item.group('lo')))
3218 return (la, lo)
3219 except:
3220 return (0.0, 0.0)
3221
3222
3224 c = f.func_code.co_argcount
3225 n = f.func_code.co_varnames[:c]
3226 b = dict([(k, v) for k, v in b.items() if k in n])
3227 if len(b) == c:
3228 return f(**b)
3229 elif len(a) >= c:
3230 return f(*a[:c])
3231 raise HTTP(404, "Object does not exist")
3232
3233
3235
3237 self.environment = environment
3238 self.run_procedures = {}
3239 self.csv_procedures = {}
3240 self.xml_procedures = {}
3241 self.rss_procedures = {}
3242 self.json_procedures = {}
3243 self.jsonrpc_procedures = {}
3244 self.xmlrpc_procedures = {}
3245 self.amfrpc_procedures = {}
3246 self.amfrpc3_procedures = {}
3247 self.soap_procedures = {}
3248
3250 """
3251 example::
3252
3253 service = Service(globals())
3254 @service.run
3255 def myfunction(a, b):
3256 return a + b
3257 def call():
3258 return service()
3259
3260 Then call it with::
3261
3262 wget http://..../app/default/call/run/myfunction?a=3&b=4
3263
3264 """
3265 self.run_procedures[f.__name__] = f
3266 return f
3267
3269 """
3270 example::
3271
3272 service = Service(globals())
3273 @service.csv
3274 def myfunction(a, b):
3275 return a + b
3276 def call():
3277 return service()
3278
3279 Then call it with::
3280
3281 wget http://..../app/default/call/csv/myfunction?a=3&b=4
3282
3283 """
3284 self.run_procedures[f.__name__] = f
3285 return f
3286
3288 """
3289 example::
3290
3291 service = Service(globals())
3292 @service.xml
3293 def myfunction(a, b):
3294 return a + b
3295 def call():
3296 return service()
3297
3298 Then call it with::
3299
3300 wget http://..../app/default/call/xml/myfunction?a=3&b=4
3301
3302 """
3303 self.run_procedures[f.__name__] = f
3304 return f
3305
3307 """
3308 example::
3309
3310 service = Service(globals())
3311 @service.rss
3312 def myfunction():
3313 return dict(title=..., link=..., description=...,
3314 created_on=..., entries=[dict(title=..., link=...,
3315 description=..., created_on=...])
3316 def call():
3317 return service()
3318
3319 Then call it with::
3320
3321 wget http://..../app/default/call/rss/myfunction
3322
3323 """
3324 self.rss_procedures[f.__name__] = f
3325 return f
3326
3327 - def json(self, f):
3328 """
3329 example::
3330
3331 service = Service(globals())
3332 @service.json
3333 def myfunction(a, b):
3334 return [{a: b}]
3335 def call():
3336 return service()
3337
3338 Then call it with::
3339
3340 wget http://..../app/default/call/json/myfunction?a=hello&b=world
3341
3342 """
3343 self.json_procedures[f.__name__] = f
3344 return f
3345
3347 """
3348 example::
3349
3350 service = Service(globals())
3351 @service.jsonrpc
3352 def myfunction(a, b):
3353 return a + b
3354 def call():
3355 return service()
3356
3357 Then call it with::
3358
3359 wget http://..../app/default/call/jsonrpc/myfunction?a=hello&b=world
3360
3361 """
3362 self.jsonrpc_procedures[f.__name__] = f
3363 return f
3364
3366 """
3367 example::
3368
3369 service = Service(globals())
3370 @service.xmlrpc
3371 def myfunction(a, b):
3372 return a + b
3373 def call():
3374 return service()
3375
3376 The call it with::
3377
3378 wget http://..../app/default/call/xmlrpc/myfunction?a=hello&b=world
3379
3380 """
3381 self.xmlrpc_procedures[f.__name__] = f
3382 return f
3383
3385 """
3386 example::
3387
3388 service = Service(globals())
3389 @service.amfrpc
3390 def myfunction(a, b):
3391 return a + b
3392 def call():
3393 return service()
3394
3395 The call it with::
3396
3397 wget http://..../app/default/call/amfrpc/myfunction?a=hello&b=world
3398
3399 """
3400 self.amfrpc_procedures[f.__name__] = f
3401 return f
3402
3403 - def amfrpc3(self, domain='default'):
3404 """
3405 example::
3406
3407 service = Service(globals())
3408 @service.amfrpc3('domain')
3409 def myfunction(a, b):
3410 return a + b
3411 def call():
3412 return service()
3413
3414 The call it with::
3415
3416 wget http://..../app/default/call/amfrpc3/myfunction?a=hello&b=world
3417
3418 """
3419 if not isinstance(domain, str):
3420 raise SyntaxError, "AMF3 requires a domain for function"
3421
3422 def _amfrpc3(f):
3423 if domain:
3424 self.amfrpc3_procedures[domain+'.'+f.__name__] = f
3425 else:
3426 self.amfrpc3_procedures[f.__name__] = f
3427 return f
3428 return _amfrpc3
3429
3430 - def soap(self, name=None, returns=None, args=None,doc=None):
3431 """
3432 example::
3433
3434 service = Service(globals())
3435 @service.soap('MyFunction',returns={'result':int},args={'a':int,'b':int,})
3436 def myfunction(a, b):
3437 return a + b
3438 def call():
3439 return service()
3440
3441 The call it with::
3442
3443 from gluon.contrib.pysimplesoap.client import SoapClient
3444 client = SoapClient(wsdl="http://..../app/default/call/soap?WSDL")
3445 response = client.MyFunction(a=1,b=2)
3446 return response['result']
3447
3448 Exposes online generated documentation and xml example messages at:
3449 - http://..../app/default/call/soap
3450 """
3451
3452 def _soap(f):
3453 self.soap_procedures[name or f.__name__] = f, returns, args, doc
3454 return f
3455 return _soap
3456
3458 request = self.environment['request']
3459 if not args:
3460 args = request.args
3461 if args and args[0] in self.run_procedures:
3462 return str(universal_caller(self.run_procedures[args[0]],
3463 *args[1:], **dict(request.vars)))
3464 self.error()
3465
3467 request = self.environment['request']
3468 response = self.environment['response']
3469 response.headers['Content-Type'] = 'text/x-csv'
3470 if not args:
3471 args = request.args
3472
3473 def none_exception(value):
3474 if isinstance(value, unicode):
3475 return value.encode('utf8')
3476 if hasattr(value, 'isoformat'):
3477 return value.isoformat()[:19].replace('T', ' ')
3478 if value == None:
3479 return '<NULL>'
3480 return value
3481 if args and args[0] in self.run_procedures:
3482 r = universal_caller(self.run_procedures[args[0]],
3483 *args[1:], **dict(request.vars))
3484 s = cStringIO.StringIO()
3485 if hasattr(r, 'export_to_csv_file'):
3486 r.export_to_csv_file(s)
3487 elif r and isinstance(r[0], (dict, Storage)):
3488 import csv
3489 writer = csv.writer(s)
3490 writer.writerow(r[0].keys())
3491 for line in r:
3492 writer.writerow([none_exception(v) \
3493 for v in line.values()])
3494 else:
3495 import csv
3496 writer = csv.writer(s)
3497 for line in r:
3498 writer.writerow(line)
3499 return s.getvalue()
3500 self.error()
3501
3503 request = self.environment['request']
3504 response = self.environment['response']
3505 response.headers['Content-Type'] = 'text/xml'
3506 if not args:
3507 args = request.args
3508 if args and args[0] in self.run_procedures:
3509 s = universal_caller(self.run_procedures[args[0]],
3510 *args[1:], **dict(request.vars))
3511 if hasattr(s, 'as_list'):
3512 s = s.as_list()
3513 return serializers.xml(s)
3514 self.error()
3515
3517 request = self.environment['request']
3518 response = self.environment['response']
3519 if not args:
3520 args = request.args
3521 if args and args[0] in self.rss_procedures:
3522 feed = universal_caller(self.rss_procedures[args[0]],
3523 *args[1:], **dict(request.vars))
3524 else:
3525 self.error()
3526 response.headers['Content-Type'] = 'application/rss+xml'
3527 return serializers.rss(feed)
3528
3530 request = self.environment['request']
3531 response = self.environment['response']
3532 response.headers['Content-Type'] = 'text/x-json'
3533 if not args:
3534 args = request.args
3535 d = dict(request.vars)
3536 if args and args[0] in self.json_procedures:
3537 s = universal_caller(self.json_procedures[args[0]],*args[1:],**d)
3538 if hasattr(s, 'as_list'):
3539 s = s.as_list()
3540 return response.json(s)
3541 self.error()
3542
3545 self.code,self.info = code,info
3546
3548 import contrib.simplejson as simplejson
3549 def return_response(id, result):
3550 return simplejson.dumps({'version': '1.1',
3551 'id': id, 'result': result, 'error': None})
3552
3553 def return_error(id, code, message):
3554 return simplejson.dumps({'id': id,
3555 'version': '1.1',
3556 'error': {'name': 'JSONRPCError',
3557 'code': code, 'message': message}
3558 })
3559
3560 request = self.environment['request']
3561 methods = self.jsonrpc_procedures
3562 data = simplejson.loads(request.body.read())
3563 id, method, params = data['id'], data['method'], data.get('params','')
3564 if not method in methods:
3565 return return_error(id, 100, 'method "%s" does not exist' % method)
3566 try:
3567 s = methods[method](*params)
3568 if hasattr(s, 'as_list'):
3569 s = s.as_list()
3570 return return_response(id, s)
3571 except Service.JsonRpcException, e:
3572 return return_error(id, e.code, e.info)
3573 except BaseException:
3574 etype, eval, etb = sys.exc_info()
3575 return return_error(id, 100, '%s: %s' % (etype.__name__, eval))
3576 except:
3577 etype, eval, etb = sys.exc_info()
3578 return return_error(id, 100, 'Exception %s: %s' % (etype, eval))
3579
3581 request = self.environment['request']
3582 response = self.environment['response']
3583 services = self.xmlrpc_procedures.values()
3584 return response.xmlrpc(request, services)
3585
3587 try:
3588 import pyamf
3589 import pyamf.remoting.gateway
3590 except:
3591 return "pyamf not installed or not in Python sys.path"
3592 request = self.environment['request']
3593 response = self.environment['response']
3594 if version == 3:
3595 services = self.amfrpc3_procedures
3596 base_gateway = pyamf.remoting.gateway.BaseGateway(services)
3597 pyamf_request = pyamf.remoting.decode(request.body)
3598 else:
3599 services = self.amfrpc_procedures
3600 base_gateway = pyamf.remoting.gateway.BaseGateway(services)
3601 context = pyamf.get_context(pyamf.AMF0)
3602 pyamf_request = pyamf.remoting.decode(request.body, context)
3603 pyamf_response = pyamf.remoting.Envelope(pyamf_request.amfVersion,
3604 pyamf_request.clientType)
3605 for name, message in pyamf_request:
3606 pyamf_response[name] = base_gateway.getProcessor(message)(message)
3607 response.headers['Content-Type'] = pyamf.remoting.CONTENT_TYPE
3608 if version==3:
3609 return pyamf.remoting.encode(pyamf_response).getvalue()
3610 else:
3611 return pyamf.remoting.encode(pyamf_response, context).getvalue()
3612
3614 try:
3615 from contrib.pysimplesoap.server import SoapDispatcher
3616 except:
3617 return "pysimplesoap not installed in contrib"
3618 request = self.environment['request']
3619 response = self.environment['response']
3620 procedures = self.soap_procedures
3621
3622 location = "%s://%s%s" % (
3623 request.env.wsgi_url_scheme,
3624 request.env.http_host,
3625 URL(r=request,f="call/soap",vars={}))
3626 namespace = 'namespace' in response and response.namespace or location
3627 documentation = response.description or ''
3628 dispatcher = SoapDispatcher(
3629 name = response.title,
3630 location = location,
3631 action = location,
3632 namespace = namespace,
3633 prefix='pys',
3634 documentation = documentation,
3635 ns = True)
3636 for method, (function, returns, args, doc) in procedures.items():
3637 dispatcher.register_function(method, function, returns, args, doc)
3638 if request.env.request_method == 'POST':
3639
3640 response.headers['Content-Type'] = 'text/xml'
3641 return dispatcher.dispatch(request.body.read())
3642 elif 'WSDL' in request.vars:
3643
3644 response.headers['Content-Type'] = 'text/xml'
3645 return dispatcher.wsdl()
3646 elif 'op' in request.vars:
3647
3648 response.headers['Content-Type'] = 'text/html'
3649 method = request.vars['op']
3650 sample_req_xml, sample_res_xml, doc = dispatcher.help(method)
3651 body = [H1("Welcome to Web2Py SOAP webservice gateway"),
3652 A("See all webservice operations",
3653 _href=URL(r=request,f="call/soap",vars={})),
3654 H2(method),
3655 P(doc),
3656 UL(LI("Location: %s" % dispatcher.location),
3657 LI("Namespace: %s" % dispatcher.namespace),
3658 LI("SoapAction: %s" % dispatcher.action),
3659 ),
3660 H3("Sample SOAP XML Request Message:"),
3661 CODE(sample_req_xml,language="xml"),
3662 H3("Sample SOAP XML Response Message:"),
3663 CODE(sample_res_xml,language="xml"),
3664 ]
3665 return {'body': body}
3666 else:
3667
3668 response.headers['Content-Type'] = 'text/html'
3669 body = [H1("Welcome to Web2Py SOAP webservice gateway"),
3670 P(response.description),
3671 P("The following operations are available"),
3672 A("See WSDL for webservice description",
3673 _href=URL(r=request,f="call/soap",vars={"WSDL":None})),
3674 UL([LI(A("%s: %s" % (method, doc or ''),
3675 _href=URL(r=request,f="call/soap",vars={'op': method})))
3676 for method, doc in dispatcher.list_methods()]),
3677 ]
3678 return {'body': body}
3679
3681 """
3682 register services with:
3683 service = Service(globals())
3684 @service.run
3685 @service.rss
3686 @service.json
3687 @service.jsonrpc
3688 @service.xmlrpc
3689 @service.jsonrpc
3690 @service.amfrpc
3691 @service.amfrpc3('domain')
3692 @service.soap('Method', returns={'Result':int}, args={'a':int,'b':int,})
3693
3694 expose services with
3695
3696 def call(): return service()
3697
3698 call services with
3699 http://..../app/default/call/run?[parameters]
3700 http://..../app/default/call/rss?[parameters]
3701 http://..../app/default/call/json?[parameters]
3702 http://..../app/default/call/jsonrpc
3703 http://..../app/default/call/xmlrpc
3704 http://..../app/default/call/amfrpc
3705 http://..../app/default/call/amfrpc3
3706 http://..../app/default/call/soap
3707 """
3708
3709 request = self.environment['request']
3710 if len(request.args) < 1:
3711 raise HTTP(404, "Not Found")
3712 arg0 = request.args(0)
3713 if arg0 == 'run':
3714 return self.serve_run(request.args[1:])
3715 elif arg0 == 'rss':
3716 return self.serve_rss(request.args[1:])
3717 elif arg0 == 'csv':
3718 return self.serve_csv(request.args[1:])
3719 elif arg0 == 'xml':
3720 return self.serve_xml(request.args[1:])
3721 elif arg0 == 'json':
3722 return self.serve_json(request.args[1:])
3723 elif arg0 == 'jsonrpc':
3724 return self.serve_jsonrpc()
3725 elif arg0 == 'xmlrpc':
3726 return self.serve_xmlrpc()
3727 elif arg0 == 'amfrpc':
3728 return self.serve_amfrpc()
3729 elif arg0 == 'amfrpc3':
3730 return self.serve_amfrpc(3)
3731 elif arg0 == 'soap':
3732 return self.serve_soap()
3733 else:
3734 self.error()
3735
3737 raise HTTP(404, "Object does not exist")
3738
3739
3741 """
3742 Executes a task on completion of the called action. For example:
3743
3744 from gluon.tools import completion
3745 @completion(lambda d: logging.info(repr(d)))
3746 def index():
3747 return dict(message='hello')
3748
3749 It logs the output of the function every time input is called.
3750 The argument of completion is executed in a new thread.
3751 """
3752 def _completion(f):
3753 def __completion(*a,**b):
3754 d = None
3755 try:
3756 d = f(*a,**b)
3757 return d
3758 finally:
3759 thread.start_new_thread(callback,(d,))
3760 return __completion
3761 return _completion
3762
3764 try:
3765 dt = datetime.datetime.now() - d
3766 except:
3767 return ''
3768 if dt.days >= 2*365:
3769 return T('%d years ago') % int(dt.days / 365)
3770 elif dt.days >= 365:
3771 return T('1 year ago')
3772 elif dt.days >= 60:
3773 return T('%d months ago') % int(dt.days / 30)
3774 elif dt.days > 21:
3775 return T('1 month ago')
3776 elif dt.days >= 14:
3777 return T('%d weeks ago') % int(dt.days / 7)
3778 elif dt.days >= 7:
3779 return T('1 week ago')
3780 elif dt.days > 1:
3781 return T('%d days ago') % dt.days
3782 elif dt.days == 1:
3783 return T('1 day ago')
3784 elif dt.seconds >= 2*60*60:
3785 return T('%d hours ago') % int(dt.seconds / 3600)
3786 elif dt.seconds >= 60*60:
3787 return T('1 hour ago')
3788 elif dt.seconds >= 2*60:
3789 return T('%d minutes ago') % int(dt.seconds / 60)
3790 elif dt.seconds >= 60:
3791 return T('1 minute ago')
3792 elif dt.seconds > 1:
3793 return T('%d seconds ago') % dt.seconds
3794 elif dt.seconds == 1:
3795 return T('1 second ago')
3796 else:
3797 return T('now')
3798
3807 lock1=thread.allocate_lock()
3808 lock2=thread.allocate_lock()
3809 lock1.acquire()
3810 thread.start_new_thread(f,())
3811 a=PluginManager()
3812 a.x=5
3813 lock1.release()
3814 lock2.acquire()
3815 return a.x
3816
3818 """
3819
3820 Plugin Manager is similar to a storage object but it is a single level singleton
3821 this means that multiple instances within the same thread share the same attributes
3822 Its constructor is also special. The first argument is the name of the plugin you are defining.
3823 The named arguments are parameters needed by the plugin with default values.
3824 If the parameters were previous defined, the old values are used.
3825
3826 For example:
3827
3828 ### in some general configuration file:
3829 >>> plugins = PluginManager()
3830 >>> plugins.me.param1=3
3831
3832 ### within the plugin model
3833 >>> _ = PluginManager('me',param1=5,param2=6,param3=7)
3834
3835 ### where the plugin is used
3836 >>> print plugins.me.param1
3837 3
3838 >>> print plugins.me.param2
3839 6
3840 >>> plugins.me.param3 = 8
3841 >>> print plugins.me.param3
3842 8
3843
3844 Here are some tests:
3845
3846 >>> a=PluginManager()
3847 >>> a.x=6
3848 >>> b=PluginManager('check')
3849 >>> print b.x
3850 6
3851 >>> b=PluginManager() # reset settings
3852 >>> print b.x
3853 <Storage {}>
3854 >>> b.x=7
3855 >>> print a.x
3856 7
3857 >>> a.y.z=8
3858 >>> print b.y.z
3859 8
3860 >>> test_thread_separation()
3861 5
3862 >>> plugins=PluginManager('me',db='mydb')
3863 >>> print plugins.me.db
3864 mydb
3865 >>> print 'me' in plugins
3866 True
3867 >>> print plugins.me.installed
3868 True
3869 """
3870 instances = {}
3884 - def __init__(self,plugin=None,**defaults):
3891 if not key in self.__dict__:
3892 self.__dict__[key] = Storage()
3893 return self.__dict__[key]
3895 return self.__dict__.keys()
3897 return key in self.__dict__
3898
3899 if __name__ == '__main__':
3900 import doctest
3901 doctest.testmod()
3902