Changeset - a3d9d24acbec
[Not reviewed]
celery
0 10 2
Marcin Kuzminski - 15 years ago 2010-09-13 01:27:41
marcin@python-works.com
Implemented password reset(forms/models/ tasks) and mailing tasks.
Added smtp mailer, configurations, cleaned user model
12 files changed with 415 insertions and 35 deletions:
0 comments (0 inline, 0 general)
celeryconfig.py
Show inline comments
 
@@ -8,6 +8,7 @@ CELERY_IMPORTS = ("pylons_app.lib.celery
 
CELERY_RESULT_BACKEND = "database"
 
CELERY_RESULT_DBURI = "sqlite:///hg_app.db"
 

	
 
BROKER_CONNECTION_MAX_RETRIES = 30
 

	
 
## Broker settings.
 
BROKER_HOST = "localhost"
development.ini
Show inline comments
 
################################################################################
 
################################################################################
 
# pylons_app - Pylons environment configuration                                #
 
# hg-app - Pylons environment configuration                                    #
 
#                                                                              # 
 
# The %(here)s variable will be replaced with the parent directory of this file#
 
################################################################################
 

	
 
[DEFAULT]
 
debug = true
 
############################################
 
## Uncomment and replace with the address ##
 
## which should receive any error reports ##
 
############################################
 
################################################################################
 
## Uncomment and replace with the address which should receive                ## 
 
## any error reports after application crash								  ##
 
## Additionally those settings will be used by hg-app mailing system          ##
 
################################################################################
 
#email_to = admin@localhost
 
#error_email_from = paste_error@localhost
 
#app_email_from = hg-app-noreply@localhost
 
#error_message =
 

	
 
#smtp_server = mail.server.com
 
#error_email_from = paste_error@localhost
 
#smtp_username = 
 
#smtp_password = 
 
#error_message = 'mercurial crash !'
 
#smtp_port = 
 
#smtp_use_tls = 
 

	
 
[server:main]
 
##nr of threads to spawn
 
threadpool_workers = 5
 

	
 
##max request before
 
threadpool_max_requests = 2
 
threadpool_max_requests = 6
 

	
 
##option to use threads of process
 
use_threadpool = true
 
use_threadpool = false
 

	
 
use = egg:Paste#http
 
host = 127.0.0.1
pylons_app/config/routing.py
Show inline comments
 
@@ -110,10 +110,11 @@ def make_map(config):
 
    #SEARCH
 
    map.connect('search', '/_admin/search', controller='search')
 
    
 
    #LOGIN/LOGOUT
 
    #LOGIN/LOGOUT/REGISTER/SIGN IN
 
    map.connect('login_home', '/_admin/login', controller='login')
 
    map.connect('logout_home', '/_admin/logout', controller='login', action='logout')
 
    map.connect('register', '/_admin/register', controller='login', action='register')
 
    map.connect('reset_password', '/_admin/password_reset', controller='login', action='password_reset')
 
        
 
    #FEEDS
 
    map.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
pylons_app/controllers/login.py
Show inline comments
 
@@ -28,7 +28,9 @@ from pylons import request, response, se
 
from pylons.controllers.util import abort, redirect
 
from pylons_app.lib.auth import AuthUser, HasPermissionAnyDecorator
 
from pylons_app.lib.base import BaseController, render
 
from pylons_app.model.forms import LoginForm, RegisterForm
 
import pylons_app.lib.helpers as h 
 
from pylons.i18n.translation import _
 
from pylons_app.model.forms import LoginForm, RegisterForm, PasswordResetForm
 
from pylons_app.model.user_model import UserModel
 
import formencode
 
import logging
 
@@ -99,6 +101,8 @@ class LoginController(BaseController):
 
                form_result = register_form.to_python(dict(request.POST))
 
                form_result['active'] = c.auto_active
 
                user_model.create_registration(form_result)
 
                h.flash(_('You have successfully registered into hg-app'),
 
                            category='success')                
 
                return redirect(url('login_home'))
 
                               
 
            except formencode.Invalid as errors:
 
@@ -111,6 +115,28 @@ class LoginController(BaseController):
 
        
 
        return render('/register.html')
 
    
 
    def password_reset(self):
 
        user_model = UserModel()
 
        if request.POST:
 
                
 
            password_reset_form = PasswordResetForm()()
 
            try:
 
                form_result = password_reset_form.to_python(dict(request.POST))
 
                user_model.reset_password(form_result)
 
                h.flash(_('Your new password was sent'),
 
                            category='success')                 
 
                return redirect(url('login_home'))
 
                               
 
            except formencode.Invalid as errors:
 
                return htmlfill.render(
 
                    render('/password_reset.html'),
 
                    defaults=errors.value,
 
                    errors=errors.error_dict or {},
 
                    prefix_error=False,
 
                    encoding="UTF-8")
 
        
 
        return render('/password_reset.html')
 
        
 
    def logout(self):
 
        session['hg_app_user'] = AuthUser()
 
        session.save()
pylons_app/lib/auth.py
Show inline comments
 
@@ -34,9 +34,36 @@ from sqlalchemy.orm.exc import NoResultF
 
import bcrypt
 
from decorator import decorator
 
import logging
 
import random
 

	
 
log = logging.getLogger(__name__) 
 

	
 
class PasswordGenerator(object):
 
    """This is a simple class for generating password from
 
        different sets of characters
 
        usage:
 
        passwd_gen = PasswordGenerator()
 
        #print 8-letter password containing only big and small letters of alphabet
 
        print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)        
 
    """
 
    ALPHABETS_NUM = r'''1234567890'''#[0]
 
    ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
 
    ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
 
    ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''    #[3]
 
    ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
 
    ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
 
    ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
 
    ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
 
    ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
 
            
 
    def __init__(self, passwd=''):
 
        self.passwd = passwd
 

	
 
    def gen_password(self, len, type):
 
        self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
 
        return self.passwd
 

	
 
    
 
def get_crypt_password(password):
 
    """Cryptographic function used for password hashing based on sha1
 
    @param password: password to hash
pylons_app/lib/celerylib/__init__.py
Show inline comments
 
from vcs.utils.lazy import LazyProperty
 
import logging
 
import os
 
import sys
 
import traceback
 

	
 
log = logging.getLogger(__name__)
 

	
 
@@ -11,14 +14,13 @@ class ResultWrapper(object):
 
    def result(self):
 
        return self.task
 

	
 
def run_task(task,async,*args,**kwargs):
 
def run_task(task,*args,**kwargs):
 
    try:
 
        t = task.delay(*args,**kwargs)
 
        log.info('running task %s',t.task_id)
 
        if not async:
 
            t.wait()
 
        return t
 
    except:
 
        log.error(traceback.format_exc())
 
        #pure sync version
 
        return ResultWrapper(task(*args,**kwargs))
 
    
 
\ No newline at end of file
pylons_app/lib/celerylib/tasks.py
Show inline comments
 
from celery.decorators import task
 
from celery.task.sets import subtask
 
from datetime import datetime, timedelta
 
from os.path import dirname as dn
 
from pylons.i18n.translation import _
 
from pylons_app.lib.celerylib import run_task
 
from pylons_app.lib.helpers import person
 
from pylons_app.lib.smtp_mailer import SmtpMailer
 
from pylons_app.lib.utils import OrderedDict
 
from time import mktime
 
from vcs.backends.hg import MercurialRepository
 
import ConfigParser
 
import calendar
 
import logging
 
from vcs.backends.hg import MercurialRepository
 
import os
 
import traceback
 

	
 

	
 
root = dn(dn(dn(dn(os.path.realpath(__file__)))))
 
config = ConfigParser.ConfigParser({'here':root})
 
config.read('%s/development.ini' % root)
 

	
 
__all__ = ['whoosh_index', 'get_commits_stats',
 
           'reset_user_password', 'send_email']
 

	
 
def get_session():
 
    from sqlalchemy import engine_from_config
 
    from sqlalchemy.orm import sessionmaker, scoped_session
 
    engine = engine_from_config(dict(config.items('app:main')), 'sqlalchemy.db1.')
 
    sa = scoped_session(sessionmaker(bind=engine))
 
    return sa
 

	
 
def get_hg_settings():
 
    from pylons_app.model.db import HgAppSettings
 
    try:
 
        sa = get_session()
 
        ret = sa.query(HgAppSettings).all()
 
    finally:
 
        sa.remove()
 

	
 
log = logging.getLogger(__name__)
 
    if not ret:
 
        raise Exception('Could not get application settings !')
 
    settings = {}
 
    for each in ret:
 
        settings['hg_app_' + each.app_settings_name] = each.app_settings_value    
 
    
 
    return settings
 

	
 
def get_hg_ui_settings():
 
    from pylons_app.model.db import HgAppUi
 
    try:
 
        sa = get_session()
 
        ret = sa.query(HgAppUi).all()
 
    finally:
 
        sa.remove()
 

	
 
@task()
 
    if not ret:
 
        raise Exception('Could not get application ui settings !')
 
    settings = {}
 
    for each in ret:
 
        k = each.ui_key
 
        v = each.ui_value
 
        if k == '/':
 
            k = 'root_path'
 
        
 
        if k.find('.') != -1:
 
            k = k.replace('.', '_')
 
        
 
        if each.ui_section == 'hooks':
 
            v = each.ui_active
 
        
 
        settings[each.ui_section + '_' + k] = v  
 
    
 
    return settings   
 

	
 
@task
 
def whoosh_index(repo_location,full_index):
 
    log = whoosh_index.get_logger()
 
    from pylons_app.lib.indexers import DaemonLock
 
    from pylons_app.lib.indexers.daemon import WhooshIndexingDaemon,LockHeld
 
    try:
 
@@ -23,10 +87,12 @@ def whoosh_index(repo_location,full_inde
 
        log.info('LockHeld')
 
        return 'LockHeld'    
 

	
 
@task()
 
@task
 
def get_commits_stats(repo):
 
    log = get_commits_stats.get_logger()
 
    aggregate = OrderedDict()
 
    repo = MercurialRepository('/home/marcink/hg_repos/'+repo)
 
    repos_path = get_hg_ui_settings()['paths_root_path'].replace('*','')
 
    repo = MercurialRepository(repos_path + repo)
 
    #graph range
 
    td = datetime.today() + timedelta(days=1) 
 
    y, m, d = td.year, td.month, td.day
 
@@ -90,3 +156,60 @@ def get_commits_stats(repo):
 
            % (author_key_cleaner(repo.contact),
 
               author_key_cleaner(repo.contact))
 
    return (ts_min, ts_max, d)    
 

	
 
@task
 
def reset_user_password(user_email):
 
    log = reset_user_password.get_logger()
 
    from pylons_app.lib import auth
 
    from pylons_app.model.db import User
 
    
 
    try:
 
        
 
        try:
 
            sa = get_session()
 
            user = sa.query(User).filter(User.email == user_email).scalar()
 
            new_passwd = auth.PasswordGenerator().gen_password(8,
 
                             auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
 
            user.password = auth.get_crypt_password(new_passwd)
 
            sa.add(user)
 
            sa.commit()
 
            log.info('change password for %s', user_email)
 
            if new_passwd is None:
 
                raise Exception('unable to generate new password')
 
            
 
        except:
 
            log.error(traceback.format_exc())
 
            sa.rollback()
 
        
 
        run_task(send_email, user_email,
 
                 "Your new hg-app password",
 
                 'Your new hg-app password:%s' % (new_passwd))
 
        log.info('send new password mail to %s', user_email)
 
        
 
        
 
    except:
 
        log.error('Failed to update user password')
 
        log.error(traceback.format_exc())
 
    return True
 

	
 
@task    
 
def send_email(recipients, subject, body):
 
    log = send_email.get_logger()
 
    email_config = dict(config.items('DEFAULT')) 
 
    mail_from = email_config.get('app_email_from')
 
    user = email_config.get('smtp_username')
 
    passwd = email_config.get('smtp_password')
 
    mail_server = email_config.get('smtp_server')
 
    mail_port = email_config.get('smtp_port')
 
    tls = email_config.get('smtp_use_tls')
 
    ssl = False
 
    
 
    try:
 
        m = SmtpMailer(mail_from, user, passwd, mail_server, 
 
                       mail_port, ssl, tls)
 
        m.send(recipients, subject, body)  
 
    except:
 
        log.error('Mail sending failed')
 
        log.error(traceback.format_exc())
 
        return False
 
    return True
pylons_app/lib/smtp_mailer.py
Show inline comments
 
new file 100644
 
import logging
 
import smtplib
 
import mimetypes
 
from email.mime.multipart import MIMEMultipart
 
from email.mime.image import MIMEImage
 
from email.mime.audio import MIMEAudio
 
from email.mime.base import MIMEBase
 
from email.mime.text import MIMEText
 
from email.utils import formatdate
 
from email import encoders
 

	
 
class SmtpMailer(object):
 
    """simple smtp mailer class
 
    
 
    mailer = SmtpMailer(mail_from, user, passwd, mail_server, mail_port, ssl, tls)
 
    mailer.send(recipients, subject, body, attachment_files)    
 
    
 
    :param recipients might be a list of string or single string
 
    :param attachment_files is a dict of {filename:location} 
 
    it tries to guess the mimetype and attach the file
 
    """
 

	
 
    def __init__(self, mail_from, user, passwd, mail_server,
 
                    mail_port=None, ssl=False, tls=False):
 
        
 
        self.mail_from = mail_from
 
        self.mail_server = mail_server
 
        self.mail_port = mail_port
 
        self.user = user
 
        self.passwd = passwd
 
        self.ssl = ssl
 
        self.tls = tls
 
        self.debug = False
 
        
 
    def send(self, recipients=[], subject='', body='', attachment_files={}):
 

	
 
        if isinstance(recipients, basestring):
 
            recipients = [recipients]
 
        if self.ssl:
 
            smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
 
        else:
 
            smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
 

	
 
        if self.tls:
 
            smtp_serv.starttls()
 
         
 
        if self.debug:    
 
            smtp_serv.set_debuglevel(1)
 

	
 
        smtp_serv.ehlo("mailer")
 

	
 
        #if server requires authorization you must provide login and password
 
        smtp_serv.login(self.user, self.passwd)
 

	
 
        date_ = formatdate(localtime=True)
 
        msg = MIMEMultipart()
 
        msg['From'] = self.mail_from
 
        msg['To'] = ','.join(recipients)
 
        msg['Date'] = date_
 
        msg['Subject'] = subject
 
        msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
 

	
 
        msg.attach(MIMEText(body))
 

	
 
        if attachment_files:
 
            self.__atach_files(msg, attachment_files)
 

	
 
        smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
 
        logging.info('MAIL SEND TO: %s' % recipients)
 
        smtp_serv.quit()
 

	
 

	
 
    def __atach_files(self, msg, attachment_files):
 
        if isinstance(attachment_files, dict):
 
            for f_name, msg_file in attachment_files.items():
 
                ctype, encoding = mimetypes.guess_type(f_name)
 
                logging.info("guessing file %s type based on %s" , ctype, f_name)
 
                if ctype is None or encoding is not None:
 
                    # No guess could be made, or the file is encoded (compressed), so
 
                    # use a generic bag-of-bits type.
 
                    ctype = 'application/octet-stream'
 
                maintype, subtype = ctype.split('/', 1)
 
                if maintype == 'text':
 
                    # Note: we should handle calculating the charset
 
                    file_part = MIMEText(self.get_content(msg_file), 
 
                                         _subtype=subtype)
 
                elif maintype == 'image':
 
                    file_part = MIMEImage(self.get_content(msg_file), 
 
                                          _subtype=subtype)
 
                elif maintype == 'audio':
 
                    file_part = MIMEAudio(self.get_content(msg_file), 
 
                                          _subtype=subtype)
 
                else:
 
                    file_part = MIMEBase(maintype, subtype)
 
                    file_part.set_payload(self.get_content(msg_file))
 
                    # Encode the payload using Base64
 
                    encoders.encode_base64(msg)
 
                # Set the filename parameter
 
                file_part.add_header('Content-Disposition', 'attachment', 
 
                                     filename=f_name)
 
                file_part.add_header('Content-Type', ctype, name=f_name)
 
                msg.attach(file_part)
 
        else:
 
            raise Exception('Attachment files should be' 
 
                            'a dict in format {"filename":"filepath"}')    
 

	
 
    def get_content(self, msg_file):
 
        '''
 
        Get content based on type, if content is a string do open first
 
        else just read because it's a probably open file object
 
        @param msg_file:
 
        '''
 
        if isinstance(msg_file, str):
 
            return open(msg_file, "rb").read()
 
        else:
 
            #just for safe seek to 0
 
            msg_file.seek(0)
 
            return msg_file.read()
pylons_app/model/forms.py
Show inline comments
 
@@ -209,6 +209,19 @@ class ValidPath(formencode.validators.Fa
 
        raise formencode.Invalid(msg, value, state,
 
                                     error_dict={'paths_root_path':msg})            
 
                       
 
class ValidSystemEmail(formencode.validators.FancyValidator):
 
    def to_python(self, value, state):
 
        sa = meta.Session
 
        try:
 
            user = sa.query(User).filter(User.email == value).scalar()
 
            if  user is None:
 
                raise formencode.Invalid(_("That e-mail address doesn't exist.") ,
 
                                         value, state)
 
        finally:
 
            meta.Session.remove()
 
            
 
        return value     
 

	
 
#===============================================================================
 
# FORMS        
 
#===============================================================================
 
@@ -256,6 +269,12 @@ def UserForm(edit=False, old_data={}):
 

	
 
RegisterForm = UserForm
 
    
 
def PasswordResetForm():
 
    class _PasswordResetForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        email = All(ValidSystemEmail(), Email(not_empty=True))             
 
    return _PasswordResetForm
 
    
 
def RepoForm(edit=False, old_data={}):
 
    class _RepoForm(formencode.Schema):
pylons_app/model/user_model.py
Show inline comments
 
@@ -2,7 +2,7 @@
 
# encoding: utf-8
 
# Model for users
 
# Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
 
 
 
# 
 
# This program is free software; you can redistribute it and/or
 
# modify it under the terms of the GNU General Public License
 
# as published by the Free Software Foundation; version 2
 
@@ -23,10 +23,12 @@ Created on April 9, 2010
 
Model for users
 
@author: marcink
 
"""
 

	
 
from pylons_app.lib import auth
 
from pylons.i18n.translation import _
 
from pylons_app.lib.celerylib import tasks, run_task
 
from pylons_app.model.db import User
 
from pylons_app.model.meta import Session
 
from pylons.i18n.translation import _
 
import traceback
 
import logging
 
log = logging.getLogger(__name__)
 

	
 
@@ -54,8 +56,8 @@ class UserModel(object):
 
                
 
            self.sa.add(new_user)
 
            self.sa.commit()
 
        except Exception as e:
 
            log.error(e)
 
        except:
 
            log.error(traceback.format_exc())
 
            self.sa.rollback()
 
            raise      
 
    
 
@@ -68,8 +70,8 @@ class UserModel(object):
 
                
 
            self.sa.add(new_user)
 
            self.sa.commit()
 
        except Exception as e:
 
            log.error(e)
 
        except:
 
            log.error(traceback.format_exc())
 
            self.sa.rollback()
 
            raise      
 
    
 
@@ -88,8 +90,8 @@ class UserModel(object):
 
                
 
            self.sa.add(new_user)
 
            self.sa.commit()
 
        except Exception as e:
 
            log.error(e)
 
        except:
 
            log.error(traceback.format_exc())
 
            self.sa.rollback()
 
            raise      
 
        
 
@@ -109,13 +111,12 @@ class UserModel(object):
 
                
 
            self.sa.add(new_user)
 
            self.sa.commit()
 
        except Exception as e:
 
            log.error(e)
 
        except:
 
            log.error(traceback.format_exc())
 
            self.sa.rollback()
 
            raise 
 
                
 
    def delete(self, id):
 
        
 
        try:
 
            
 
            user = self.sa.query(User).get(id)
 
@@ -125,7 +126,10 @@ class UserModel(object):
 
                                  " crucial for entire application"))
 
            self.sa.delete(user)
 
            self.sa.commit()            
 
        except Exception as e:
 
            log.error(e)
 
        except:
 
            log.error(traceback.format_exc())
 
            self.sa.rollback()
 
            raise        
 

	
 
    def reset_password(self, data):
 
        run_task(tasks.reset_user_password, data['email'])
pylons_app/templates/login.html
Show inline comments
 
@@ -60,7 +60,7 @@
 
                    <!-- end fields -->
 
                    <!-- links -->
 
                    <div class="links">
 
                        ${h.link_to(_('Forgot your password ?'),h.url('#'))}
 
                        ${h.link_to(_('Forgot your password ?'),h.url('reset_password'))}
 
                        %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
 
	                         / 
 
	                        ${h.link_to(_("Don't have an account ?"),h.url('register'))}
pylons_app/templates/password_reset.html
Show inline comments
 
new file 100644
 
## -*- coding: utf-8 -*-
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
 
    <head>
 
        <title>${_('Reset You password to hg-app')}</title>
 
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
 
        <link rel="icon" href="/images/hgicon.png" type="image/png" />
 
        <meta name="robots" content="index, nofollow"/>
 
            
 
        <!-- stylesheets -->
 
        <link rel="stylesheet" type="text/css" href="/css/reset.css" />
 
        <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen" />
 
        <link id="color" rel="stylesheet" type="text/css" href="/css/colors/blue.css" />
 

	
 
        <!-- scripts -->
 

	
 
    </head>
 
    <body>
 
		<div id="register">
 
			
 
			<div class="title">
 
				<h5>${_('Reset You password to hg-app')}</h5>
 
                <div class="corner tl"></div>
 
                <div class="corner tr"></div>				
 
			</div>
 
			<div class="inner">
 
			    ${h.form(url('password_reset'))}
 
			    <div class="form">
 
			        <!-- fields -->
 
			        <div class="fields">
 
			            
 
			             <div class="field">
 
			                <div class="label">
 
			                    <label for="email">${_('Email address')}:</label>
 
			                </div>
 
			                <div class="input">
 
			                    ${h.text('email')}
 
			                </div>
 
			             </div>
 
			                        
 
			            <div class="buttons">
 
				            <div class="nohighlight">
 
				              ${h.submit('send','Reset my password',class_="ui-button ui-widget ui-state-default ui-corner-all")}
 
							  	<div class="activation_msg">${_('Your new password will be send to matching email address')}</div>
 
				            </div>
 
			            </div>             
 
			    	</div>
 
			    </div>
 
			    ${h.end_form()}
 
			</div>    
 
	    </div>
 
    </body>
 
</html>
 

	
0 comments (0 inline, 0 general)