#概要
前回はApacheへのデプロイを学びました。今回はWebアプリ、といいたいのですが、もっと簡単にDBの中身を表示するだけのサイトを作ろうと思います。
OSSの更新情報をスクレイピングで集めて、DBに登録、DBの中身を一覧表示という感じです。スクレイピング部分は日次バッチで動かすので、Djangoには組み込みません。DjangoはDBの中身を表示するWebサイトとして作ります。
##参考
Djangoを最速でマスターする part1を参考にさせて頂きました。
(というかほとんど流用なので、ぜひ参考サイトをご覧いただきたく思います)
##環境
- Windows10 home
- Anaconda 5.0.1 (Just meでインストール)
- Python 3.6.3 :: Anaconda, Inc.
- Django2.0
+Apache HTTP Server2.4.29(以下Apache)
#手順
##プロジェクト作成
まずc:直下でプロジェクトを作成します。
>django-admin startproject oss_project
プロジェクトができます。
oss_project/
manage.py
oss_project/
__init__.py
settings.py
urls.py
wsgi.py
この最上層のoss_projectをルートディレクトリと呼ぶことにします。
次にoss_projectをカレントディレクトリにしてアプリ(のひな型)を作成します。次のコマンドを実行します。
>python manage.py startapp info
これでひな型ができました。ディレクトリは以下のようになっていると思います。
oss_project/
manage.py
info/
migrations
__init__.py
admin.py
apps.py
models.py
tests.py
views.py
oss_project/
__init__.py
settings.py
urls.py
wsgi.py
setting.pyにアプリを登録します。
…
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'info' #ここを追加
]
…
##データベース作成
テーブルは2つ作ります。ひとつはOSSの名前とOSSのid(識別番号)を登録します。もうひとつは、更新情報を貯めておくテーブルで、OSSのバージョン、OSSのリリース日、スクレイピングした日、OSSの番号を登録します。それぞれのテーブルはmodels.pyの中のclassで定義されます。それぞれOssクラスとInfoクラスに対応します。
from django.db import models
# Create your models here.
class Info(models.Model):
#OSSのバージョン
version = models.CharField(max_length=128)
#OSSのリリース日
released = models.DateField()
#スクレイピングした日
registrated = models.DateTimeField()
#ossの関係づけ
oss = models.ForeignKey('Oss',on_delete=models.CASCADE)
class Meta:
unique_together = (('version', 'oss'))
class Oss(models.Model):
#OSSのid
oss_id = models.IntegerField(primary_key=True)
#OSSの名前
oss_name = models.CharField(max_length=128)
クラスはmodels.Modelを継承して作成し、カラムはmodels.~で定義します。主キーを定義したい場合はprimary_key=Trueを引数に持たせます。primary_key=Trueがないクラスの場合、DBをマイグレーションしたときに自動的にidカラムが生成されます。
それぞれの型の詳細は調べてほしいのですが、ForeignKeyについて説明しておきます。SQLのJoinの感覚では、InfoクラスのテーブルにもOSSのidカラムを作り、OssクラスのテーブルとJoinしたくなります。しかし、Djangoでは、外部キーとして指定して、見に行くカラムが含まれるクラスを指定します(ここではOss)。すると、見に行ったクラスの主キーがカラムになります(ここではoss_id)。ちょっと慣れが必要かと思います。また、Django2.0からon_delete=models.CASCADEのように、外部キーが削除された場合の動作を記述することが必要です。
テーブルを生成するためのクラスを定義したら、DBを作成します。
> python manage.py makemigrations
> python manage.py migrate
デフォルトならば、sqlite3がデータベースとして設定されており、最上層oss_projectの直下にdb.sqlite3というファイルが生成されます。
##データ登録
次のコマンドでDjangoのシェルを起動します。
> python manage.py shell
次のコードを張り付け実行します。
from datetime import timezone, datetime
import pytz
from info.models import *
from dateutil import tz
#Ossクラスのテーブルに登録
Oss.objects.create(oss_id="1", oss_name="Apache httpd")
#Infoクラスのテーブルに登録
oss_id="1"#Apache httpdのid
version="2.4.29"#バージョン
released = date(2017, 10, 23)#リリース日
now = datetime.now().replace(tzinfo=tz.tzlocal())#現在時刻(JST)
registrated = datetime(now.year, now.month, now.day, now.hour, now.minute, now.second)#タイムゾーン情報を落とす
Info.objects.create(oss_id=oss_id, version=version, released=released, registrated=registrated)#SQLでいうところのInsert
データベースの中身を見てみましょう。sqlite3のパスを通してあれば、次のコマンドでDBに接続します。
>sqlite3 db.sqlite3
SQLite version 3.20.1 2017-08-24 16:21:36
Enter ".help" for usage hints.
sqlite>
接続していない場合はsqlite3.exeを検索してbinフォルダに移動し、db.sqlite3のところをフルパスで実行してください。
テーブル一覧を表示します。
sqlite>.tables
auth_group django_admin_log
auth_group_permissions django_content_type
auth_permission django_migrations
auth_user django_session
auth_user_groups info_info
auth_user_user_permissions info_oss
info_info、info_ossがあると思います。infoアプリのinfoクラス、ossクラスということでしょうか。実はよくわかってないです。
selectでテーブルの中身を見てみます。
sqlite>select * from info_info;
1|2.4.29|2017-10-23|2018-02-05 00:21:53|1
初期状態だとヘッダーがありません。sqliteの設定は.show
で確認できます。
sqlite> .show
echo: off
eqp: off
explain: auto
headers: off
mode: list
nullvalue: ""
output: stdout
colseparator: "|"
rowseparator: "\n"
stats: off
width:
filename: db.sqlite3
.headers ON
でヘッダーを表示し、.mode column
で表形式にしましょう。
sqlite> .headers ON
sqlite> .mode column
sqlite> select * from info_info;
id version released registrated oss_id
---------- ---------- ---------- ------------------- ----------
1 2.4.29 2017-10-23 2018-02-05 00:21:53 1
sqlite> select * from info_oss;
oss_id oss_name
---------- ------------
1 Apache httpd
一件データが登録されているのを確認できます。同様にほかのOSSもマスタ登録しましょう。今回はTomcatとOpenSSLを登録しました。
sqlite> select * from info_oss;
oss_id oss_name
---------- ------------
1 Apache httpd
2 Apache Tomcat
3 OpenSSL
##viewの作成
Djangoでは、WebページのURLをgetすると、それに対応するview.py内のクラスが呼び出されます。呼び出されたviewがDBに接続したり、その他の処理をして、htmlへと誘導します。
まずクラスを作ります。
from django.shortcuts import render
# Create your views here.
from django.shortcuts import render, redirect, get_object_or_404
from django.views.generic import TemplateView
from info.models import *
class InfoListView(TemplateView):
template_name = "info/info_list.html"
def get(self, request, *args, **kwargs):
context = super(InfoListView, self).get_context_data(**kwargs)
info_list = Info.objects.all().order_by("released").reverse() # データベースからオブジェクトを取得して
context['info_list'] = info_list # 入れ物に入れる
return render(self.request, self.template_name, context)
データベースからデータを取得し、info_listに格納、info_list.htmlへフォワードします。
URLとクラスはsetting.pyで結びつけます。
from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include
import info.views
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^info_list/', info.views.InfoListView.as_view())
]
これで、http://ホスト名/info_list/ とInfoListViewクラスが結びつきます。
##HTMLの作成
oss_project/info/templates/infoディレクトリを作成し、テンプレートとなるbase.htmlを作成します。base.htmlを継承してinfo_list.htmlを作成します。
<!DOCTYPE html>
<html lang="en">
<head>
{% load staticfiles %}
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>{% block title %}{% endblock %}</title>
<link href="{% static 'bootstrap/vendor/bootstrap/css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/vendor/metisMenu/metisMenu.min.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/dist/css/sb-admin-2.css' %}" rel="stylesheet">
<link href="{% static 'bootstrap/vendor/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'info/css/structure.css' %}" rel="stylesheet" type="text/css">
<script type="text/javascript" src="{% static 'bootstrap/vendor/jquery/jquery.min.js' %}"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.0/jquery-ui.min.js"></script>
<script src="{% static 'bootstrap/vendor/bootstrap/js/bootstrap.min.js' %}"></script>
<script src="{% static 'bootstrap/vendor/metisMenu/metisMenu.min.js' %}"></script>
<script src="{% static 'bootstrap/vendor/datatables/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'bootstrap/vendor/datatables-plugins/dataTables.bootstrap.min.js' %}"></script>
<script src="{% static 'bootstrap/vendor/datatables-responsive/dataTables.responsive.js' %}"></script>
<script src="{% static 'bootstrap/dist/js/sb-admin-2.js' %}"></script>
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div id="wrapper">
<nav class="navbar navbar-default navbar-static-top manager-nav no-margin" role="navigation">
<div class="navbar-header">
<a class="navbar-brand">OSS情報</a>
</div>
<div class="navbar-default sidebar" role="navigation">
<div class="sidebar-nav navbar-collapse">
<ul class="nav" id="side-menu">
<li><a href="/info_list/"><i class="fa fa-bar-chart" aria-hidden="true"></i> OSS更新情報一覧</a></li>
</ul>
</div>
</div>
</nav>
{% block body %}
{% endblock %}
</div>
</body>
</html>
通常、Webページのhtmlを作成していくと、ヘッダーなどの共通のパーツを作る必要があると思います。共通部分をbase.htmlとし、これを継承して各ページをつくることでhtml作成を効率化します。継承したhtml内の{% ~ %}部分がbase.htmlに埋め込まれるイメージです。
{% extends "info/base.html" %}
{% block title %}Info List{% endblock %}
{% load staticfiles %}
{% block body %}
<div id="wrapper">
<div id="page-wrapper">
<div class="row">
<div class="col-lg-6 full-width margin-top-20percent" >
<div class="panel panel-default full-width">
<div class="panel-heading">
Edit Help
</div>
<div class="panel-body full-width full-height">
<table id="info-list-table" class="table table-striped table-bordered table-hover dataTable no-footer dtr-inline full-width">
<thead>
<tr>
<th>OSS名</th>
<th>バージョン</th>
<th>リリース日</th>
<th>更新日</th>
</tr>
</thead>
<tbody>
{% for record in info_list %}
<tr>
<td>{{record.oss.oss_name}}</td>
<td>{{record.version}}</td>
<td>{{record.released|date:"Y/m/d"}}{{record.released|time:"H:i" }}</td>
<td>{{record.registrated|date:"Y/m/d"}} {{record.registrated|time:"H:i" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>、
</div>
</div>
<script>
$(document).ready(function() {
$('#info-list-table').DataTable({
responsive: true,
// sort機能の無効化
ordering: false,
// ページの表示件数を変更する
displayLength: 20,
});
});
</script>
{% endblock %}
ここでbootstrapを使用しています。oss_project/static/bootstrapディレクトリを作成し、https://startbootstrap.com/template-overviews/sb-admin-2/ からダウンロードしたディレクトリ内の、dist、js、vendorを格納します。
また、oss_project/static/info/cssにディレクトリを作成しstructure.cssを格納します。
/* ---------------------------------------------------------- */
/* general */
/* ---------------------------------------------------------- */
.full-height {
height: 100%;
}
.full-width {
width: 100%;
}
.margin-top-20percent {
margin-top: 20px;
}
.no-margin {
margin: 0 !important;
}
また、{% load staticfiles %}はsetting.pyに設定されているSTATIC_URL配下のファイルを読み込む動作をします。setting.pyを以下のように修正します。
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATIC_URL = '/static/'
# Static file settings
STATIC_ROOT = os.path.join(BASE_DIR, 'assets')
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
基本的に静的ファイルはSTATIC_ROOTで指定したディレクトリに格納します。このディレクトリとURLを結びつけるのがSTATIC_URLでドメイン+STATIC_URLを指定するとSTATIC_ROOTを見に行く仕組みです。基本、STATIC_ROOTはos.path.join(BASE_DIR, 'assets')のようにルートディレクトリの直下に置き、管理しますが、アプリごとに静的ファイルを用意する場合はSTATICFILES_DIRSにパスを指定します。Djangoは指定したパスを上から順にファイルを探しに行き、先に見つかったものを使用します。
##サーバ起動
runserverでサーバを起動します。
> python manage.py runserver 8000
http://localhost:8000/info_list にアクセスしてDBに登録したものが表示されることを確認します。
これでDBの中身の表示はできました。次回はスクレイピングを行います。