今回は、そのウェブアプリにログインページを追加する。
- __init__.py
auth なんとか、というのがいくつか追加されている。
login/logout の config.add_route も追加。
#!/usr/bin/env python # coding: UTF-8 from pyramid.config import Configurator from pyramid.session import UnencryptedCookieSessionFactoryConfig from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from sqlalchemy import engine_from_config from pypwm.security import groupfinder from .models import ( DBSession, Base, ) my_session_factory = UnencryptedCookieSessionFactoryConfig('something_so_secret_strings') def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config = Configurator(settings=settings, root_factory='pypwm.models.RootFactory', session_factory=my_session_factory) config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') config.add_route('home_org', '/home_org') config.add_route('update_slider_pwm', '/update_slider_pwm') config.add_route('login', '/login') config.add_route('logout', '/logout') config.add_renderer(".html", "pyramid.mako_templating.renderer_factory") config.scan() return config.make_wsgi_app()
- models.py
User と RootFactory Class が追加されている。
#!/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
- security.py
これは新規に追加。ほんとうは、ここでデーターベースを参照してそれなりの値を返す?
今回は、ログインID が user 、パスワードが password にしている。
#!/usr/bin/env python # coding: UTF-8 USERS = { 'user': 'password', } GROUPS = { 'user':['group:editors'] } def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, [])
- views.py
import の追加・修正、login/logout 処理の追加。
そして、@view_config に permission='edit' を追加している。これだけで、この関数にセッション管理などの必要な処理が追加される。
#!/usr/bin/env python # coding: UTF-8 from pyramid.httpexceptions import HTTPFound from pyramid.response import Response from pyramid.view import ( view_config, forbidden_view_config, ) from sqlalchemy.exc import DBAPIError from .models import ( DBSession, MyModel, User, ) from pyramid.security import ( remember, forget, authenticated_userid, ) from .security import USERS import time, string, os os_name = os.uname() if os_name[0] == 'Darwin': fname = '/Users/pi/env_pwm/PyPWM/pypwm/pwm.prefs' elif os_name[0] == 'Linux': fname = '/home/pi/env_pwm/PyPWM/pypwm/pwm.prefs' @view_config(route_name='home_org', renderer='templates/mytemplate.pt') def my_view(request): try: one = DBSession.query(MyModel).filter(MyModel.name == 'one').first() except DBAPIError: return Response(conn_err_msg, content_type='text/plain', status_int=500) return {'one': one, 'project': 'PyPWM'} conn_err_msg = """\ Pyramid is having a problem using your SQL database. The problem might be caused by one of the following things: 1. You may need to run the "initialize_PyPWM_db" script to initialize your database tables. Check your virtual environment's "bin" directory for this script and try to run it. 2. Your database server may not be running. Check that the database server referred to by the "sqlalchemy.url" setting in your "development.ini" file is running. After you fix the problem, please restart the Pyramid application to try it again. """ #---- pwm @view_config(route_name='home', renderer='index.html', permission='edit') def home(request): pwm_dict = {} pwm_val = read_prefs() pwm_dict['pwm_val'] = pwm_val return dict(pwm_dict=pwm_dict) @view_config(route_name='update_slider_pwm', xhr=True, renderer='json', permission='edit') def update_slider_pwm(request): """ This function is call from ajax_slider_pwm.js send_option_request when slider is updated. receive json pwm data by ajax "pwm_val": slider_val, "channel": channel, "command": command command is 'read' or 'write' """ pwm_val = request.GET.get('pwm_val') pwm_val = to_int(pwm_val) channel = request.GET.get('channel') channel = to_int(channel) command = request.GET.get('command') if command == 'read': pwm_val = read_prefs() elif command == 'write': write_prefs(pwm_val) return { 'pwm_val': pwm_val, 'channel': channel, 'command': command, } def read_prefs(): kMinVal = 0 kMaxVal = 1999 pwm_val = 0 fd = None try: fd = open(fname, 'r') except IOError: fd = None if fd is not None: line = fd.readline() pwm_val = string.strip(line) try: pwm_val = int(pwm_val) except ValueError: pwm_val = 0 if pwm_val < kMinVal: pwm_val = kMinVal if pwm_val > kMaxVal: pwm_val = kMaxVal fd.close() return pwm_val def write_prefs(pwm_val): my_error_count = 0 fin = False while (not fin): try: fd = open(fname, 'w') except IOError: fd = None if fd is not None: fd.write(str(pwm_val)) fd.close() fin = True else: if my_error_count < 10: my_error_count += 1 time.sleep(0.1) else: fin = True def to_int(the_str): try: int_value = int(the_str) except: int_value = 0 return int_value #---- login/logout @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)
- templates/login.html
新規追加。
## for mako <!DOCTYPE html> <html> <head> <title>PyPWM Login</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="keywords" content="python web application" /> <meta name="description" content="pyramid web application" /> <link rel="shortcut icon" href="${request.static_url('pypwm:static/favicon.ico')}" /> <link rel="stylesheet" href="${request.static_url('pypwm:static/jquery/login.css')}" /> <script type="text/javascript"></script> </head> <body> <div id="wrap"> <div id="top-small"> <div class="top-small align-center"> <div> </div> </div> </div> <div id="middle"> <div class="middle align-right"> <div id="left" class="app-welcome align-left"> <span style="background-color: #FFFF00">${message}</span> </div> <div id="right" class="app-welcome align-right"></div> </div> </div> <div id="bottom"> <div class="bottom"> <form action="${url}" method="post" id="id_login_form"> <input type="hidden" name="came_from" value="${came_from}" /> <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="submit" name="form.submitted" value="Log In" /> </form> </div> </div> </div> <div id="footer"> <div class="footer">© Copyright 2013, .</div> </div> </body> </html>
- login.css
#id_login_form { width: 220px; height: 155px; position: absolute; left: 50%; top: 30%; margin-left: -110px; margin-top: -75px; } #id_login_form input[type="text"],#id_login_form input[type="password"] { width: 100%; height: 40px; margin-top: 7px; font-size: 14px; color: #444; outline: none; background-color: #fff; border-radius: 6px; border: 1px solid rgba(0, 0, 0, .49); } /* button */ #id_login_form input[type="submit"] { width: 100%; height: 50px; margin-top: 7px; color: #fff; font-size: 18px; font-weight: bold; outline: none; background-color: #5466da; border-radius: 6px; border: 1px solid rgba(0, 0, 0, .49); } #id_login_form input[type="submit"]:active { background-color: #7588e1; }