Python+DjangoでSNSを作る~Day4 ユーザーに応じた年齢表示アプリを作成-延長戦-
前回、DBに登録されたユーザーの生年月日をもとに、年齢を算出・表示させるところまで書きました。
今回は、「①ログインしたユーザーの登録された生年月日に応じた年齢表示」、「➁ログイン不要で入力した生年月日に応じた年齢表示」の2点を実装したいと思います。
1. ログインやユーザー認証の方法
苦戦しましたが、結論から言うとDjangoには標準でユーザー認証機能があります。これを最低限の労力で済むように実装しました。
Authentication Viewsを利用することで、簡単に実装ができます。
おさらいも含め、プロジェクト作成から流れを書いていきます。
2. 仮想環境作成→プロジェクト作成→アプリケーション作成
(1) 仮想環境作成
今回は、myprojectという仮想環境名・プロジェクト名で、agecalculateというアプリケーション名で作成していきます。(ところどころやらかしてるのは笑ってやってください)
・仮想環境作成
・Djangoのインストール+プロジェクト作成
・アプリケーション作成
3.各種Pythonファイル①settings.py・urls.py
・settings.pyファイルの修正① INSTALLED_APPS
・settings.pyファイルの修正➁ LANGUAGE_CODE・TIME_ZONE
・プロジェクトのurls.pyへの追記
django.urlsからincludeを追加でimport、作成したagecalculateアプリのurls.pyを参照するよう追記。一番下に追記したのは、ログイン認証機能利用のため。django.contrib.authという標準実装されたユーザー認証機能のurls.pyを参照するように追記している。パスはaccounts固定となるため、そう追記。
・アプリケーションフォルダにurls.pyファイルを新規作成・追記
loginビューと、logoutビューを利用するため、django.contrib.authからauth_viewsをviewとしてimportしてます。
ここでも、accounts/login/と、accounts/logout/という定められたパスを指定する必要があります。
auth_views.LoginView.as_viewのくだりは、インポートしたログイン認証用のビュー機能を利用するために記載してます。括弧内のtemplate_nameの部分は、後程用意するlogin.htmlというテンプレートファイルを使うからね!と宣言してます。
(コード中の"\"は、ここに載せるときに、見やすくするためにコード内で改行するために記載してます。皆さんがコード書く時は、記載せず一行で書いていただいて結構です。)
4. models.pyの作成
def以降は、前回解説したので説明割愛します。ポイントは、ここでもdjango.contrib.authのmodels.pyファイルからUserクラスをインポートしているところです。
PHPでログイン機能を開発したときは、いちからデータベース定義含め処理を作りました(結構大変だった・・・)
Djangoはユーザー認証機能を標準実装しているので、開発スピードを短縮できるメリットとなりますね!
これを今回作成したUserageクラスのname変数に1対1で紐づけてます。
こうすることで、admin機能で登録したユーザーと、Userageクラスのユーザーを紐づけることが可能となります。
UserモデルとUserageモデルが紐づけば、Userテーブルの誰それの生年月日情報をUserageテーブル側で持つことができます。
Djangoには、1対1・1対N(複数)・N対Nのデータベース項目間の紐づけが可能となってます。
今回は、1対1を利用するため、models.OneToOneFieldと記載し、引数にUserテーブルと紐づけを行うことを宣言してます。もう一つの引数、on_deleteは、該当の項目を削除したときに紐づけている相手先も削除するかどうかを定義します。CASCADEだと削除、PROTECTだと削除不可、SET_NULLだとNULL(空)、SET_DEFAULTだと初期値となります。
では、次にコントローラーであるviews.pyに表示プログラムを定義していきます。
5.views.pyの修正
では、views.pyにメインとなるバックエンド処理を書いていきましょう!
【ポイント】
①ログアウト時に、再度ログインページを表示させるため、今回はrender関数だけでなく、redirect関数をインポートしています。
from django.shortcuts import render, redirect
ログアウト用の関数(logout_view関数)を定義し、インポートしたredirect関数でリダイレクト先をname変数loginにし、ログアウト処理をしたら、ログインページを表示させるようにします。
def logout_view(request):
return redirect('login')
➁ログインをきっかけに、ログインしているユーザーを認識し年齢を返すサイト設計とするため、indexページはログイン時のみ閲覧可能とします。
デコレーター機能といって、関数の前につけることで働きます。
@login_required(login_url='accounts/login/')
def index(request):
・・・・
・・・・
と記載することで、「index関数はログインしてないと動かないし、ログインしてない場合は/accounts/loginという相対パスを誘導します」という事になります。
これを利用するため、django.contrib.auth.decoratorsから、login_requierdというデコレ―タをインポートしてます。
from django.contrib.auth.decorators import login_required
➂最後に今回の主目的である、ログインしたユーザーの年齢を表示するという部分です。以下記載でログインユーザーをuser変数に渡しています。
user = request.user
これをもとに、以下の記載でUserageテーブルから、該当のuserを引っ張り出してuserage変数に格納してます。これが可能なのは、Userテーブルと、UserageテーブルをOneToOneリレーションで紐づけてるからです。
userage = Userage.obejects.filter(name=user)
あとは、contextという変数に辞書形式で、userageというインデックスで、userage変数に格納されたデータベース情報を含め、index.htmlファイルに渡しています。
6. データベースのマイグレーションをする
では、models.pyもviews.pyも作成したので、マイグレーションをしてみます。
マイグレーションが成功しました。プロジェクトのマイグレーションフォルダをみると、0001_initial.pyファイルができています。中を見ると・・・
Userageという名前のモデルが作られてます。切れてしまうので、スクロールした画面を下につけましたが、id(データを一意に示すキー項目、勝手に作られます)、birthday、nameというフィールド項目が定義されてます。また、nameは、AUTH_USER_MODELに紐づけされていることが確認できます。
では、マイグレートを行います。
7. テンプレートファイルを用意する
それではWebサイトに表示するためのHTMLファイルを用意していきます。
今回は、Djangoを利用するメリットの一つである、レイアウト用のテンプレートの使い方とBootstarp4で見た目にもわずかにこだわりたいと思います。
わずかに・・・(笑)
レイアウト用ファイルのイメージは以下のとおりです。
え?よくわかんないって?
レイアウトファイルをベースとして作っておくと、中身のコンテンツ部分の表示のみを変えて、複数のhtmlファイルを作ることができます。
htmlタグ書いて、headタグ書いて、スタイルシート読み込んで、、、ナビゲーションバーを作って・・・とやる必要がなくなるので、これも開発スピードに直結します。
では、base.htmlとしてレイアウトファイルを書きます。
その際、Bootstarp4を利用するようにします。
・base.html
まずはアプリケーションフォルダの配下に、templatesフォルダを作成し、その配下にアプリケーション名のフォルダを作成。その配下にbase.htmlファイルを作成します。
こちらに記載をしていきます。
ファイル内を見てもらうと、二つテンプレートタグがあります。
{% block title %} {% endblock %}
と
{% block content %}
{% endblock %}
の二か所ですね。このレイアウトファイルを例えば、index.htmlファイル側で呼び出すことで、title(タイトル)と、content(内容)部分のみコーディングすれば、後はレイアウトファイルのものを使ってくれるという訳です。
便利ですねー。
では、index.htmlファイルも作成しましょう。
・index.html
これだけです(笑)
あえて、htmlタグは一切書きませんでした。
{% block title %}と{% endblock %}の間にタイトルを、{% block content %}と{% endblock %}の間に、本文を書いてます。
views.pyで記載した、以下のrender処理で渡されたcontext(userageというインデックスが貼られたデータベース情報)は1件ですが辞書形式なので、forループ処理で中身をuserage変数に改めて格納したうえで、{{userage.name}}で名前、models.pyの中で計算処理した年齢情報を{{userage}}としてテンプレートタグで記述しています。
return render(request, 'agecalculate/index.html', context)
今回、urls.py記載に記載した、urlpatternsの内容を振り返ると・・・
login.htmlファイルも必要です。なので、login.htmlも用意します。
本来は、入力フォームは別途forms.pyファイルを作成して定義する必要がありますが、ログインフォームはuserフォームが標準実装されているのでそれを流用しちゃおうってことです。
8. サーバー起動してみる
何か忘れてるような気もするけど、一通り準備はできた気もするのでサーバー起動してみます。なんかおかしかったら、エラーを見て直しましょうってことで。
とりあえず、起動は無事できました。では、http://127.0.0.1:8000/agecalculateにアクセスしてみましょう。
これはどうしたことでしょう。ここでハマりました・・・。
エラーにかいてあるとおり、どうやらlogin.htmlはregistrationというフォルダに入っていることがルールのようです。
やってみましょう。
ファイル内容は変えず、ただtemplates配下にregistrationフォルダを切って、そこにlogin.htmlファイルを移動させました。
これで再度起動し、アクセスしてみましょう。
ちなみにお気づきの方がいるかもしれませんが、retistartionとなっているのでこの後再びエラーとなったことは言うまでもありません。。。
本当にプログラム書いているとタイポおおいですよね。(僕だけ??)
正しくは、以下です。
では、一度Ctrl + Cでサーバーを停止し、再度python manage.py runserverでサーバー起動しましょう。
ヤッタゼ!
ちなみに、ブラウザのアドレスバーには、http://127.0.0.1:8000/agecalculate/と入れたのに、accounts/login/に導かれてます。
そして、ログインしようにもユーザーIDがないことに気付きました。(笑)
9. ユーザーアカウントを作ってログインしてページを表示してみる
前回同様、まずは管理権限を持つユーザーを作ります。
python manage.py createsuperuser
でしたね。IDとメールアドレス、パスワードを設定したらOKです。
再びrunserverで、サーバーを起動し次に、http://127.0.0.1:8000/admin/にアクセスします。
ユーザー名とパスワードを入力し、ログインします。
あ・・・何を忘れてたか思い出した。アプリケーションのadmin.pyファイルを開いて、以下を記述してください。admin画面で作成したUserageモデルを追加するためです。
それから、管理サイトにログインすると・・・
Userageが更新できるので、+追加を選んで今登録したユーザーIDと同じIDで登録をしてみましょう。
nameの部分がプルダウンで、登録ユーザーIDを選んで、Birthdayは、年-月-日(4桁-2桁-2桁)で保存してください。
ここまでできたら、再度http://127.0.0.1:8000/agecalculate/にアクセスしてみましょう。(ログアウトしてからにしてください)
そして、ログイン要求されるので登録したユーザーIDとパスワードでログインします。
はい!
上手くいってるようですが、念のためほかのユーザーを作って試してみます。
できましたね。これで、ログインユーザーだけの年齢を表示することができました。
良かった良かった。せっかくだから、ログインせずに好きな生年月日を入れたら、年齢を返してくれる機能も作ってみましょう。
10. forms.pyに入力フォームを作成する
機能としては、こんなイメージです。ログインせずに、名前と生年月日(年・月・日)それぞれをページ上の入力フォームから送信すると、その場で年齢を計算して表示する。
特にDB等にも書き込みはしない。そういえば、親の年齢っていくつだっけ?とかそんなユースケースを想定してます。
必要になりそうなのは、フロントのhtmlファイルと、それ用のアドレス(ルーティング)、入力用のフォーム、viewsファイルの関数といったところでしょうか。
では、この際ですのでやっていきましょう。まずは入力フォームから。
・forms.py
djangoのforms関数をインプット、AgeCalculateFormクラスを作成します。項目はname,birthyear,birthmonth,birthdateの4項目としました。
・views.py
まずは、forms.pyで定義したAgeCalculateFormをインポートします。
年齢算出にdate関数とmath関数を使いたいので、こちらもインポートします。
次に、agecalculate関数を定義します。
まずは、render関数の引数として渡すparams変数に辞書型で、messageというインデックスに「算出結果:」という文字列を、formという変数にAgeClaculateFormのインスタンスを格納しています。
params = {
'message' : '算出結果:',
'form' : AgeCalculateForm()
}
その後に記載している以下のコードを見てください。if文となっており、request.method == 'POST'は、「投稿されたら」という意味です。
投稿された場合、まずtoday変数に今日の日付を文字列で格納します。
次に、投稿された年・月・日を+でつないで、生年月日を文字列で変数birthdayに格納します。
あとは、models.pyのように、age変数に数値に変換した「今日の日付-生年月日/10000」の結果を格納。
辞書型paramsのインデックスmessageの値を、フォームのname欄に投稿された名前と、age変数(今度は文字列にする)を使って、「”名前”さん、”年齢”歳やね、今。”」に更新します。
つまり、投稿されてない場合は、インデックスmessageの値は、「算出結果:」、投稿されると「”名前”さん、”年齢”歳やね、今。”」となるわけです。
これをparamsでこれから、追加予定のテンプレートagecalculate.htmlファイルにお渡ししようとしてます。
・urls.py
views.pyに記載した、agecalculate/agecalculate.htmlへの道を通す必要があるので、urls.pyのurlpatternsに追加をします。(一番最後の行)
views.pyファイルのagecalculate関数で、テンプレートタグでurlを呼び出す際は、agecalculateという名前でaタグを書くことができます。冒頭のageapは、URLアドレス(パス)です。
http://127.0.0.1:8000/agecalcuate/apage/ で該当ページにいけます。
anotherperson(他の人の)age(年齢)ということで、適当にそうしました(センスなし)
・agecalculate.html
では最後にテンプレートとなるhtmlファイルを書きます。こちらも先ほどのbase.htmlというレイアウトファイルを利用できます。
index.htmlが格納されているフォルダ内に新たに、ageacalculate.htmlファイルを追加し、以下のように記載しました。
いくつか、新しいテンプレートタグが出てきましたね。
テンプレートタグの解説
① {{message|safe}}
このテンプレートタグは、htmlタグを含めて渡す場合に使います。"|safe"がないと、<h3></h3>タグが文字列の扱いとなり、そのまま表示されてしまいます。
➁ {% url 'agecalculate' %}
agecalculate(パスはhttp://127.0.0.1:8000/agecalculate/apage)に対してフォームの入力情報を投稿(post)するよ、というURLの指定を行っています。
aタグのhref属性にセットすると、遷移先のURLを表すことになります。
➂ {% csrf_token %}
簡単にいうと、サイバー攻撃を防ぐための検証です。ログイン状態のまま、ユーザーが罠が仕掛けられたサイトにアクセスした場合に、あたかもそのユーザーが行ったかのように偽のリクエストが行われます。
これをCSRF検証といって、リクエストの真偽性をトークンが一致するかチェックすることで、検証し意図しない更新がかからないようにするものです。
【ご参考】
➃ {{ form.as_table}}
formをどう、Webページ上に表示あせるかで、form以降の部分を変えます。パラグラフ(Pタグ)として表示の場合は、as_p。リストとして表示する場合は、as_ul。表形式の場合はas_tableです。
11. Webページを表示してみる
では、サーバー起動させてみましょう。
そして、自分で設定したパス、http://127.0.0.1:8000/agecalculate/apage/にアクセスします。
出てきましたね。では、ここに名前と生まれた年・生まれた月・生まれた日を入力しましょう。
そして、「入力完了・算出する」をクリックします。
はい。出力ができました。
12. 別ページへの遷移をする方法
大分長くなったので、ページに画像表示したり、Bootstarp4を使ってレイアウトを整えたりは次回にします。
今回は、①ログイン→ログインユーザーの年齢表示、➁ログインせず生年月日入力による他人の年齢確認の大きくいうと2つ機能を作りました。
実際には、「a.ログインして①」、「b.ログインせず➁」、または「c.ログインしてから➁」の3つの動線があると思います。これらをアドレスバーにパスをいちいち入力せずに、ページ内にリンクを貼る方法だけ最後に書きます。
こんな時も、レイアウトとして使っているbase.htmlが役に立ちます。
以下のように、base.htmlを修正します。
修正したのは、<style>タグで囲まれたCSSを記述している部分と、<nav>タグ内の、メニューリストです。即席ナビゲーションバーです。
CSS自体は、次回概要を解説するので、一旦写経(そのまま写して書くこと)で大丈夫です。
ちょっとWebスクレイピングにも関係します。簡単に言うと、ulタグにnavbarというクラス属性を持たせ、displayというCSSのレイアウト方法を使って、リスト要素を水平方向に並べてます。
ポイントは、aタグ内のhref属性の指定の仕方です。
これもDjangoのテンプレートタグで指定ができます。
{% url "name属性" %}
だけでOKです。name属性とは、urls.pyのurlpatternsに記述したpath関数に渡しているnameという引数に設定した文字列です。
最後に一個補足。。。ログアウトをクリックすると、管理画面(admin画面)のログアウトページに行ってしまうと思います。
これをログインページに誘導するには、settings.pyファイルに以下の一行を追記すればOKです!
ページを遷移させてみましょう。
・http://127.0.0.1:8000/agecalculate/ (ログイン前)
・http://127.0.0.1:8000/agecalculate/ (ログイン後)
・ログアウトボタン押下
・http://127.0.0.1:8000/agecalculate/apage/
一通り、いけそうです。ちなみにログアウト前にログインを開いて再ログインすると・・・、おかしなことになります(笑)
では、次回はこの素朴なサイトに、画像を載せたり、CSSを使って見た目を整えていきましょう。