Django Girlsで学んだことをまとめてみた

##はじめに
未来電子テクノロジー（https://www.miraidenshi-tech.jp/intern-content/program/）
でインターンをしているrayaと申します。
プログラミング初心者であるため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するのでどんどん指摘してください。

##今回の内容
今回は、DjangoGirlsチュートリアルで学んだことなどを自分なりの言葉でまとめていきます。
詳しいことはチュートリアルに記載されているので以下のURLからご覧ください。
参考：https://tutorial.djangogirls.org/ja/


##Djangoとは
Pythonを使ったWebアプリケーションを作るためのフレームワークです。
フレームワークとは「枠組み、骨組み」という意味です。
この枠組み、骨組みはアプリケーションの基本的な機能を搭載していて、Djangoを使うことで開発が簡単になります。
たい焼きの鋳型があるから、たい焼きを簡単に作れるという感じですね。

##アプリケーションを作る準備
python,djangoのインストール、仮想環境が構築できていることを前提に進めます。
まずはDjangoプロジェクトを作ります。

```
$ django-admin startproject mysite .
```
プロジェクトの中にはいくつかディレクトリやファイルが作られます。
これらのフォルダやファイルのほとんどは、Djangoアプリケーション全体を管理したり、設定を反映させます。

次に、プロジェクトの中にアプリケーションを作ります。

```
$ python manage.py startapp blog
```
プロジェクトを作った時と同様にアプリケーションの中にもいくつかディレクトリやファイルが作られます。
これらには以下のmodel.py,views.pyなどがあります。

##Model, Url, View, Templateの関係
アプリケーションの中には以下のディレクトリやファイルを作ります。
models.py：どのような形でデータを保存するかを定め、そのデータをデータベースに保存します。
url.pys：urlを定め、そのurlでアプリケーションにアクセスされた場合に、どのViewを適用するかを指示します。
views.py：Urlからの指示を受け、アプリケーションがどういった挙動をするかを定めます。
ModelとTemplateを繋げ、ModelのデータをTemplateに表示させます。
Templates：htmlファイルが入っていて、表示する画面を定めます。

##Model
models.pyファイル内にどのような形でデータを格納したいか記述します。
このモデルの場合は、タイトル、テキスト、作成日を含めたデータとしてデータベースに格納されます。
モデルはmodels.Modelを継承することで、Djangoのモデルであり、データベースに格納するデータであることを伝えています。
そのためには最初にimportでmodelsを呼び出しておく必要があります。

```python

from django.db import models

class Post(models.Model):
    author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    title = models.CharField()
    text = models.TextField()
    created_date = models.DateTimeField(default=timezone.now)
```

##Url
urls.pyファイルはプロジェクトとアプリケーションの直下で合計２個作られます。
アプリケーション開発では主にアプリケーションフォルダ内のurls.pyに記述し、その内容をプロジェクト内にインポートする形にします。
まずは、プロジェクト内のurls.pyです。
１行目でpath関数とinclude関数がインポートされています。
path関数の第1引数でUrlを定め、include関数でアプリケーション内のurls.pyをインポートすることを指示しています。

```python

from django.urls import path, include

urlpatterns = [
    path('', include('blog.urls')),
]
```
次にアプリケーション内のurls.pyです。
viewsをインポートすることで、第１引数のurlでアクセスされた時に、第２引数のpost_listというViewを適用すると定められます。第３引数のnameはこのurlの名前を示します。

```python

from django.urls import path
from . import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
]
```

##View
viewでは、特定のurlでアクセスされた場合に、アプリケーションがどのように動くかを関数で定めます。
先ほどの、``name = 'post_list'``のurlでアクセスされた場合は、以下のpost_list関数を動かします。
この関数は、ユーザーからのrequestを受けて、Postモデルのデータを全て変数postsに代入します。
インポートされているrender関数はそのrequestを受けて、第２引数のhtmlファイルに'posts'という名前で変数postsに代入されているデータを渡します。
これがModelからTemplateへのデータの引き渡しです。


```python
from django.shortcuts import render
from .models import Post

def post_list(request):
    posts = Post.objects.all()
    return render(request, 'blog/post_list.html, {'posts': posts})
```
##Template
Template内にはアプリケーションと同じ名前のディレクトリを作って、その中にhtmlファイルを作成します。
通常のhtmlファイルと異なるところは、Modelからのデータを表示する点です。
pythonで記述されたコードを表示するので特別な書き方をします。
画面に出力したい場合は、``{{ }}``で囲み、画面に出力するわけではないpythonの文法部分は``{% %}``で囲みます。
以下のように、Viewで定めた変数postsは画面に出力したいので``{{ }}``で囲み、pythonの文法であるfor文は``{% %}``で囲んでいます。
Postモデルで定めたtitleを表示させたいので、``post.title``としています。

```html

<div>
    <h1><a href="/">Django Girls Blog</a></h1>
</div>

{% for post in posts %}
    <div>
        <p>published: {{ post.published_date }}</p>
        <h1><a href="">{{ post.title }}</a></h1>
        <p>{{ post.text|linebreaksbr }}</p>
    </div>
{% endfor %}
```

また、Template内のhtmlファイルはそれぞれ異なる画面を表示させます。
この際に同じような構成の画面になる場合が出てきます。
繰り返し似たコードを記述するのを避けるために、Templateの拡張ということが可能です。
まず、ベースとなる画面のhtmlファイルを作成します。（base.html）
base.htmlの中には、繰り返し表示したい部分を記述し、それぞれのhtmlファイルごとに中身を変えたい部分に``{% blockcontent %}{% endblock%}``と記述します。
次にそれぞれのhtmlファイルを作ります。
そのファイル独自の記述のみを記載すれば良いです。
base.htmlファイルを反映するために、それぞれのファイルの先頭に``{extends 'blog/base.html'}``と記述し、``{% blockcontent %}{% endblock%}``の中に独自の記述を書いて完成です。

htmlファイルにcssを適用させたい場合は、アプリケーションディレクトリ直下にstatic/cssディレクトリを作成し、その中にblog.cssファイルを作成します。
このcssファイルをhtmlに反映させるには、htmlファイルの先頭に``{% load static %}``と記述し、head内に以下のコードを記述します。

```html
<link rel="stylesheet" href="{% static 'css/blog.css' %}">
```

##管理者画面
アプリケーションを運営するにあたって、全体の管理画面があると便利です。
この管理画面はアプリケーションディレクトリ直下のadmin.pyで定めます。
models.pyで定めたPostモデルをインポートし、``admin.site.register(Post)``によって管理画面にPostモデル表示されるようにします。
これにより管理画面でPostモデルの操作ができるようになります。

```python
from django.contrib import admin
from .models import Post

admin.site.register(Post)
```
ちなみに管理画面へのUrlはもともとプロジェクトディレクトリ直下のurls.pyに記載されています。

```python
path('admin/', admin.site.urls),
```

このUrlでアクセスするとログイン画面が表示されますが、まだ管理者を登録していないのでログインできません。
そこで、コマンドラインでsuperuserという管理者を作り、名前やパスワードを決める必要があります。

```
$ python manage.py createsuperuser
```

これで管理画面にログインできるsuperuserが作られ、ユーザーの登録などアプリケーションの全ての動作を管理できます。

##フォームからModelにデータを追加する
先ほど、管理画面を作り、そこからユーザー登録ができるようになりました。
しかし、実際のアプリケーションは管理者がいちいちユーザーを登録しているわけではありません。
ユーザー自身が登録画面から自分のデータを入力しログインしてアプリケーションを使います。
登録した情報はModelに結びついて、先ほどのModel, Url, View, Templateの流れで画面に表示されます。
ここでは、その機能を実装します。
今回は、新規投稿を登録する場合を考えます。

まず、アプリケーション直下にforms.pyディレクトリを作ります。
その中にPostモデルに結びつけたい場合は、Postをインポートします。
また、フォーム機能を使うのでformsもインポートします。
``class PostForm(forms.ModelForm):``では、PostFormという名前のフォームを定め、これがDjangoのフォームであることを伝えます。
``class Meta:``では、どのModelと結びつけるかを定めます。
``model = Post``でPostモデルと結びつけ、``fields = ('title', 'text',)``でこのフォームにはtitle, textを記述する箇所を設けます。

```python
from django import forms
from .models import Post

class PostForm(forms.ModelForm):

    class Meta:
        model = Post
        fields = ('title', 'text',)
```
これで、Postモデルと関連づけられたフォームが作成され、実質Modelのような役割を果たします。
次は、先ほど行ったように、Url, View, Templateを作成していきます。

最初にTemplateでフォームを記述する画面を作ります。
ここで注目すべきは、formタグ内の``method="POST"``という記述です。
これは、サーバーに対して、データを送信したいというrequestを表しています。
POSTによって、フォームに記述した自身のデータがサーバーに送信されます。
一方で、データを送信するのではなくただサイトを見るだけの場合はGETメソッドを使っています。
``{% csrf_token %}``はフォームの安全性を高める記述です。
``{{ form.as_p }}``はフォームの記載項目をpタグで囲まれたように表示します。
ここでは、forms.pyのfieldsで定めたtitle, textが表示されます。

```html
{% extends 'blog/base.html' %}

{% block content %}
    <h1>New post</h1>
    <form method="POST" class="post-form">{% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="save btn btn-default">Save</button>
    </form>
{% endblock %
```
Urlはurls.pyに記述します。

```python
path('post/new', views.post_new, name='post_new'),
```

Viewは2つの場合に分けてアプリケーションの動作を定めます。
それは、POSTでデータを送信する場合とそうでない場合です。
POSTでデータを送信するとは、実際にデータを登録する場合で、そうでない場合とはフォーム画面を訪れただけの場合です。
POSTでデータが送信された場合は、そのデータが適切なデータかを確認し、適切ならばModelにデータを保存して新たなユーザーとして登録する必要があります。
``if request.method == "POST":``でPOSTメソッドかどうかを確認します。

requestがPOSTの場合
``form = PostForm(request.POST)``でPOSTで送られたデータ（request.POST）をPostFormに反映させて、新たなインスタンスを作り、変数formに代入します。
``if form.is_valid():``でそのインスタンスが適切なものかを認証します。
適切ならば``post = form.save(commit=False)``で保存するのですが、``commit=False``に注目です。
これは現時点ではまだ保存しないという意味です。
実は、現時点で保存するとエラーします。
なぜかというと、フォームで送られてくるデータは、forms.pyのfieldsで定めたtitle, textのみです。
このフォームはPostモデルと結びついているので、Postモデルに保存するには、この記事のModelのところで定めたauthorも必要です。
よって、ここでは保存せずにまずauthorを作る必要があります。
ちなみに、このpostはTemplateのところで記述したpostsのデータをfor文で取り出して代入している変数postです。
postのauthor(Postモデルのauthor)にrequestしているユーザーを代入します。
このuserはDjangoでデフォルトで定められているUserモデルのインスタンスです。
インポートされているredirect関数は、render関数と役割が似ているのですが、少し違いがあります。
render関数が変数のデータを``{'form': form}``の形でhtmlファイルに出力してから表示させるのに対し、redirect関数は特にそのようなことをしません。

requestがPOSTでない場合。
ユーザーが新規投稿画面を訪れただけなので、空のフォームを表示します。
``post_detail``や``post_edit``はDjango Girlsチュートリアルに記述があるのでご確認ください。

```python
from django.shortcuts import render, redirect
from .forms import PostForm

def post_new(request):
    if request.method == "POST":
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.published_date = timezone.now()
            post.save()
            return redirect('post_detail', pk=post.pk)
    else:
        form = PostForm()
    return render(request, 'blog/post_edit.html', {'form': form})
```
Urlはurls.pyに記述します。

```python
path('post/new', views.post_new, name='post_new'),
```

これで、ユーザーもデータの登録が可能になりました。

##セキュリティ
現状、このアプリケーションはセキュリティがガバガバです。
誰もが他の人のユーザー画面でデータを登録して、勝手に編集できてしまいます。
これを避けるためにアプリケーションにログイン機能とログインしている場合のみデータ編集などができるよう制限を加えます。

まず、ログインしている場合のみデータ編集などアプリケーションの動作ができるようにします。
views.pyの中で、``login_required``をインポートし、制限をかけたい関数の上に``@login_required``と記述します。

```python
from django.contrib.auth.decorators import login_required

@login_required
def post_new(request):
```
次に、ログイン機能を実装します。
プロジェクト直下のurls.pyに以下のUrlを記述します。

```python
path('accounts/login/', views.LoginView.as_view(), name='login'),
```

Template内に``registration``というディレクトリを作成し、``{{form.username}}``、``{{form.password}}``という形で記述します。
これだけでユーザーとパスワードの認証が可能になります。

```html
{% extends "blog/base.html" %}

{% block content %}
    {% if form.errors %}
        <p>Your username and password didn't match. Please try again.</p>
    {% endif %}

    <form method="post" action="{% url 'login' %}">
    {% csrf_token %}
        <table>
        <tr>
            <td>{{ form.username.label_tag }}</td>
            <td>{{ form.username }}</td>
        </tr>
        <tr>
            <td>{{ form.password.label_tag }}</td>
            <td>{{ form.password }}</td>
        </tr>
        </table>

        <input type="submit" value="login" />
        <input type="hidden" name="next" value="{{ next }}" />
    </form>
{% endblock %}
```
最後に、ログインが成功した時にホーム画面に戻ってこられるように、プロジェクト直下のsettings.pyに記述します。

```python
LOGIN_REDIRECT_URL = '/'
```

##Modelの変更をデータベースに伝える
Modelはデータベースと密接に繋がっています。
よって、Modelが新たに作成された場合や変更があった場合は、それらをデータベースに伝え反映させる必要があります。
ここまで、PostモデルやPostFormモデルを作成しているのでデータベースにそれらを伝えます。
コマンドラインのプロジェクトディレクトリ直下で、以下のコマンドを実行します。

```
$ python manage.py makemigrations blog
$ python manage.py migrate blog
```
``makemigrations``でmigrationsファイルを作成し、変更をまとめます。
次に、その内容を``migrate``でデータベースに反映させます。

ちなみに``migrate``した変更を元に戻したい場合は、どうするかを説明します。
まず以下のコマンドでmigrationの履歴を確認します。

```
$ python manage.py showmigrations
```
すると、次にような画面が表示されます。

```
admin
 [X] 0001_initial
 [X] 0002_・・・・・
 [X] 0003_・・・・・
auth　
 [X] 0001_initial　
 [X] 0002_・・・・・
 [X] 0003_・・・・・
app2　#アプリケーションの名前
 [X] 0001_initial　#migrationの名前
 [X] 0002_・・・・・
 [X] 0003_・・・・・
```

``app2``の``0001_initial``まで戻したい場合は、以下のコマンドを実行します。

```
$ python manage.py migrate app2 0001_initial
```
するとmigrationを戻した場所以降の``[X]``が``[ ]``に変わり、migrationが戻ります。

```
app2　
 [X] 0001_initial　
 [ ] 0002_・・・・・
 [ ] 0003_・・・・・
```