【Python】FullCalendarにログイン機能をつけてみる
おはようございます。
今回はログイン画面を追加して、
ユーザー毎にカレンダーを持てるようにしてみました。
ちょっと長くなってしまったので、ソースはそのうち Github のあげる予定です。
プログラムは前回のものを流用します。
スポンサーリンク
目次
テーブルの追加、修正
ユーザ、パスワード用にそれぞれテーブルを追加します。
-- MSTパスワード create table MST_PASSWORD ( USER_CD varchar(20) not null comment 'ユーザーコード' , PASSWORD varchar(128) not null comment 'パスワード' , CREATE_USER varchar(20) comment '作成者' , CREATE_DATE datetime comment '作成日時' , UPDATE_USER varchar(20) comment '更新者' , UPDATE_DATE datetime comment '更新日時' , constraint MST_PASSWORD_PKC primary key (USER_CD) ) comment 'MSTパスワード' ; -- MSTユーザー create table MST_USER ( USER_CD varchar(20) not null comment 'ユーザーコード' , USER_NAME varchar(60) not null comment 'ユーザー名' , MAIL_ADDRESS varchar(128) comment 'メールアドレス' , DEFAULT_COLOR varchar(8) default '#FFFFFF' comment 'デフォルト背景色:#RGB形式' , CREATE_USER varchar(20) comment '作成者' , CREATE_DATE datetime comment '作成日時' , UPDATE_USER varchar(20) comment '更新者' , UPDATE_DATE datetime comment '更新日時' , constraint MST_USER_PKC primary key (USER_CD) ) comment 'MSTユーザー' ; -- TBLスケジュール create table TBL_SCHEDULE ( USER_CD varchar(20) not null comment 'ユーザコード' , ID int(10) not null comment 'ID' , TITLE varchar(100) comment 'タイトル' , START datetime comment '開始日時' , END datetime comment '終了日時' , TEXTCOLOR varchar(20) comment '文字色' , COLOR varchar(20) default '#FFFFFF' comment '背景色:#RGB形式' , URL varchar(100) comment 'URL' , ALLDAY int(1) default 0 comment '終日フラグ:0:終日/1:時間指定' , DESCRIPTION varchar(1000) comment '説明' , CREATE_USER varchar(20) comment '作成者' , CREATE_DATE datetime comment '作成日時' , UPDATE_USER varchar(20) comment '更新者' , UPDATE_DATE datetime comment '更新日時' , constraint TBL_SCHEDULE_PKC primary key (USER_CD,ID) ) comment 'TBLスケジュール' ;
テーブルを作成したら、適当にユーザーの情報をインサートしておきましょう。
画面の追加、修正
CSSの追加
ログイン画面用のCSSを追加
login.css
body { background: #ffbb55 none repeat scroll 0 0; } .jumbotron { text-align: center; width: 35rem; border-radius: 0.5rem; top: 0; bottom: 0; left: 0; right: 0; position: absolute; margin: 4rem auto; background-color: #fff; padding: 2rem; height:45rem; } .container .glyphicon-list-alt { font-size: 10rem; margin-top: 3rem; color: #f96145; } input { width: 100%; margin-bottom: 1.4rem; padding: 1rem; background-color: #ecf2f4; border-radius: 0.2rem; border: none; } h2 { margin-bottom: 3rem; font-weight: bold; color: #ababab; } .btn { border-radius: 0.2rem; } .btn .glyphicon { font-size: 3rem; color: #fff; } .full-width { background-color: #8eb5e2; width: 100%; -webkit-border-top-right-radius: 0; -webkit-border-bottom-right-radius: 0; -moz-border-radius-topright: 0; -moz-border-radius-bottomright: 0; border-top-right-radius: 0; border-bottom-right-radius: 0; } .box { position: absolute; bottom: 0; left: 0; margin-bottom: 3rem; margin-left: 3rem; margin-right: 3rem; } span.errMsg { color: #f96145; font-size: 11px; }
ログイン画面の追加
Login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>カレンダーサンプル - ログイン</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <link rel="stylesheet" href="{{ static_url('css/login.css') }}"/> <script type="text/javascript" src="https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <script type="text/javascript"> function submitLogin() { $("#loginForm").submit(); } </script> </head> <body> <form id="loginForm" method="post" action="/login"> <div class="jumbotron"> <div class="container"> <span class="glyphicon glyphicon-list-alt"></span> <h2>カレンダーサンプル</h2> <div class="box"> {% module xsrf_form_html() %} <input id="inputUserCd" name="user_cd" type="text" placeholder="ユーザーコード"> <input id="inputPassword" name="password" type="password" placeholder="パスワード"> <span class="errMsg">{{ error_msg }}</span> <button class="btn btn-default full-width"> <span class="glyphicon glyphicon-ok"></span> </button> </div> </div> </div> </form> </body> </html>
プログラムの修正
ユーザ情報取得用メソッド追加
データベースからユーザのパスワードや名前を取得するメソッドを追加
MySQLUtil.py
def get_user_password(self, user_cd=""): ''' ユーザーコードからパスワードを取得 :param user_cd: :return: ''' with closing(mysql.connector.connect(**self.config)) as conn: c = conn.cursor(dictionary=True) sql = "SELECT PASSWORD FROM MST_PASSWORD WHERE USER_CD = '" + user_cd + "'" c.execute(sql) result = c.fetchone() if result == None : return None return result[r"PASSWORD"] def get_user_name(self, user_cd=""): ''' ユーザーコードからユーザ名を取得 :param user_cd: :return: ''' with closing(mysql.connector.connect(**self.config)) as conn: c = conn.cursor(dictionary=True) sql = "SELECT USER_NAME FROM MST_USER WHERE USER_CD = '" + user_cd + "'" c.execute(sql) result = c.fetchone() if result == None : return None return result[r"USER_NAME"]
認証用クラス/メソッド追加
変更点
1. RequestHandlerを継承した認証用のクラスを追加
2. 認証処理を実施するクラスを追加
3. ログアウト用のクラスを追加
4. 「@tornado.web.authenticated」アノテーションでチェックをするように
Main.py
class AuthBaseHandler(tornado.web.RequestHandler): ''' 認証ハンドラー基底クラス ''' cookie_user_cd = "user_cd" def get_current_user(self): logging.info("AuthBaseHandler [get_current_user]") user_cd = self.get_secure_cookie(self.cookie_user_cd) if not user_cd: return "" return user_cd.decode("UTF-8") def set_current_user(self, user_cd): logging.info("AuthBaseHandler [set_current_user]") self.set_secure_cookie(self.cookie_user_cd, user_cd) def clear_current_user(self): logging.info("AuthBaseHandler [clear_current_user]") self.clear_cookie(self.cookie_user_cd) class AuthLoginHandler(AuthBaseHandler): ''' ログインハンドラー ''' def get(self): logging.info("AuthLoginHandler [get]") self.render("Login.html", error_msg="") def post(self): logging.info("AuthLoginHandler [post]") self.check_xsrf_cookie() # 認証処理 input_user_cd = self.get_argument("user_cd") input_password = self.get_argument("password") mysql = MySQLUtil() password = mysql.get_user_password(input_user_cd) # 入力されたパスワードと保存されているパスワードをチェック is_auth = False if input_password != password else True if is_auth: self.set_current_user(input_user_cd) self.redirect("/main") else: self.render("Login.html", error_msg="ユーザーコードまたはパスワードが間違っています。") class AuthLogoutHandler(AuthBaseHandler): def get(self): self.clear_current_user() self.redirect('/login')
カレンダー取得処理の修正
Main.py
class GetCalendar(AuthBaseHandler): """ カレンダー取得 """ def initialize(self): logging.info("GetCalendar [initialize]") @tornado.web.authenticated def get(self): logging.info("GetCalendar [get]") mysql = MySQLUtil() arg = self.request.arguments start = arg["start"][0].decode("UTF-8") end = arg["end"][0].decode("UTF-8") user_cd = self.get_current_user() data = mysql.get_schedule(start, end, user_cd) json_data = json.dumps(data, default=support_datetime_default) self.write(json_data) URLハンドリングの追加 Main.py app = tornado.web.Application([ (r"/login", AuthLoginHandler), (r"/logout", AuthLogoutHandler), (r"/main", MainHandler), (r"/getCalendar", GetCalendar), (r"/regist", RegistSchedule), (r"/update", UpdateSchedule), (r"/delete", DeleteSchedule), ], template_path=os.path.join(os.getcwd(), "templates"), static_path=os.path.join(os.getcwd(), "static"), login_url="/login", cookie_secret="adfaskljfwepmaldskf:as;k", xsrf_cookies=True )
非同期通信時のXSRF設定
Ajax通信でXSRFパラメータを送信するように
script.js
/** * リクエスト送信. */ function sendAjaxRequest(method, eventData) { var cal = $("#calendar").fullCalendar("getView"); eventData.searchStart = cal.start; eventData.searchEnd = cal.end; // 処理名を設定 var methodName = "登録"; if (method == "update") { methodName = "更新" } else if (method == "delete") { methodName = "削除" } $.ajax({ url: "http://localhost:8080/" + method, type: "POST", headers: {'X-XSRFToken' : $("*[name=_xsrf]")[0].value }, data: JSON.stringify(eventData), success: function(jsonResponse) { // カレンダー再描画 $('#calendar').fullCalendar('removeEvents'); $('#calendar').fullCalendar('renderEvents', $.parseJSON(jsonResponse) ) $('#inputScheduleForm').modal('hide'); alert("予定を" + methodName + "しました。"); }, error: function() { alert("予定の" + methodName + "に失敗しました。"); } }); $('#calendar').fullCalendar('unselect'); }
起動してみる
ヘッダーを追加して、ユーザ名を表示、あとは適当にメニューを追加してみました。
まとめ
ひとまず、ここまででカレンダーは終わりにしようかなと思ってます。
そのうちなんかサービスが作れたらいいな。
ソースは整理して別途 Github にアップする予定です。
何かの参考になれば。
ではでは。
ディスカッション
コメント一覧
まだ、コメントがありません