3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Python初心者によるDjango超入門!その4 超シンプルな日記アプリケーションを作ってみた(クラスベース汎用ビューを利用しないで関数のみで作成)

Last updated at Posted at 2020-08-17

本記事について

UdemyでDjangoについて学習した結果のアウトプットページです。
前の記事はこちらです。

今回は、Djangoで超シンプルな日記アプリケーションを作成したいと思います。
また、今回はDjangoの動作について理解を深めるためにクラスベース汎用ビューを利用せず、関数のみで作ります。

成果物

トップページ

詳細ページ

更新ページ

削除ページ

事前準備

まずは環境構築から。
Secondというプロジェクトを作成し、diaryというDjangoアプリを作成します。
プロジェクト名は任意ですが、可能なら半角英数字のみにしておいた方が無難です。
アンダーバーや空白スペースがプロジェクト名にあると後述の手順で謎のエラーが発生することがあります。

django-admin startproject Second
cd Second
py .\manage.py startapp diary

settings.pyにdiaryアプリを登録して、日本時間をセット

Second\Second\sattings.py
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という主キーが自動でセットされます。

Second\Second\models.py
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に行くように設定します。

Second\Second\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を以下のように記入。

Second\diary\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というファイルを作成し、
内容を以下のようにしてください。

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の神機能を使うことで簡単に書けるのですが、
今回の記事は関数で書いてみようというものなので、あえて関数で書きます。

Second\diary\views.py
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の値>'%}でリンクしましょう。

Second\diary\templates\diary\base.html
<!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を渡して、それぞれの記事の更新ページへアクセスする仕組みです。

Second\diary\templates\diary\day_index.html
{% 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上で改行を表現するためです。無くても問題はありません。

Second\diary\templates\diary\day_detail.html.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の作成

フォームの作成です。
記事の作成・更新で利用します。

Second\diary\templates\diary\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の作成

似たような内容が続きますが、これで最後です。
最後は削除ページです。

Second\diary\templates\diary\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で動作確認しましょう。
記事の作成・更新・削除が出来たら成功です。

次の記事は↓
https://qiita.com/sw1394/items/5d5af3356485c6980d92

3
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?