本記事について
UdemyでDjangoについて学習した結果のアウトプットページです。
前の記事はこちらです。
今回は、Djangoで超シンプルな日記アプリケーションを作成したいと思います。
また、今回はDjangoの動作について理解を深めるためにクラスベース汎用ビューを利用せず、関数のみで作ります。
成果物
事前準備
まずは環境構築から。
Secondというプロジェクトを作成し、diaryというDjangoアプリを作成します。
プロジェクト名は任意ですが、可能なら半角英数字のみにしておいた方が無難です。
アンダーバーや空白スペースがプロジェクト名にあると後述の手順で謎のエラーが発生することがあります。
django-admin startproject Second
cd Second
py .\manage.py startapp diary
settings.pyにdiaryアプリを登録して、日本時間をセット
INSTALLED_APPS = [
'diary',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
models.py
今回はDjangoのDBにデータを登録する処理が発生するので、models.pyでDBにどんなデータを入れるのか定義します。
逆に言うと、models.pyを見れば、DjangoのDBにどんなデータフィールドが用意されているのか分かるということです。
また、models.pyで明示的に主キーを設定しなかった場合、idという主キーが自動でセットされます。
from django.db import models
from django.utils import timezone
class Day(models.Model):
#ここで宣言はしていないけど、idという主キーが自動で登録されている
title = models.CharField('タイトル', max_length=200)
text = models.TextField('本文')
date = models.DateField('日付', default=timezone.now)
models.pyの入力が終わったら、manage.pyでDBに登録します。
manage.pyが保存されているフォルダに移動して、以下のコマンドで登録してください。
py .\manage.py makemigrations
py .\manage.py migrate
makemigrations実行時に、RecursionErrorが表示されることがあります。
RecursionError: maximum recursion depth exceeded while calling a Python object
エラーの原因の1つとして、プロジェクト名に半角英数字以外の文字があったり、
パスの中に空白スペースが含まれている場合に発生することがあります。
OneDirveなどの特殊なフォルダに保存していると空白スペースがパスに含まれることがありますので、注意してください。
urls.py
プロジェクト直下にあるurls.pyで、アプリ直下にあるurls.pyに行くように設定します。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('diary/', include('diary.urls')),
]
アプリ直下にあるurls.pyを以下のように記入。
from django.urls import path
from . import views
app_name = 'diary'
urlpatterns = [
path('', views.index, name='index'), #一覧ページ
path('add/', views.add, name='add'), #追加ページ
path('update/<int:pk>/', views.update, name='update'), #更新ページ
path('delete/<int:pk>', views.delete, name='delete'), #削除ページ
path('detail/<int:pk>', views.detail, name='detail'), #詳細ページ
]
アプリ直下のurls.pyにはURLを登録。
今回は一覧、追加、更新、削除、詳細の5ページを作成します。
<int:pk>
というのは、それぞれの記事に紐づいている主キーという意味です。
models.pyの説明時にidという主キーが自動登録されていると書きましたが、
Djangoではそのidをpkという変数で表現することが慣例となっています。
<int:id>
と表現しても良いのですが、<int:pk>
としておきましょう。
例えば、pkが1の詳細ページにアクセスする場合は、diary/detail/1/
になります。
特定の記事に紐づいていない、一覧ページや追加ページには不要です。
forms.py
views.pyの作成の前に、forms.pyというファイルを新規に作成します。
views.pyと同じフォルダ(Second\diary)にforms.pyというファイルを作成し、
内容を以下のようにしてください。
from django import forms
from .models import Day
class DayCreateForm(forms.ModelForm):
class Meta:
model = Day
fields = '__all__'
記事を作成したり、更新したりする場合、当然ですがHTMLファイルに入力欄が必要になります。
その入力欄を作成するにあたり、HTMLファイルにFormというものを構成する必要があります。
そのFormに対して、どんなデータを渡すのかを定義するのがこのforms.pyです。
今回はmodels.pyで定義されているDayクラスの全てのデータを渡すことにしましょう。
全てを渡す場合はfields = '__all__'
としてください。
DayCreateFormというクラス名は任意ですが、
その中にあるMetaというクラスは定型文です。
変更しないようにしましょう。
views.py
views.pyに一覧、追加、更新、削除、詳細のページを表示するための関数を作成します。
本来であれば、クラスベース汎用ビューというDjangoの神機能を使うことで簡単に書けるのですが、
今回の記事は関数で書いてみようというものなので、あえて関数で書きます。
from django.shortcuts import render, redirect, get_object_or_404
from .forms import DayCreateForm
from .models import Day
def index(request):
"""
日記の一覧を表示
"""
context = {
'day_list':Day.objects.all(),
}
return render(request, 'diary/day_index.html', context)
def add(request):
"""
日記の記事を追加
"""
# 送信内容を元にフォームを作る。POSTじゃなければ空のフォームを作成。
form = DayCreateForm(request.POST or None)
# method==POSTとは送信ボタンが押されたとき。form.is_validは入力内容に問題が無い場合Trueになる。
if request.method == 'POST' and form.is_valid():
form.save()
return redirect('diary:index')
# 通常時のアクセスや入力内容に誤りがあれば、再度day_form.htmlを表示
context = {
'form':form
}
return render(request, 'diary/day_form.html', context)
def update(request, pk):
"""
日記の記事を変更
"""
# urlのpkを基に、Dayを取得(pkはidと同じ)
day = get_object_or_404(Day, pk=pk)
# formに取得したDayを紐づける
form = DayCreateForm(request.POST or None, instance=day)
# method=POST(送信ボタン押下)、でかつ、入力内容に問題がなければフォームを保存する。
if request.method == 'POST' and form.is_valid():
form.save()
return redirect('diary:index')
# 通常時のアクセスや入力内容に問題があった場合は最初のページを表示
context = {
'form':form
}
return render(request, 'diary/day_form.html', context)
def delete(request, pk):
"""
日記の記事を削除
"""
# URLのPKを基に、Dayを取得(pkはidと同じ)
day = get_object_or_404(Day, pk=pk)
# method=POST(送信ボタン押下)
if request.method == 'POST':
day.delete()
return redirect('diary:index')
# 通常時のアクセス。または問題があった場合のアクセス。
context = {
'day':day
}
return render(request, 'diary/day_delete.html', context)
def detail(request, pk):
"""
日記の詳細ページ
"""
# URLのPKを基に、Dayを取得
day = get_object_or_404(Day, pk=pk)
context = {
'day':day
}
return render(request, 'diary/day_detail.html', context)
def index(request):
は記事の一覧を表示するための関数です。
models.pyにあるDayクラスの全てのデータを、contextに代入します。
render関数を使って、day_index.htmlに作成したcontextを渡しています。
def add(request):
は記事を新規作成するための関数です。
form = DayCreateForm(request.POST or None)
で送信内容を元にフォームを作ります。送信前の状態だと、空のフォームになります。
if request.method == 'POST' and form.is_valid():
は、送信ボタンが押され、かつ、内容に問題が無い場合の処理です。
問題が無いので、form.save()で保存して、index.htmlのページにリダイレクトします。
通常時のアクセスの場合や入力に誤りがあった場合は、render関数でday_form.htmlにcontextを渡します。
def update(request, pk):
は記事の内容を変更するための関数です。
このupdateページを表示する場合、URLはdiary/update/<記事のpk>になります。
更新対象となる記事の主キーが必要になるので、pkを引数に渡します。
URLで受け取ったpkをDayクラスに紐づけて記事の内容を以下のコードで取得します。
day = get_object_or_404(Day, pk=pk)
後の処理はadd関数と同じ流れです。
def delete(request, pk):
は記事を削除するための関数です。
ほとんどupdateと同じ処理ですが、削除なのでformを操作する必要が無いので、作成しません。
day.delete()で記事を削除し、index.htmlへリダイレクトします。
def detail(request, pk):
は記事の詳細ページを表示するための関数です。
formも作成する必要が無いのでシンプルです。
htmlファイルの作成
最後にhtmlファイルを作成します。
diaryフォルダの下にtemplatesフォルダを作成し、更にdiaryフォルダを作成します。
※<プロジェクト名>/<アプリ名>/templates/<アプリ名>と覚えると楽です。
base.htmlの作成
まずbase.htmlを作成します。
すべての大本となるhtmlファイルです。
※CSSやJavaScriptはBootstrapを利用しています。
nav部分で一覧ページと記事の新規作成をリンクしておきます。
リンクは{% url '<アプリ名>:<urls.pyで定義したnameの値>'%}
でリンクしましょう。
<!doctype html>
<html lang="ja">
<head>
<title>日記アプリケーション</title>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
</head>
<body>
<div class="container">
<nav class="nav">
<a class="nav-link active" href="{% url 'diary:index' %}">一覧</a>
<a class="nav-link" href="{% url 'diary:add' %}">追加</a>
</nav>
{% block content %}
{% endblock %}
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
</body>
</html>
day_index.htmlの作成
記事の一覧ページです。
views.pyから、day_listがKeyのcontextを渡されているので、
day_listを使ってDBに保存されている記事のタイトルと日付にアクセスできます。
これをfor文を使って表示させています。
updateとdeleteにはday.pkを渡して、それぞれの記事の更新ページへアクセスする仕組みです。
{% extends 'diary/base.html' %}
{% block content %}
<h1>日記一覧</h1>
<table class="table">
<thead>
<tr>
<th>タイトル</th>
<th>日付</th>
<th>更新処理</th>
<th>削除処理</th>
</tr>
</thead>
<tbody>
{% for day in day_list %}
<tr>
<td><a href="{% url 'diary:detail' day.pk %}">{{ day.title }}</a></td>
<td>{{ day.date }}</td>
<td><a href="{% url 'diary:update' day.pk %}">更新</a></td>
<td><a href="{% url 'diary:delete' day.pk %}">削除</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
day_detail.htmlの作成
詳細ページです。
title、text、dateを表示しています。
textの後に、linebreaksbrと付け足しているのは、
HTML上で改行を表現するためです。無くても問題はありません。
{% extends 'diary/base.html' %}
{% block content %}
<table class="table">
<tr>
<th>タイトル</th>
<td>{{ day.title }}</td>
</tr>
<tr>
<th>本文</th>
<td>{{ day.text | linebreaksbr}}</td>
</tr>
<tr>
<th>日付</th>
<td>{{ day.date }}</td>
</tr>
</table>
<a href="{% url 'diary:update' day.pk %}"><button class="btn btn-primary">更新</button></a>
<a href="{% url 'diary:delete' day.pk %}"><button class="btn btn-danger">削除</button></a>
{% endblock %}
day_form.htmlの作成
フォームの作成です。
記事の作成・更新で利用します。
{% extends 'diary/base.html' %}
{% block content %}
<form action="" method="POST">
<table class="tabel">
<tr>
<th>タイトル</th>
<td>{{ form.title }}</td>
</tr>
<tr>
<th>本文</th>
<td>{{ form.text }}</td>
</tr>
<tr>
<th>日付</th>
<td>{{ form.date }}</td>
</tr>
</table>
<button type="submit" class="btn btn-primary">送信</button>
{% csrf_token %}
</form>
{% endblock %}
day_delete.htmlの作成
似たような内容が続きますが、これで最後です。
最後は削除ページです。
{% extends 'diary/base.html' %}
{% block content %}
<form action="" method="POST">
<table class="table">
<tr>
<th>タイトル</th>
<td>{{ day.title }}</td>
</tr>
<tr>
<th>本文</th>
<td>{{ day.text }}</td>
</tr>
<tr>
<th>日付</th>
<td>{{ day.date }}</td>
</tr>
</table>
<p>こちらのデータを削除します。</p>
<button type="submit" class="btn btn-primary">削除</button>
{% csrf_token %}
</form>
{% endblock %}
動作確認
あとはpy manage.py runserverで動作確認しましょう。
記事の作成・更新・削除が出来たら成功です。