4
7

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 5 years have passed since last update.

【Django入門】簡単な単語帳を作る

Last updated at Posted at 2019-10-14

はじめに

 インターン先でDjangoに触れる機会があったので、どうせなら普段使いできるようなものをと思い、勉強がてら簡単な単語帳を作成したので、今回はそれについてまとめます。

概要

 暗記系科目のテスト勉強では中高生時代はオレンジボールペン+赤シートで、学部時代はパワーポイントで問題スライドと答えスライドを順番につくることで簡単な単語帳を作っていました。今回はこれらに代わる単語帳をDjangoで作成していきます。求める機能は以下の2点です。

  • 問題に対して答えを見るボタンを押すと答えが表示される
  • 指定した問題カテゴリ(科目)の問題のみが表示される

作成物

問題ページ

1571011401753.png

答えページ
1571011441015.png

問題ページの答えを見るボタンを押すと答えページに移動し問題に戻るボタンを押すと元の問題ページに戻ります。また上の問題カテゴリを指定すると指定されたカテゴリの問題のみが表示される仕様です。

実行環境

windows10
python 3.7.3
django 2.0.3

Djangoで作成

Djangoプロジェクトの作成

Djangoではアプリケーションの作成環境をプロジェクトと呼ばれる単位で管理します。ターミナル上でDjangoプロジェクトを作成したいディレクトリに移動して以下のように実行します。

$ python -m django startproject mybook

カレントディレクトリ上にmybookというプロジェクトのディレクトリが以下のような構造で作成されます。

mybook/
    manage.py
    mybook/
        __init__.py
        settings.py
        urls.py
        wsgi.py

mybookプロジェクトのディレクトリの中には、さらに同じ名前のディレクトリが1つ作成されています。これには、このプロジェクトで使うファイル類がまとめられています。Djangoプロジェクトでは、「プロジェクト名と同じ名前のディレクトリ」にプロジェクト全体で使うファイルが保存されいます。各ファイルについて簡単にまとめます。

ファイル名 説明
_init_.py Djangoプロジェクトを実行するときの初期化処理を行うスクリプトファイル
settings.py プロジェクトの設定情報を記述したファイル
urls.py プロジェクトで使うURLを管理するファイル
wsgi.py Webアプリケーションのメインプログラムとなる部分

manage.py : プロジェクトで実行する様々な機能に関するプログラム

言語とタイムゾーンの設定

mybook/settings.pyを以下のように変更します。

# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ja'

# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Tokyo'

サーバの起動

 ここまでで一度プロジェクトが動くかどうかを確認するために以下のコマンドを実行します。

mybook$ python manage.py runserver

実行したらhttp://127.0.0.1:8000 にアクセスして以下のような画面になれば成功です。

1571018767761.png

サーバを終了したいときは、Control+Cで終了させることができます。

アプリケーションの作成

 Djangoでは、プロジェクトの中にサブディレクトリとしてアプリケーションを作成します。こうすることで、1つのプロジェクトで複数のアプリケーションを管理できるようになっています。

 アプリケーションを作成するために以下のコマンドを実行します。

mybook$ python manage.py startapp vocaapp

mybookプロジェクトのディレクトリの中で以下のように作成されました。

mybook/
    manage.py
    mybook/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    vocaapp/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        views.py  

vocaappアプリケーションのディレクトリに含まれるファイルについて簡単にまとめます。

ファイル名 説明
「migrations」ディレクトリ データベース関係の機能のファイルがまとめられている
_init_.py アプリケーションの初期化のためのもの
admin.py 管理者ツールのためのもの
apps.py アプリケーション本体の処理をまとめる
models.py モデルに関する処理を記述するもの
tests.py プログラムテストに関するもの
views.py 画面表示に関するもの

アプリケーションの登録

 アプリケーションを作成をしたら、Djangoに「このプロジェクトでは、こういうアプリケーションがある」ということを知らせる必要があるのでアプリケーションの登録をします。

 mybook/settings.pyの「INSTASLLED_APPS」という変数の値を設定している部分に以下のように追記します。

INSTALLED_APPS = [
    'vocaapp.apps.VocaappConfig',  # 追記
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

モデルの作成

データベースに定義したいデータをvocaapp/models.pyに定義します。「モデル=テーブルの定義」と考えることもできます。

vocaapp/models.pyを以下のように書き換えます。

from django.db import models
from django.utils import timezone


class Category(models.Model):
    """問題カテゴリ"""
    name = models.CharField('カテゴリ名', max_length=255)
    created_at = models.DateTimeField('作成日', default=timezone.now)

    def __str__(self):
        """テキストの値を返す"""
        return self.name


class Post(models.Model):
    """問題本文と答え"""
    question = models.TextField('問題文')
    answer = models.TextField('答え')
    created_at = models.DateTimeField('作成日', default=timezone.now)
    category = models.ForeignKey(Category, verbose_name='カテゴリ', on_delete=models.PROTECT)

    def __str__(self):
        """テキストの値を返す"""
        return self.question

マイグレーション

 モデルが用意出来たら、次にマイグレーションと呼ばれる作業を行います。マイグレーションとはデータベースの移行を行うための機能です。あるデータベースから別のデータベースに移行する時、必要なテーブルを作成したりしてスムーズに移行できるようにするのがマイグレーションです。マイグレーションは以下の2つの作業からなります。

  • マイグレーションファイルの作成
  • マイグレーションの適用

それぞれ以下のコマンドで実行できます。

マイグレーションファイルの作成

mybook$ python manage.py makemigrations vocaapp

 これを実行すると、作成したモデルの情報などを元に、マイグレーションファイルを作成します。

マイグレーション

mybook$ python manage.py migrate

 これを実行することで、作成したマイグレーションファイルを適用してデータベースを更新する処理が行われます。

管理サイトを有効にする

 まずは管理サイトにログインするために管理者を登録しておく必要があります。以下のコマンドを実行することで登録することができます。

mybook$ python manage.py createsuperuser
  • Username: 管理者名
  • Email address:メールアドレス
  • Password:パスワード
  • Password(Again):パスワード(確認)

これらが順に尋ねられるのでそれぞれに答えます。

モデルの登録

 次に先ほど作成したモデルをadmin上で編集できるようにvocaapp/admin.pyを以下のように編集します。

from django.contrib import admin
from .models import Post, Category

# 管理サイトでmodels.pyで定義したものを操作できるようにする
admin.site.register(Post)
admin.site.register(Category)

 このようにできたら管理サイトにアクセスしてみます。

mybook$ python manage.py runserver

サーバを起動したらhttp://127.0.0.1:8000/adminこちらにアクセスします。すると以下のようなログインが面が出てきます。

1571025856062.png

 

先ほど設定したユーザー名とパスワードを入れてログインに成功すると次のような画面に移動します。

Postsから問題カテゴリーと問題文と答えをそれぞれ登録できます。

Posts→POSTを追加で以下のような画面に移動します。
1571026471039.png

以下のように追加しておきます。

カテゴリ 問題 答え
英熟語 apple りんご
英単語 be surprised at~ ~に驚く

adminサイトから簡単にモデルに追加したテーブルの一覧、登録、修正、削除を簡単にすることができます。

ビューの作成

 データベースからデータを受け取り問題を表示するための問題ページと答えページの部分を作成していきます。

まずvocaapp/views.pyを以下のようにします。

from django.views import generic
from .models import Post, Category


class IndexView(generic.ListView):
    model = Post
    paginate_by = 10

    def get_queryset(self):
        return Post.objects.order_by('-created_at')


class CategoryView(generic.ListView):
    model = Post
    paginate_by = 10

    def get_queryset(self):
        """
        category = get_object_or_404(Category, pk=self.kwargs['pk'])
        queryset = Post.objects.order_by('created_at').filter(category=category)
        """
        category_pk = self.kwargs['pk']
        queryset = Post.objects.order_by('-created_at').filter(category__pk=category_pk)
        return queryset


class DetailView(generic.DetailView):
    model = Post

vocaappアプリケーションディレクトリ内にurls.pyを作成する

 アプリのアドレスをアプリ内で管理させるようにします。vocaappアプリ内にURLを管理するファイルを作成して、vocaappのアドレスはそこですべて管理します。vocaapp内に新しくurls.pyを作成し、以下のように書きます。

from django.urls import path
from . import views

app_name = 'vocaapp'

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'), 
    path('category/<int:pk>/', views.CategoryView.as_view(), name='category'),
    path('detail/<int:pk>/', views.DetailView.as_view(), name='detail'),
]

続いて、プロジェクトのurls.py(mybook/urls.py)を以下のように修正します。

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('vocaapp.urls'))
]

テンプレートの作成

 表示される各画面用のHTMLファイルを保存するテンプレートディレクトリを作成する必要があります。Djangoでは、テンプレートは、各アプリケーションごとに「templates」という名前のディレクトリを用意し、その中に保管するようになっています。まずmybookプロジェクトの「vocaapp」ディレクトリの中に新しく「temlpates」ディレクトリを作成します。そしてさらにこの「temlpates」ディレクトリの中に「vocaapp」というディレクトリを作成し、そこでhtmlファイルを管理します。

ではこのvocaapp/templates/vocaappに以下の4つのhtmlを保存します。

base.html

<!doctype html>
<html lang="ja" xmlns:display="http://www.w3.org/1999/xhtml">
  <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>
  <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
  <a class="navbar-brand" href="{% url 'vocaapp:index' %}" >VOCABULARY BOOK</a>
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>

  <div class="collapse navbar-collapse" id="navbarSupportedContent">
    <ul class="navbar-nav mr-auto">
      <li class="nav-item active" >
        <a class="nav-link" style="color: #00fa9a;">問題カテゴリ</a>
        {% for category in category_list %}
          <a class="nav-link" href="{% url 'vocaapp:category' category.pk %}" style="display: inline;">{{ category }}</a>
        {% endfor %}
      {% if user.is_superuser %}
      <li class="nav-item">
        <a class="nav-link" href="{% url 'admin:index' %}">Admin</a>
      </li>
      {% endif %}
    </ul>
  </div>
  </nav>
    <div class="container">
      <div class="row">
        <div class="col-md-12">
        {% block content %}
        {% endblock %}
      </div>
     </div>
    </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>

page.html

{% load mytag %}
<nav aria-label="Page navigation">
  <ul class="pagination">

      {% if page_obj.has_previous %}
        <li class="page-item">
        <a class="page-link" href="?{% url_replace request 'page' page_obj.previous_page_number %}" aria-label="Previous">
          <span aria-hidden="true">&laquo;</span>
        </a>
      </li>
      {% endif %}

      {% for link_page in page_obj.paginator.page_range %}
        {% if link_page == page_obj.number %}
          <li class="page-item active">
            <a class="page-link" href="?{% url_replace request 'page' link_page %}">
              {{ link_page }}
            </a>
          </li>
        {% else %}
          <li class="page-item">
            <a class="page-link" href="?{% url_replace request 'page' link_page %}">
              {{ link_page }}
            </a>
          </li>
        {% endif %}
      {% endfor %}

      {% if page_obj.has_next %}
        <li class="page-item">
        <a class="page-link" href="?{% url_replace request 'page' page_obj.next_page_number %}" aria-label="Next">
          <span aria-hidden="true">&raquo;</span>
        </a>
      </li>
      {% endif %}
  </ul>
</nav>

post_detail.html

{% extends 'vocaapp/base.html' %}

{% block content %}
<div class="card mb-3 mt-4">
  <h4 class="card-header bg-dark text-white">{{ post.question }}</h4>
  <div class="card-body">
      <h3>{{ post.answer | linebreaksbr | urlize}}</h3>
      <br /><br />
      <a href="{{request.META.HTTP_REFERER}}" class="btn btn-dark">問題に戻る</a>
  </div>
</div>

{% endblock %}

post_list.html

{% extends 'vocaapp/base.html' %}

{% block content %}
{% for post in post_list %}
<div class="card mb-4 mt-4">
  <h4 class="card-header bg-dark text-white">問題</h4>
  <div class="card-body">
      <h3>{{post.question}}</h3>
   <br /><br />
    <a href="{% url 'vocaapp:detail' post.pk %}" class="btn btn-dark">答えを見る</a>
  </div>
</div>
{% endfor %}

{% include 'vocaapp/page.html' %}

{% endblock %}

各機能の追加

問題カテゴリを渡すためにvocaapp内にcontext_processors.pyを新しく作成し以下のように書きます。

from .models import Category


def common(request):
    """テンプレートに毎回渡すデータ"""
    context = {
        'category_list': Category.objects.all(),
    }
    return context

また、vocaapp内に新しくtemplatetagsディレクトリを作成しその中にmytag.pyを作成し以下のようにします。

from django import template

register = template.Library()


@register.simple_tag
def url_replace(request, field, value):
    """GETパラメータを一部を置き換える."""
    url_dict = request.GET.copy()
    url_dict[field] = value
    return url_dict.urlencode()

最後にmybook/settings.pyのTEMPLATESの部分に以下のように追記して完成となります。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'vocaapp.context_processors.common',
            ],
        },
    },
]

サーバを起動します。

mybook$ python manage.py runserver

実行したらhttp://127.0.0.1:8000/ にアクセスして完成していることを確認します。

1571037471193.png

最終的なディレクトリ構造

mybook/
    manage.py
    db.sqlite3
    mybook/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    vocaapp/
        migrations/
        	0001_initial.py
        	__init__.py
        templates/
        	vocaapp/
        		base.html
        		page.html
        		post_detail.html
        		post_list.html
        templatetags/
        	mytag.py
        __init__.py
        admin.py
        apps.py
        context_processors.py
        models.py
        tests.py
        urls.py
        views.py 

最後に

 今回はDjangoで簡単な単語帳を作成しました。今後は暗記済みのものにチェックがつけられるようにしたり、問題文や答えに数式が書けるように改良していこうと思います。

4
7
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
4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?