Django Girls Tutorialメモ

django girls tutorialの勉強メモです
https://tutorial.djangogirls.org/ja/

## 試行環境

PythonAnywhereというPaaSを使ってデプロイするtutorialなので、pythonとgitが入る環境ならどこでも出来ます。環境構築についてもtutorial内にありますので、pythonとgitが入ってない方は以下のリンクを参照してください。
https://tutorial.djangogirls.org/ja/python_installation/







## 1.プロジェクトを作成して動かしてみる

まず適当な場所にprojectディレクトリを作成します。今回は /home/ユーザ名/django/proj1 にprojectを作成しました。

```terminal:terminal
mkdir django
cd django
django-admin startproject proj1
```

以下のようなファイルが自動で作成されます。

```:/home/ユーザ名/django/proj1/
proj1
├── manage.py
├── mysite
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── myvenv
│   └── ...
└── requirements.txt
```

設定ファイルであるsettings.pyに必要な設定を書き込んでいきます。

#### タイムゾーンと言語

以下では日本標準時と日本語に設定しています。

```python:setting.py
TIME_ZONE = 'Asia/Tokyo'
LANGUAGE_CODE = 'ja'
```

#### 静的ファイルのPATH

```python:setting.py
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
```

#### 読み込みを許容するURL

ALLOWED_HOSTを * にしておくとすべてのURLへの読み込みが可能になりますが、httpインジェクション攻撃に無防備になってしまいます。必要なURLのみlistに追加するようにしましょう。今回は簡易サーバーでlocalhostでテストをした後、pythonanywhere.comにデプロイするので以下の2つを追加しておきます。

```python:setting.py
ALLOWED_HOSTS = ['127.0.0.1', '.pythonanywhere.com']
```


#### データベースの設定

sqlite3を使用する場合は以下のようにします。

```python:setting.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
```


#### djangoのテストサーバーを起動する

仮サーバーを動かしてみてどうなったか確認します。

```terminal:terminal
python manage.py migrate
python manage.py runserver
```

http://localhost:8000 をブラウザで開くとdjangoのサンプルページが表示される筈です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/d5ff3be2-02cd-2bea-10fc-0a084b86f538.png" width=75%>
表示内容に関わる部分は変更していないのでデフォルトのサンプルページが表示されます。


































## 2. modelを作成する

プロジェクトの中に複数のアプリを作る事ができるようになっているので、blogというディレクトリにblogアプリを作成していきます。

```terminal:terminal
python manage.py startapp blog
```

以下のようにblogという名前のディレクトリが追加されます。

```:/home/ユーザ名/django/proj1/
proj1
├── blog
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── db.sqlite3
├── manage.py
├── mysite
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── myvenv
│   └── ...
└── requirements.txt
```
settings.pyのINSTALLED_APPSに追加したアプリを追加してdjangoが読みに来るようにしてやります。

```python:setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog.apps.BlogConfig',
]
```
models.pyのMVCモデルのMにあたるmodelの部分を書いていきます。modelはデータベースの読み書きやデータの処理などのバックエンド部分を担当します。

django.db.models.Modelクラスを継承して

```python:models.py
from django.conf import settings
from django.db import models
from django.utils import timezone


class Post(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    text = models.TextField()
    created_date = models.DateTimeField(default=timezone.now)
    published_date = models.DateTimeField(blank=True, null=True)

    def publish(self):
        self.published_date = timezone.now()
        self.save()

    def __str__(self):
        return self.title
```

```
models.CharField – 文字数が制限されたテキストを定義するフィールド
models.TextField – これは制限無しの長いテキスト用です。ブログポストのコンテンツに理想的なフィールドでしょ？
models.DateTimeField – 日付と時間のフィールド
models.ForeignKey – これは他のモデルへのリンク
```
作成したモデルをデータベースに追加します。

```terminal:terminal
python manage.py makemigrations blog
python manage.py migrate blog
```

```terminal:結果
Operations to perform:
  Apply all migrations: blog
Running migrations:
  Applying blog.0001_initial... OK
```
上のように表示されていればmigrationに成功しています。




































## 3. modelをadminページに追加する

管理画面から先程作ったmodelを見えるようにする為、from .models import Postで先程作ったPostクラスをimportして、admin.site.register(Post)をadmin.pyに追記します。

```blog/admin.py
from django.contrib import admin
from .models import Post

admin.site.register(Post)
```

管理画面にログインできるdjangoのsuper userを作成します。

```
python manage.py createsuperuser
```

管理画面を開きます。
http://localhost:8000/admin/
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/60e31428-87b1-ceb4-8c72-b6f61d000d96.png" width=50%>
UsernameとPasswordを入力すると管理画面に入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/686e938c-7ebe-e3cb-c36a-c83b68400501.png)
Postsの右にある追加ボタンを押せばpostを追加出来るようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/da290e14-1b44-2923-4b85-1aca9fda3f20.png)
適当なPostを記入してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/c1deaa69-029a-17dc-f441-ab8ad797ae74.png)
どうやらmodelは正常に動いているようです。

ちなみに、まだviewを変更してないのでトップページはロケットのままです。
http://localhost:8000/
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/d5ff3be2-02cd-2bea-10fc-0a084b86f538.png" width=75%>













































## 4. デプロイする

この章ではgithubにレポジトリを作り、PythonAnywhereにブログをデプロイします。

#### githubにpushする

まず、githubにログインしてレポジトリを作成します。レポジトリ名はmy-first-blogにしておきます。アカウントがない場合は無料で作れるのでアカウントを作成します。
https://github.com/

terminalを開いてgitをインストールします。

```terminal:terminal
sudo apt install git
```

インストール出来たらプロジェクトのディレクトリ(今回の場合は/home/ユーザー名/django/proj1/)でgit initしてからユーザー名とemailアドレスを登録します。以下はユーザー名「hoge」、メールアドレス「fuga@gmail.com」の場合です。

```terminal:terminal
git init
git config --global user.name hoge
git config --global user.email fuga@gmail.com
```

gitに反映しないファイル/ディレクトリを.gitignoreに登録しておきます。

```terminal:/home/ユーザー名/django/proj1/.gitignore
*.pyc
*~
/.vscode
__pycache__
myvenv
db.sqlite3
/static
.DS_Store
```

.gitignoreが反映されているか確認しておきます。

```terminal:terminal
git status
```

```terminal:出力
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        .gitignore
        blog/
        manage.py
        mysite/

nothing added to commit but untracked files present (use "git add" to track)
```

無事反映されているようですのでgithubにpushします。

```terminal:terminal
git add --all
git commit -m "first commit"
git remote add origin https://github.com/ユーザー名/my-first-blog.git
git push -u origin master
```
正常にpush出来ていればgithubのレポジトリにファイルが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/fc48b1cd-b4ff-1010-6036-1603a88a31a4.png)

#### PythonAnywhereにデプロイする

PythonAnyWhereのアカウントを作成します
https://www.pythonanywhere.com/

アカウントを作ったら右上のメニューからAccountページに移動してAPI Tokenを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/345454f6-9fb7-b117-c979-22a108c7adac.png)

Dashboardの左下のNew consoleを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/98160724-fd12-ce40-8169-af27304153c7.png)
開くと下のようにコンソールが開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/8dc4b7a6-11eb-c79f-adc4-d73953289a24.png)
ヘルパーツールをインストールして、ヘルパーツールによりgithubの内容をデプロイします。

```terminal:PythonAnywhere_command-line
pip3.6 install --user pythonanywhere
pa_autoconfigure_django.py --python=3.6 https://github.com/githubのユーザー名/my-first-blog.git
```
lsコマンドで同期されたファイルを確認してみましょう。

```terminal:PythonAnywhere_command-line
$ ls
blog  db.sqlite3  manage.py  proj1  static
$ ls blog/
__init__.py  __pycache__  admin.py  apps.py  migrations  models.py  tests.py  views.py
```
PythonAnywhereのFilesページから確認することも出来ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/b80a5c93-d76e-feef-41d4-732a82674325.png)
ヘルパーツールはgithubのコードを使って新規に仮想環境を構築していますので先程作ったdjango superuserはまだ登録されていませんから、下のコマンドで登録してやります。

```terminal:PythonAnywhere_command-line
python manage.py createsuperuser
```
これでローカルで作ったのと同じものがインターネットに公開された状態になります。PythonAnywhere DashboardからWebAppsページに移動すればサイトのリンクが貼ってありますので、そこから開くことが出来ます。

管理画面に入るとまだPostがないのが分かります。これは.gitignoreに書いてあるのでデータベースがgithubに同期されていないからです。うっかり本番環境のDBを上書きしない為に.gitignoreの設定は非常に重要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/686e938c-7ebe-e3cb-c36a-c83b68400501.png)



















































































## 5. viewを作る

ここでは実際にページを表示する為のviewを作り、viewまでのリンクを書いていきます。

#### viewまでのリンクを書く

urls.pyを開くと以下のように書かれていて、http://localhost/admin/ に来たらadmin.site.urlsを参照するよう指定されています。まだblogビューへのリンクが指定されていませんのでこれを書いていきます。

```python:proj1/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('blog.urls')),
]
```
トップページであるhttp://localhost/ に来たらproj1/blog/urls.pyを参照するように追記します。

```python:proj1/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('blog.urls')),
]
```
blog.urls.pyを作成して、http://localhost/ に来たらblog/views.pyからpost_listという名前のviewを参照するように指定します。

```python:proj1/blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
]
```
views.pyに上記で参照するように設定したpost_list関数を記述します。ここではblog/post_list.htmlの内容をそのまま返すようにしています。

```blog/views.py
from django.shortcuts import render

def post_list(request):
    return render(request, 'blog/post_list.html', {})
```

これで、http://localhost/ に来たらproj1/urls.py → proj1/blog/urls.py → proj1/blog/views.py → proj1/blog/post_list.htmlとリンクが繋がるようになりました。

ここでpython manage.py runserverしてページを開くと、まだproj1/blog/post_list.htmlがないのでエラーページが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/2c00f446-5d9e-0d02-febf-86aaa7e24aeb.png)
もしrunserverが失敗してページが表示されないときはここまで書いてきたどこかが間違ってますので確認しましょう。

#### view templateを作る

proj1/blog/templates/blog/post_list.htmlを作成して再度python manage.py runserverしてページを確認するとようやくエラーなしでページが表示されます。ただし、まだ何も書かれていないので何も表示されてないページになります。
![step1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/679aa932-3507-8b23-564b-b8e3266c25a4.png)
適当にhtmlを書けばそのまま表示されます。

```python:proj1/blog/templates/blog/post_list.html
<html>
<body>
    <p>Hi there!</p>
    <p>It works!</p>
</body>
</html>
```
![step3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/4b2f7e31-e30c-a82e-eb3e-53b0eeec34de.png)
ちょっとブログっぽいページを作ってみます。

```python:proj1/blog/templates/blog/post_list.html
<html>
    <head>
        <title>Django Girls blog</title>
    </head>
    <body>
        <div>
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>

        <div>
            <p>published: 14.06.2014, 12:14</p>
            <h2><a href="">My first post</a></h2>
            <p>Aenean eu leo quam. こんにちは！ よろしくお願いします！ </p>
        </div>

        <div>
            <p>公開日: 2014/06/14, 12:14</p>
            <h2><a href="">2番目の投稿</a></h2>
            <p> こんにちは！ よろしくお願いします！ </p>
        </div>
    </body>
</html>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/b7c15e54-4b38-f6a5-3b32-8fab255788ad.png)

#### デプロイする

ここまでの内容をPythonAnywhereにデプロイしてみます。

```terminal:/home/ユーザ名/django/proj1/
git add --all
git commit -m "changed the html for the site"
git push
```
PythonAnywhere command-lineを開いてpullします。

```terminal:~/PythonAnywhereユーザ名.pythonanywhere.com
git pull
```
PythonAnywhere DashboardからWebのページに移動し、Reloadしてからサイトを開くと同じ内容が表示される筈です。












































## 6. クエリセットの使い方

クエリセットを使えばdjangoがアクセスするのと同じようにデータベースに読み書き出来ます。これにより実際に動かしてみて思ったとおりの動作になった記述をview.pyに書いていくというやり方が出来ます。

django shellでクエリセットを開きます。

```terminal:terminal
python manage.py shell
```

PostクラスをimportしてPost.objects.all()ですべてのポストデータを表示してみましょう。

```python:command-line
>>> from blog.models import Post
>>> Post.objects.all()
 <QuerySet [<Post: たいとる１>, <Post: たいとる２>, <Post: たいとる３>]>
```
Postを追加してみます。

```python:command-line
>>> from django.contrib.auth.models import User
>>> me = User.objects.get(username='ユーザ名')
>>> Post.objects.create(author=me, title='Sample title', text='Test')
>>> Post.objects.all()
<QuerySet [<Post: たいとる１>, <Post: たいとる２>, <Post: たいとる３>, <Post: Sample title>]>
```
足したPostが末尾に追加されていますね。
続いてtitleに'title'が含まれるPostだけ表示してみます。

```python:command-line
>>> Post.objects.filter(title__contains='title')
<QuerySet [<Post: Sample title>]>
```
公開済みのPostを表示してみます。

```python:command-line
>>> from django.utils import timezone
>>> Post.objects.filter(published_date__lte=timezone.now())
<QuerySet []>
```
公開します。

```python:command-line
>>> post = Post.objects.get(title="Sample title")
>>> post.publish()
>>> Post.objects.filter(title__contains='title')
<QuerySet [<Post: Sample title>]>
```



djangoに登録したsuper userの一覧も表示できます。

```python:command-line
>>> User.objects.all()
<QuerySet [<User: ユーザ名>]>
```
投稿順に表示したり、逆順に表示したりもできます。

```python:command-line
>>> Post.objects.order_by('created_date')
<QuerySet [<Post: たいとる１>, <Post: たいとる２>, <Post: たいとる３>, <Post: Sample title>]>

>>> Post.objects.order_by('-created_date')
<QuerySet [<Post: Sample title>, <Post: たいとる３>, <Post: たいとる２>, <Post: たいとる１>]>
```
クエリを連ねて書くことも出来ます。

```python:command-line
>>> Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
<QuerySet [<Post: Sample title>]>
```
クエリセットからはexit()で抜けることが出来ます。

```python:command-line
exit()
```
























## 7. 動的なview templateを作る

データベースに保存されているPostの内容を表示するようにview templateを書いていきます。先程クエリセットでやったように公開済みのPostデータをテンプレートに渡して表示するように書いています。

```python:proj1/blog/views.py
from django.shortcuts import render
from django.utils import timezone
from .models import Post

def post_list(request):
    posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
    return render(request, 'blog/post_list.html', {'posts': posts})
```
先程作成したblog/post_list.htmlは静的なhtmlを書いていましたが、データベースから受け取った値を表示するように書き換えます。

{%  %}で囲んだ範囲はpythonスクリプトが実行され、{{  }}で囲んだ範囲には受け渡したオブジェクトの値が挿入されますので、postを順番に表示していくという記述になっています。

```python:proj1/blog/templates/blog/post_list.html
<html>
    <head>
        <title>Django Girls blog</title>
    </head>
    <body>
        <div>
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>

        {% for post in posts %}
            <div>
                <p>published: {{ post.published_date }}</p>
                <h2><a href="">{{ post.title }}</a></h2>
                <p>{{ post.text|linebreaksbr }}</p>
            </div>
        {% endfor %}
    </body>
</html>
```
これで管理ページで入力したPostの内容が表示されるようになった筈なので、python manage.py runserverで表示を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/435bb98a-c890-4210-39bd-6cba9b839c1c.png)
無事完成です。

























## 8. CSSでかわいくする

Bootstrapを使ってかわいいデザインを作っていきます。

まず、post_list.htmlのheadにリンクを貼ってBootstrapを使えるようにします。

```python:proj1/blog/templates/blog/post_list.htmlの<head>に追加
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/0aa0b745-2bb1-ddf2-c46c-a5b87bdbcd1e.png)
これだけでBootstrapのデフォルトCSSが設定されるのでちょっとデザインが変わります。

#### CSSファイルを作る

djangoはstaticと名付けられたフォルダを静的ファイルだと認識しますので、staticディレクトリ内にcssディレクトリを作って、その中にblog.cssを置いてやります。

```
proj1
└─── blog
     └─── static
          └─── css
               └─── blog.css
```
このファイルには普通にCSSを書いていきます。とりあえず見出しのaタグの色を変えてやりましょう。

```python:proj1/blog/static/css/blog.css
h1 a, h2 a {
    color: #C25100;
}
```
blog.cssを反映するように {% load static %} と {% static 'css/blog/css' %} をpost_list.htmlに追記してやります。

```html:proj1/blog/post_list.html
{% load static %}
<html>
    <head>
        <title>Django Girls blog</title>
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
        <link rel="stylesheet" href="{% static 'css/blog.css' %}">
    </head>
    <body>
        <div>
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>

        {% for post in posts %}
            <div>
                <p>published: {{ post.published_date }}</p>
                <h2><a href="">{{ post.title }}</a></h2>
                <p>{{ post.text|linebreaksbr }}</p>
            </div>
        {% endfor %}
    </body>
</html>
```
CSSが反映されて見出しのaタグの文字色がオレンジになりました。
![margin2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/f07a1558-152c-fe4b-fb43-786f3d875287.png)

#### 色々追加してみる

```css:proj1/blog/static/css/blog.css
h1 a, h2 a {
    color: #C25100;
    font-family: 'Lobster';
}
body {
    padding-left: 15px;
}
.page-header {
    background-color: #C25100;
    margin-top: 0;
    padding: 20px 20px 20px 40px;
}

.page-header h1, .page-header h1 a, .page-header h1 a:visited, .page-header h1 a:active {
    color: #ffffff;
    font-size: 36pt;
    text-decoration: none;
}

.content {
    margin-left: 40px;
}

h1, h2, h3, h4 {
    font-family: 'Lobster', cursive;
}

.date {
    color: #828282;
}

.save {
    float: right;
}

.post-form textarea, .post-form input {
    width: 100%;
}

.top-menu, .top-menu:hover, .top-menu:visited {
    color: #ffffff;
    float: right;
    font-size: 26pt;
    margin-right: 20px;
}

.post {
    margin-bottom: 70px;
}

.post h2 a, .post h2 a:visited {
    color: #000000;
}
```

```html:proj1/blog/templates/blog/post_list.html
{% load static %}
<html>
    <head>
        <title>Django Girls blog</title>
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
        <link href="//fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext" rel="stylesheet" type="text/css">
        <link rel="stylesheet" href="{% static 'css/blog.css' %}">
    </head>
    <body>
        <div class="page-header">
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>

        {% for post in posts %}
            <div class="post">
                <p>published: {{ post.published_date }}</p>
                <h2><a href="">{{ post.title }}</a></h2>
                <p>{{ post.text|linebreaksbr }}</p>
            </div>
        {% endfor %}
    </body>
</html>
```
![final.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/9ec140c6-6743-7177-52d6-17bb531782cc.png)
良さげになりました










## 9. テンプレートの拡張

viewテンプレートは共通する部分を使いまわす事ができます。

例として、先程作ったpost_list.htmlを2つのviewに分けてみましょう。基本デザインを設定する部分をbase.htmlに書いて、Post一覧を表示する部分をpost_list.htmlに書くようにします。

```
blog
└───templates
    └───blog
            base.html
            post_list.html
```
{% block content %} から {% endblock %} までのところにpost_list.htmlの内容を挿入します。

```html:proj1/blog/templates/blog/base.html
{% load static %}
<html>
    <head>
        <title>Django Girls blog</title>
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
        <link href="//fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext" rel="stylesheet" type="text/css">
        <link rel="stylesheet" href="{% static 'css/blog.css' %}">
    </head>
    <body>
        <div class="page-header">
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>
        <div class="content container">
            <div class="row">
                <div class="col-md-8">
                {% block content %}
                {% endblock %}
                </div>
            </div>
        </div>
    </body>
</html>

```
post_list.html側の1行目に {% extends 'blog/base.html' %} と書くことで base.html を拡張して使うように指示しています。

```html:proj1/blog/templates/blog/post_list.html
{% extends 'blog/base.html' %}

{% block content %}
    {% for post in posts %}
        <div class="post">
            <div class="date">
                {{ post.published_date }}
            </div>
            <h2><a href="">{{ post.title }}</a></h2>
            <p>{{ post.text|linebreaksbr }}</p>
        </div>
    {% endfor %}
{% endblock %}
```
サーバーを再起動してページを表示すると先程と同じものが表示される筈です。











## 10. アプリケーションの拡張

それぞれのblog postを表示するページを追加します。

##### テンプレートへのリンクを作成する

html://localhost/post/****/ に来たときに view.post_detail()関数を参照するように指定します。<int:pk> は文字列をint型に変換してpkに代入し、views.post_detail(request, pk) のように引数に与える事を意味しています。

```python:proj1/blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('post/<int:pk>/', views.post_detail, name='post_detail'),
]
```
proj1/blog/views.py に post_detail関数を追加します。また、Postオブジェクトに該当する記事がなかった場合に404を返すようにget_object_or_404()を使っています。render関数の第2引数で'blog/post_detail.html'をview templateとして使う事を指定しています。

```python:proj1/blog/views.py
from django.shortcuts import render, get_object_or_404

def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    return render(request, 'blog/post_detail.html', {'post': post})
```
記事を表示する為のview templateを追加します。base.htmlを使いまわしているので短く書けます。

```html:proj1/blog/templates/blog/post_detail.html
{% extends 'blog/base.html' %}

{% block content %}
    <div class="post">
        {% if post.published_date %}
            <div class="date">
                {{ post.published_date }}
            </div>
        {% endif %}
        <h2>{{ post.title }}</h2>
        <p>{{ post.text|linebreaksbr }}</p>
    </div>
{% endblock %}
```
最後にpost_list.htmlの記事タイトルを表示している<h2>タグにリンクを書き足して完成です。

```html:proj1/blog/templates/blog/post_list.html
<h2><a href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a></h2>
```
記事を表示するページが出来ました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/be5e4c4e-7c0b-8655-e410-9f7a747b4f6d.png)

#### デプロイする

ここまでの内容をgitにpushしてデプロイしてみましょう。

```terminal:terminal
git status
git add --all .
git status
git commit -m "Added view and template for detailed blog post as well as CSS for the site."
git push
```

```terminal:~/ユーザ名.pythonanywhere.com
cd ~/ドメイン名.pythonanywhere.com
git pull
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/5f95a7bb-5eea-af5d-cf7b-438928511b2c.png)
CSSが反映されていませんが、これはPythonAnywhereでは静的ファイルの置き場所が別になっているからです。ツールを使うと自動で配置してくれます。

```terminal:~/PythonAnywhereユーザ名.pythonanywhere.com
workon ユーザ名.pythonanywhere.com
python manage.py collectstatic
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/82399001-0c22-9d09-faee-d5310518675d.png)

## 11-1. 投稿フォームを作る

ここまでの操作ではブログPostを管理画面であるhttp://localhost/admin/ から入力していましたが、もう少し入力しやすいようにフォームを作る事が出来ます。


#### 投稿フォームを作る

```
blog
   └── forms.py
```

forms.ModelFormクラスを継承して一部だけ追記してフォームを作ってやります。ここではtitleとtextの入力するフォームにしています。

```proj1/blog/forms.py
from django import forms
from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ('title', 'text',)
```

#### 投稿フォームへのリンクをbase.htmlに貼る

base.htmlの上部に投稿フォームへのリンクを貼る為、以下の1行を追記します。class="glyphicon glyphicon-plus"にするとBootstrapの機能を使って＋を表示します。

```html:proj1/blog/templates/blog/base.htmlに追記
<a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
```


```html:proj1/blog/templates/blog/base.html修正後
{% load static %}
<html>
    <head>
        <title>Django Girls blog</title>
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
        <link href='//fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
        <link rel="stylesheet" href="{% static 'css/blog.css' %}">
    </head>
    <body>
        <div class="page-header">
            <a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>
        <div class="content container">
            <div class="row">
                <div class="col-md-8">
                    {% block content %}
                    {% endblock %}
                </div>
            </div>
        </div>
    </body>
</html>
```

#### urls.pyに追加

http://localhost:8000/post/new/ に来たらviews.post_new関数を呼ぶようにしてやります。

```python:blog/urls.pyに追加
path('post/new/', views.post_new, name='post_new'),
```

```python:blog/urls.py修正後
from django.urls import path 
from . import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('post/<int:pk>/', views.post_detail, name='post_detail'),
    path('post/new/', views.post_new, name='post_new'),
]
```

#### urls.pyが呼ぶ関数をviews.pyに追加する


```:blog/views.pyに追加
from django.shortcuts import redirect

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})
```
python manage.py runserver して、http://localhost:8000/post/new/ をブラウザで開くと以下のように新規投稿ページが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/dd99a5bb-25c3-da0b-7361-1868d0345df6.png)












## 11-2. 投稿済みPostの編集フォームを作る

先程と同じようにして記事の編集機能を追加してみます。


#### 編集フォーム

フォーム自体は新規投稿フォームと同じものを使います。


#### 編集ボタンを配置する

```html:blog/templates/blog/post_detail.htmlに追加
<a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>
```

```html:blog/templates/blog/post_detail.html修正後
{% extends 'blog/base.html' %}

{% block content %}
    <div class="post">
        {% if post.published_date %}
            <div class="date">
                {{ post.published_date }}
            </div>
        {% endif %}
        <a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>
        <h2>{{ post.title }}</h2>
        <p>{{ post.text|linebreaksbr }}</p>
    </div>
{% endblock %}
```

#### urls.pyにリンクを追加

```python:proj1.blog/urls.pyに追加
path('post/<int:pk>/edit/', views.post_edit, name='post_edit'),
```

#### views.pyに関数を追加する

```python:proj1/blog/views.pyに追加
def post_edit(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.method == "POST":
        form = PostForm(request.POST, instance=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(instance=post)
    return render(request, 'blog/post_edit.html', {'form': form})
```

## 11-3. ログイン時のみ編集可能にする

#### 新規投稿ボタン

```html:proj1/blog/templates/blog/base.html編集前
<a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
```

```html:proj1/blog/templates/blog/base.html編集後
{% if user.is_authenticated %}
    <a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
{% endif %}
```

#### 記事編集ボタン

```html:proj1/blog/templates/blog/post_detail.html編集前
<a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>
```

```html:proj1/blog/templates/blog/post_detail.html編集後
{% if user.is_authenticated %}
     <a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>
{% endif %}
```

これでログインしていない場合は編集ボタンが現れなくなりました。

#### デプロイ

```terminal:terminal
git status
git add --all .
git status
git commit -m "Added view and template for detailed blog post as well as CSS for the site."
git push
```

```terminal:~/ユーザ名.pythonanywhere.com
cd ~/ドメイン名.pythonanywhere.com
git pull
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/e9a79746-1699-e53c-d30d-8c0a1b2c4ed7.png)
というわけで完成です。
やったね