はじめに
インターン先でDjangoに触れる機会があったので、どうせなら普段使いできるようなものをと思い、勉強がてら簡単な単語帳を作成したので、今回はそれについてまとめます。
概要
暗記系科目のテスト勉強では中高生時代はオレンジボールペン+赤シートで、学部時代はパワーポイントで問題スライドと答えスライドを順番につくることで簡単な単語帳を作っていました。今回はこれらに代わる単語帳をDjangoで作成していきます。求める機能は以下の2点です。
- 問題に対して答えを見るボタンを押すと答えが表示される
- 指定した問題カテゴリ(科目)の問題のみが表示される
作成物
問題ページ
問題ページの答えを見る
ボタンを押すと答えページに移動し問題に戻る
ボタンを押すと元の問題ページに戻ります。また上の問題カテゴリを指定すると指定されたカテゴリの問題のみが表示される仕様です。
実行環境
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 にアクセスして以下のような画面になれば成功です。
サーバを終了したいときは、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こちらにアクセスします。すると以下のようなログインが面が出てきます。
先ほど設定したユーザー名とパスワードを入れてログインに成功すると次のような画面に移動します。
Postsから問題カテゴリーと問題文と答えをそれぞれ登録できます。
以下のように追加しておきます。
カテゴリ | 問題 | 答え |
---|---|---|
英熟語 | 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">«</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">»</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/ にアクセスして完成していることを確認します。
最終的なディレクトリ構造
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で簡単な単語帳を作成しました。今後は暗記済みのものにチェックがつけられるようにしたり、問題文や答えに数式が書けるように改良していこうと思います。