2014年5月1日木曜日

Pyramid 1.5 + jinja2-alchemy-starter で authentication

Pyramid 1.5 で、Pyramid 1.5 + jinja2-alchemy-starter で pcreate と Pyramid 1.5 + jinja2-alchemy-starter で @view_config() で作成したプロジェクトを修正します。

今回は、ログインしないと表示できないページにしてみます。
まず、データベースは使わないで、ソースの中に ID と password を持つようにします。
  1. myproject/__init__.py の修正
    インポート部分にいくつか追加します。
    #!/usr/bin/env python
    # coding: UTF-8
    from pyramid.config import Configurator
    from pyramid_jinja2 import renderer_factory
    from sqlalchemy import engine_from_config
    from pyramid.session import UnencryptedCookieSessionFactoryConfig
    from pyramid.authentication import AuthTktAuthenticationPolicy
    from pyramid.authorization import ACLAuthorizationPolicy
    from myproject.security import groupfinder
    from myproject.models import (
        initialize_sql,
        DBSession,
        Base,
        )
    

  2. myproject/__init__.py の修正
    UnencryptedCookieSessionFactoryConfig をインポートの次に追加します。
    'something_so_secret_strings' はそれなりの文字列にしておいてください。
    my_session_factory = UnencryptedCookieSessionFactoryConfig('something_so_secret_strings')
    

  3. myproject/__init__.py の修正
    main() の修正
    <修正前>
        #SQLAlchemy engine config for main DB 
        #Any setting that begins with 'sqlalchemy.' will be picked up
        db_engine = engine_from_config(settings,'sqlalchemy.')
        #Binding engine to the model
        initialize_sql(db_engine)
        
        config = Configurator(settings=settings)
        
        config.include('pyramid_jinja2')
        config.add_renderer(".html", "pyramid_jinja2.renderer_factory")
    
        #The views/routes are added here
        config.add_static_view('static', 'static')
        
        config.add_route('home', '/')
        config.add_route('next_page', '/next')
        config.scan()
        
        return config.make_wsgi_app()
    

    <修正後>
    'sosecret' はそれなりの文字列にしておいてください。
        #SQLAlchemy engine config for main DB 
        #Any setting that begins with 'sqlalchemy.' will be picked up
        db_engine = engine_from_config(settings, 'sqlalchemy.')
        #Binding engine to the model
        initialize_sql(db_engine)
        
    
        authn_policy = AuthTktAuthenticationPolicy(
            'sosecret', callback=groupfinder, hashalg='sha512')
        authz_policy = ACLAuthorizationPolicy()
    
        config = Configurator(settings=settings,
                              root_factory='myproject.models.RootFactory',
                              session_factory=my_session_factory)
    
        
        config.include('pyramid_jinja2')
        config.add_renderer(".html", "pyramid_jinja2.renderer_factory")
    
    
        config.set_authentication_policy(authn_policy)
        config.set_authorization_policy(authz_policy)
    
        
        #The views/routes are added here
        config.add_static_view('static', 'static')
        
        config.add_route('home', '/')
        config.add_route('next_page', '/next')
    
        config.add_route('login', '/login')
        config.add_route('logout', '/logout')
    
        config.scan()
        
        return config.make_wsgi_app()
    

  4. pypwm/views.py の修正
    <修正前>
    #!/usr/bin/env python
    # coding: UTF-8
    from pyramid.i18n import TranslationStringFactory
    from myproject.models import DBSession
    from pyramid.view import view_config
    
    _ = TranslationStringFactory('MyProject')
    
    @view_config(route_name='home', renderer='home.html')
    def home(request):
        page_name = 'home'
        return {'page_name':page_name, 'next_uri':'/next', 'next_name':'next_page'}
    
    @view_config(route_name='next_page', renderer='home.html')
    def next_page(request):
        page_name = 'next_page'
        return {'page_name':'page_name', 'next_uri':'/', 'next_name':'home'}
    

    <修正後>ほとんど全面的に修正。
    #!/usr/bin/env python
    # coding: UTF-8
    from pyramid.i18n import TranslationStringFactory
    from myproject.models import DBSession
    from pyramid.httpexceptions import HTTPFound
    from pyramid.response import Response
    from pyramid.view import (
        view_config,
        forbidden_view_config,
        )
    from pyramid.security import (
        remember,
        forget,
        authenticated_userid,
        )
    from .security import USERS
    
    _ = TranslationStringFactory('MyProject')
    
    @view_config(route_name='home', renderer='home.html', permission='edit', request_method="GET")
    def home(request):
        page_name = 'home'
        return {'page_name':page_name, 'next_uri':'/next', 'next_name':'next_page'}
    
    @view_config(route_name='next_page', renderer='home.html', permission='edit', request_method="GET")
    def next_page(request):
        page_name = 'next_page'
        return {'page_name':'page_name', 'next_uri':'/', 'next_name':'home'}
    
    @view_config(route_name='login', renderer='login.html')
    @forbidden_view_config(renderer='login.html')
    def login(request):
        login_url = request.route_url('login')
        referrer = request.url
        if referrer == login_url:
            referrer = '/' # never use the login form itself as came_from
        came_from = request.params.get('came_from', referrer)
        message = ''
        login = ''
        password = ''
        if 'form.submitted' in request.params:
            login = request.params['login']
            password = request.params['password']
            if USERS.get(login) == password:
                headers = remember(request, login)
                return HTTPFound(location = came_from,
                                 headers = headers)
            message = 'Failed login'
    
        return dict(
            message = message,
            url = request.application_url + '/login',
            came_from = came_from,
            login = login,
            password = password,
            )
    
    @view_config(route_name='logout')
    def logout(request):
        headers = forget(request)
        return HTTPFound(location = request.route_url('home'), headers = headers)
    

  5. myproject/security.py を追加
    #!/usr/bin/env python
    # coding: UTF-8
    USERS = {
             'editor':'editor',
             'viewer':'viewer'
             }
    GROUPS = {
              'editor':['group:editors'],
              'viewer':['group:viewers'],
              }
    
    def groupfinder(userid, request):
        if userid in USERS:
            return GROUPS.get(userid, [])
    

  6. myproject/models.py の修正
    <修正前>
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import sessionmaker,scoped_session
    from zope.sqlalchemy import ZopeTransactionExtension
    
    DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
    Base = declarative_base()
    
    #Define your models here
    #class MyModel(Base):
    #    pass
    
    def initialize_sql(engine):
        DBSession.configure(bind=engine)
    

    <修正後>
    #!/usr/bin/env python
    # coding: UTF-8
    from pyramid.security import (
        Allow,
        Everyone,
        )
    from sqlalchemy import (
        Column,
        Integer,
        Text,
        )
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import (
        scoped_session,
        sessionmaker,
        )
    from zope.sqlalchemy import ZopeTransactionExtension
    
    DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
    Base = declarative_base()
    
    class MyModel(Base):
        __tablename__ = 'models'
        id = Column(Integer, primary_key=True)
        name = Column(Text, unique=True)
        value = Column(Integer)
    
        def __init__(self, name, value):
            self.name = name
            self.value = value
    
    class User(Base):
        """ The SQLAlchemy declarative model class for a User object. """
        __tablename__ = 'users'
        id = Column(Integer, primary_key=True)
        name = Column(Text, unique=True)
        password = Column(Text)
    
        def __init__(self, name, password):
            self.name = name
            self.password = password
    
    class RootFactory(object):
        __acl__ = [ (Allow, Everyone, 'view'),
                    (Allow, 'group:editors', 'edit') ]
        def __init__(self, request):
            pass
    
    def initialize_sql(engine):
        DBSession.configure(bind=engine)
    

  7. テンプレート home.html の修正
    <br /><a href="{{ request.application_url }}/logout">Logout</a> を body 内に追加します。
    <html>
    <head>
        <title>{{ page_name }}</title>
        <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
        <link rel="shortcut icon" href="{{request.application_url}}/static/favicon.ico" />
        <link rel="stylesheet" href="{{request.application_url}}/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
    </head>
    <body>
      This page name is '{{ page_name }}'.<br />
      <a href="{{ request.application_url }}{{ next_uri }}">{{ next_name }}</a>
      <br />
      <a href="{{ request.application_url }}/logout">Logout</a>
    </body>
    </html>
    

  8. テンプレートの追加
    myproject/templates/login.html を追加します。
    <!DOCTYPE html>
    <html>
    <head>
      <title>Login</title>
      <meta charset="utf-8">
      <link rel="stylesheet" href="{{request.application_url}}/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
    </head>
    <body>
      <form action="{{ url }}" method="post" id="id_login_form">
        <input type="text" name="login" value="{{ login }}"
            placeholder="login name" autofocus required/><br/>
        <input type="password" name="password" value="{{ password }}"
            placeholder="password" required /><br/>
        <input type="hidden" name="came_from" value="{{ came_from }}" />
        <input type="submit" name="form.submitted" value="Log In" />
      </form>
    </body>
    </html>

  9. おしまい
これで実行するとログインフォームが表示されます。
ログイン前に localhost:6543/next を表示しようとしても、ログインフォームが表示されます。
localhost:6543/next からログインフォームでログインすると、ちゃんと localhost:6543/next のページになります。
どこかのサイトを参考にしたのですが、その URL はわからなくなってしまいました。

次回は、ひとまず sqlite にしてみます。それから mysql ですね。

0 件のコメント:

コメントを投稿