12
14

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を使ってみる#3

Last updated at Posted at 2018-02-14

#概要
前回は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にアプリを登録します。

oss_project/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クラスに対応します。

oss_project/info/models.py
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へと誘導します。

まずクラスを作ります。

oss_project/info/views.py
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で結びつけます。

oss_project/urls.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を作成します。

oss_project/info/templates/info/base.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に埋め込まれるイメージです。

oss_project/info/templates/info/info_list.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を格納します。

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を以下のように修正します。

oss_project/oss_project/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の中身の表示はできました。次回はスクレイピングを行います。

12
14
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
12
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?