【Python】FullCalendarにログイン機能をつけてみる

2018年7月16日Python,開発

おはようございます。

今回はログイン画面を追加して、
ユーザー毎にカレンダーを持てるようにしてみました。

ちょっと長くなってしまったので、ソースはそのうち 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 にアップする予定です。

何かの参考になれば。

ではでは。

 

スポンサーリンク


関連するコンテンツ

2018年7月16日Python,開発FullCalendar,Javascript,JQuery,Python,プログラミング

Posted by doradora