28
28

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.

Django 環境構築

Last updated at Posted at 2019-03-18

#はじめに
勉強ドキュメントです。
チームメンバーで加筆しあったWikiを元にしています。

DjangoとRuby on Railsのどちらで開発を進めるか検討しました。
詳しいメンバーがいなかったので、好みで決めることとなり、結果、Djangoを採用することに決めました。

諸々のバージョンは書いていませんでした…。
汎用的な内容が書いてあるので、ある程度通用すると思います。

ストックされる割にLGTMされないコンテンツ。
LGTMしてくれてもいいんだぜ!

#フレームワークの仕組みについて
MVCフレームワークと異なり、djangoはMTVという形態をとるそうです。
MVCと M = Model であることは共通ですが、V = View はそれぞれのフレームワークで意味が異なります。
MVCに慣れている人は特にViewの意味に注意が必要です。

MVCの場合 MTVの場合 備考
Model Model ここは一緒。
View Template 表示系をつかさどるのは同様。
呼び方だけ異なるイメージ。
Controller View 操作系をつかさどるのは同様?
ここも呼び方だけ異なるイメージ。

MVCだろうが、MTVだろうが、実装系をどこでやるかは個々の考え方やスキルに因るところが大きいようです。
MTVの場合の実装イメージは以下で統一するのが良いと思います。

  • Model

データベースとのインターフェース。プロパティの制約や結合などの外部キーはここに実装する。

  • Template

ModelのCRUDの見た目を実装。Modelが複雑になったりするとここにコードが多いと視認性とメンテナンス性が激低下しそう。
極力、表示系の実装のみに特化することが望ましい。

  • View

Modelの操作を実装。ここはゴリゴリになっちゃうと思われる。

#のちのちハマらないために
「制約の実装をModelに、見た目の実装をTemplateに。多段のifはクラス化して排除する。」を念頭にプログラムの階層を浅く。
MTVそれぞれの結合度を低く。頑張る!

#開発環境構築

Windowsの場合

Djangoで新しくプロジェクトを立ち上げるときは以下の操作で行う。
virtualenvを使ってね!
PythonとVS Codeがインストールされていることが前提です!

Djangoのインストールとプロジェクトの作成
 cd D:\python\projects
 pip install django
 django-admin startproject testpj
 cd testpj
 code .

VS Code起動したら、文字コードをUTF8に変更すること!
VS Codeでもvirtualenvの環境読み込むこと!

以下を実行後、開発用サーバーが起動するか確認すること。
ブラウザでこちらにアクセスし、"Congratulations!"と表示されればOK!

開発用サーバーの起動
 python manage.py runserver

管理者ユーザーを作成する。

管理者ユーザーの作成
 python manage.py createsuperuser

migrateしろってエラー出たら、migrateしてからcreatesuperuserする。

migrateする
 python manage.py migrate
 Operations to perform:
   Apply all migrations: admin, auth, contenttypes, sessions
 :
 :
 python manage.py createsuperuser
 Username (leave blank to use 'administrator'): administrator
 Email address: administrator@example.com
 Password:
 Password (again):
 Superuser created successfully.

Djangoのソースファイルの保存場所は以下のコマンドを実行すれば分かる。

Djangoのソースファイル確認
 python -c "import django; print(django.__path__)"

#本番環境構築
##インフラ環境
本番環境はLinuxが良いよねってことでCentOS7で準備しました。
CentOS7、Apache、mod_wsgi を使用します。

Djangoの実行ユーザを作成する

単純でOK。

ユーザー作成
useradd django

Djangoのプロジェクトごとにユーザを変更する場合は、各ユーザを同一グループに設定する。

##Python3.xをインストールする
Pythonは標準でyumから導入できない。ソースからコンパイルすることとなる。
pythonの仮想環境にはvenvを用いる、pyenvは使用しない。
CentOS7は元々Python2.4がインストールされているので、混在してしまう。
環境変数の設定に注意。

#開発
##アプリケーションの作成
プロジェクト内の機能という位置づけでアプリケーションを作成するらしい。

アプリケーションの作成
 python manage.py startapp <app_name>

アプリケーション名には _ (アンダースコア、アンダーバー)は使用しないこと。
djangoの動きを見ていると、テーブル名などに _ による区切りを使用しているので、競合する可能性が高いです。

アプリケーションを作成したら、プロジェクトの settings.py の INSTALLED_APPS に追記する。

settings.py
INSTALLED_APPS = [
   'django.contrib.admin',
   :
   :
   '<app_name>',
]

アプリケーションをdjango adminから編集させるには、アプリケーションディレクトリ内の admin.py に追記する。

/admin.py(DjangoAdminの有効化)
from .models import <model name>
admin.site.register(<model name>)

##Modelの定義
アプリケーションを作成したら、アプリケーションディレクトリ内のmodels.pyを編集する。

modelを作成するときの注意点

models.ForeignKey を使ったフィールドを作成する場合、models.pyの中で参照先となるクラスを先に定義する必要がある。
(上から順に読まれるので、最初に書けってこと。)

modelをいじったらやること

modelをいじったら、その変更をDBへ反映してあげる必要があります。

migrate
python manage.py check
python manage.py makemigrations <appname>
python manage.py migrate

create文など実行されるクエリは以下のコマンドで確認する。

クエリの確認
 python manage.py sqlmigrate <appname> <number>

modelの動作確認

対話型shellを使って動作確認できます。

modelの動作確認
python manage.py shell
from Computers.models import Maker
m = Maker(name = "hp", comment = "ヒューレットパッカード")
m.save()
m = Maker(name = "NEC", comment = "NEC")
m.save()
Maker.objects.all()
<QuerySet [<Maker: hp>, <Maker: NEC>]>

Maker.objects.all()の結果は、modelの__str__メソッドの結果が出力される。

DBを初期化する

基本的にやっちゃダメ!
どうしてもっていう人用

DBの初期化ですので、検証中や作成中のプロジェクトでのみ使用するように。
本番プロジェクトで実行したときには…目も当てられません。

modelをちまちまと変更しながらDBへマイグレーションしていると、DBに不整合が発生したりする。
特にUNIQUE、NOT NULL制約やForeignKeyを後から追加した場合など。
こうなると、DBへ手動で手を入れてテーブルの不整合などを直してあげる必要がある。
が、あれこれ考えるより、DBを初期化できる状況ならそれをやったほうが早そう。
…という意見もあったので執筆されています。

ちなみに、本例はsqlite3を利用している場合。

DB初期化
 rm -d -r db.sqlite3
 rm -d -r <appname>/migrations/*
 python manage.py makemigrations <appname>
 python manage.py migrate

その後、管理ユーザーがいなくなるので、再作成する。

管理ユーザー作成
 python manage.py createsuperuser

アプリケーションを跨いで外部キーを設定する

以下のような感じで、別アプリケーションのmodelを参照するように外部キー制約を設定できる。

外部キー設定
from <model名> import models as <別名>
 
class TestClass(models.Model):
    user_id = models.ForeignKey(<別名>.user_id, on_delete=models.CASCADE,verbose_name='ユーザID')

ただし、Djangoではアプリケーションの再利用性を高くするために、このような設計は出来るだけ使わない方が良いとされている。
これを使いたくなったら、アプリケーションやモデルの設計に間違いがないか検討しましょう。

##Templateの定義
djangoは urls.py というファイルでURLとアプリケーションのルーティング(マッピング)を行います。
ベストプラクティスだと思われる実装を記載します。

###フォルダーの階層構造

  • Project Root
    • Project Config
      • settings.py
      • urls.py
    • Applications
      • migrations
      • templates
        • Application Name
          • Class_detail.html
          • Class_list.html
      • admin.py
      • apps.py
      • models.py
      • tests.py
      • urls.py
      • views.py

##URLとviews.pyとtemplatesフォルダーの関連付けについて
ブラウザから入力されるURLと、views.py内のクラスとの関連付けはurls.pyで行います。
まずは、Project Configフォルダー内にある urls.py ファイルが参照されることにご注意ください。
例えば、Project Config/urls.pyが以下のようになっていたとします。

config/urls.py
from django.contrib import admin
from django.urls import path, include

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

すると、ブラウザから、http://…/ComputerAdmin/とアクセスされると、ComputerAdminアプリケーションフォルダー内のurls.pyを参照するようになります。
続いて./ComputerAdmin/urls.pyが以下のようになっているとします。

ComputerAdmin/urls.py
 from django.urls import path
 from . import views
 
 urlpatterns = [
     path('makers/', views.IndexView.as_view()),
     path('makers/<pk>', views.DetailView.as_view()),
 ]

views.pyの中で定義するカスタムクラスにdjango標準で準備されているview関連のクラスのうち、どのクラスを継承させるかで、マッピングされるURLが決まります。
例えば、views.pyの中で以下のように定義したとします。

views.py
from django.shortcuts import render
from django.views import generic

from . import models

class IndexView(generic.ListView):
    model = models.Maker

すると、./templates/Application_Name/Class_list.html にマッピングされます。
template_nameプロパティなどで、マッピングされるテンプレートをオーバーライドすることもできますが、マッピングルールを暗黙的に理解している人にとっては、コードを追わないといけなくなるので、特別な事情がない限り避けたほうが良いでしょう。

フォームの実装

あるModelのCRUDを実装するときに、Djangoのフォームを使うと楽できます。

ここでは、ModelFormを利用する例で解説します。まずコード。

Models.py
from django.db import models

class PC(models.Model):
    serial_num = models.CharField(primary_key=True,verbose_name='シリアルナンバー',max_length=20)
    pc_id = models.CharField(verbose_name='管理番号',max_length=8,unique=True,db_index=True)
    pc_model_name = models.ForeignKey(PCModel, on_delete=models.CASCADE,verbose_name='モデル名')
    Purchase_date = models.DateField('購入日')
    status = models.ForeignKey(Status, on_delete=models.CASCADE,verbose_name='ステータス')
    lend_user = models.ForeignKey(User, on_delete=models.SET_NULL,related_name='lend_user',verbose_name='貸出予定ユーザ',blank=True,null=True)
    remarks = models.CharField(verbose_name='備考',max_length=1000,blank=True,null=True)

    def __str__(self):
        return self.pc_id

上記の例では、PCというModelが定義しています。(他にも定義してますが、省略しています。)

forms.py
from django import forms
from .models import PC

class PCForm(forms.ModelForm):
    class Meta:
        model = PC
        fields = ('serial_num','pc_id','pc_model_name','Purchase_date','status','lend_user','remarks')

定義した「PC」ModelをForms.pyで読み込んでいます。(importしている)
さらに、「PCForm」という名前の「ModelForm」を定義しています。
「PC」Modelの形をしたフォームを定義して、「PC」Modelのフィールドの中で取り扱いたいものをチョイスしている感じです。
なんで、「class Meta:」で定義しないといけないのかは、分かりません…。

フォームを定義したら、viewの方でフォームを読み込ませてあげます。これで、簡単にModelに対応したフォームが作成できます。
PCを新規登録するviewを定義する場合はこんな感じ。もちろん、UpdateやDeleteも同じ要領で行けます。

views.py
from .models import User,PC
from .forms import PCForm
from django.views.generic import CreateView,UpdateView,ListView,DeleteView

class PCCreateView(CreateView):
    model = PC
    form_class = PCForm
    template_name = "management/form_pc.html"

class PCUpdateView(UpdateView):
    model = PC
    form_class = PCForm
    template_name = "management/form_pc.html"

「template_name = "management/form_pc.html"」のように、htmlはチコチコ用意する必要があるので、そこは少々面倒かもしれません。
ただ、サイトを作りこんでいくと、フレームワークが用意してくれたもので満足できなくなると思われるので、まあ良いかという感じです。

form_pc.html
{% extends "base.html" %}
{% block content %}
<form method="post">
  {% csrf_token %}
  <table>
  {{ form.as_table }}
  </table>
  <input type="submit" value="保存">
</form>
{% endblock %} 

「form」というのがオブジェクト変数みたいに使えます。
この例では「form.as_table」としてますが、「form.as_list」とかもできました。見た目の話なのでお好みで。

urls.pyには以下のパターンを追記します。

urls.py
 url(r'^create_pc$', PCCreateView.as_view()),

modelでForeignKeyの制約を掛けていると、勝手に入力フィールドがドロップダウンリストになったりしています。
このフォームは定義したModelに合わせてバリデーションチェックも勝手にやってくれます。
必須項目チェックとかデータ型とか。便利です。

ModelFormでフィールドの定義をオーバーライドする

ModelFormを利用した場合、外部キー制約を持たせているフィールドは、勝手にドロップダウンリストから選択する形式にします。
基本的には問題ないのですが、ドロップダウンリストの候補数が増えた場合に残念なUIとなるケースがあります。
そんな場合は、フォームのフィールドを定義をオーバーライドすることで解決します。
以下のケースでは、lend_userをオーバーライドし、テキストフィールドにした場合です。
もちろん、外部キー制約は有効なので制約違反の場合は勝手にバリデーションチェックで弾いてくれます。

forms.py
from django.forms import ModelForm,TextInput
from .models import PC

class PCForm(forms.ModelForm):
    class Meta:
        model = PC
        fields = ('serial_num','pc_id','pc_model_name','Purchase_date','status','lend_user','remarks')
        widgets = {
          'lend_user':TextInput(),
        }

フォームでフィルタ機能を実装する

django-filterを用いるのがお手軽で良かった。

導入手順

pipでインストールする。

django-filterのインストール
 pip install django-filter

setting.pyに以下を追記する。

setting.py
INSTALLED_APPS = [
  ...
  'django_filters',
]

正規表現でフィルタするパターン

アプリケーション名:management

models.py
 from django.db import models
 
 class PC(models.Model):
     serial_num = models.CharField(primary_key=True,verbose_name='シリアルナンバー',max_length=20)
     pc_id = models.CharField(verbose_name='PC管理番号',max_length=8,unique=True)
     pc_model_name = models.ForeignKey(PCModel, on_delete=models.CASCADE,verbose_name='モデル名')
     Purchase_date = models.DateField('購入日')
     status = models.ForeignKey(Status, on_delete=models.CASCADE,verbose_name='ステータス')
     lend_user = models.ForeignKey(User, on_delete=models.SET_NULL,related_name='lend_user',verbose_name='貸出予定ユーザ',blank=True,null=True)
     remarks = models.CharField(verbose_name='備考',max_length=1000,blank=True,null=True)
 
     def __str__(self):
         return self.pc_id
forms.py
from django import forms
from .models import PC
import django_filters

class PCForm(forms.ModelForm):

    class Meta:
        model = PC
        fields = ('serial_num','pc_id','pc_model_name','Purchase_date','status','lend_user','remarks')
        widgets = {
        }

class PCFilter(django_filters.FilterSet):
    #iregex:大文字小文字を区別しない正規表現
    pc_id = django_filters.CharFilter(label=("PC管理番号"),lookup_expr='iregex')
    serial_num = django_filters.CharFilter(label=("シリアルナンバー"),lookup_expr='iregex')
    remarks = django_filters.CharFilter(label=("備考"),lookup_expr='iregex')

    class Meta:
        model = PC
        fields = ('serial_num','pc_id','pc_model_name','Purchase_date','status','lend_user','remarks')
urls.py
from django.urls import path
from django.conf.urls import url
from . import views
from management.views import PCListView
from django_filters.views import FilterView
from .models import PC

urlpatterns = [
  url(r'^list_filter_pc/$', views.PCListView),
]
views.py(没例)
from django.shortcuts import render,get_object_or_404,redirect
from django.http import HttpResponse
from .models import PC
from .forms import PCForm,PCFilter
from django_filters.views import FilterView

def PCListView(request):
  f = PCFilter(request.GET, queryset=PC.objects.all())
  return render(request, 'management/list_filter_pc.html', {'filter': f})

上記例では、ものすごくクエリが遅くなったので、以下のようにして改善した。

改善例
f = PCFilter(request.GET, queryset=PC.objects.select_related('pc_id','user_name','os_name','configurator','domain_status').all())
templates/management/list_filter_pc.html
{% extends "base.html" %}
                              
{% block content %}

<h1>PC管理台帳 一覧表示</h1>
<h2>フィルタメニュー</h2>
<form action="" method="get">
  {{ filter.form.as_ul }}
  <input type="submit" />
</form>

<ul>
  <table border="1">
    <thead>
      <tr>
        <th>シリアルNo</th>
        <th>PC管理番号</th>
        <th>型番</th>
        <th>購入日</th>
        <th>ステータス</th>
        <th>貸与予定者</th>
        <th>備考</th>
      </tr>
    </thead>
    <tbody>
      {% for pc in filter.qs %}
      <tr>
        <td>{{ pc.serial_num }}</a></td>
        <td>{{ pc.pc_id }}</td>
        <td>{{ pc.pc_model_name }}</td>
        <td>{{ pc.Purchase_date }}</td>
        <td>{{ pc.status }}</td>
        <td>{{ pc.lend_user }}</td>
        <td>{{ pc.remarks }}</td>
      </tr>
      {% endfor %}
    </tbody>
  </table>
</ul>

{% endblock %}

WebAPIの実装

実装

以下のようなmodelを定義している例で解説する。

models.py
class User(models.Model):
  user_id = models.CharField(verbose_name='ユーザーID',max_length = 10,unique=True)
  user_name = models.CharField(verbose_name='ユーザー氏名',max_length=20)

まず、Django REST frameworkをインストールする。

djangorestframeworkのインストール
 pip install djangorestframework

settings.pyに以下を追記。

settings.py
INSTALLED_APPS = [
  'rest_framework',
  :

REST APIを作成するには最低限以下の3つを定義する必要がある

  • Serializer (モデルを API のデータに変換したり、逆に API のデータをモデルに変換したりする)
  • ViewSet
  • URL pattern

これらをAPI化したいModelに対してそれぞれ定義する。

monitomng/serializer.py
 from rest_framework import serializers
 from .models import User
 
 class UserSerializer(serializers.ModelSerializer):
     class Meta:
         model = User
         fields = ('user_id', 'user_name')
monitomng/views.py
 :
 from rest_framework import viewsets, filters
 from .serializer import UserSerializer
 
 class UserViewSet(viewsets.ModelViewSet):
     queryset = User.objects.all()
     serializer_class = UserSerializer
monitomng/urls.py
:
from monitormng.views import UserViewSet,MonitorViewSet
from rest_framework import routers

router = routers.DefaultRouter()
router.register(r'users', UserViewSet)

urlpatterns = [
  :
]
urls.py
:
from monitormng.urls import router as monitor_router

admin.autodiscover()

urlpatterns = [
  path('',            include('management.urls')),
  path('monitormng/', include('monitormng.urls')),
  path('admin/',      admin.site.urls),
  url(r'^api/',       include(monitor_router.urls)),
]

PowerShellからWebAPIを使って、HTTP Requestでデータをいじる

以下のようなmodelを定義している例で解説する。

models.py
class User(models.Model):
  user_id = models.CharField(verbose_name='ユーザーID',max_length = 10,unique=True)
  user_name = models.CharField(verbose_name='ユーザー氏名',max_length=20)

ここでは、PowerShellで解説するが、Linuxであればcurlを使うと良い。
調べている限りcurlを使った例は非常に多くヒットするので、迷うことはなさそう。

データの取得

オブジェクトをリストで取得
Web-API-GET-1
Invoke-RestMethod -Uri "http://localhost:8000/api/users/" -Method GET
オブジェクトを個別に取得
Web-API-GET-2
Invoke-RestMethod -Uri "http://localhost:8000/api/users/1" -Method GET

データの追加

Web-API-POST-1
 $postdata = @{ user_id=10; user_name="あいつ" }
 Invoke-RestMethod -Uri "http://localhost:8000/api/users/" -Method POST -Body $postdata

日付型や真偽型なども""でくくること。こんな感じ。

Web-API-POST-2
$postdata = @{
  monitor_id=1010;
  model_name=1;
  status=1;
  repair_period=3;
  repair_info="";
  onsite_repair="False";
  purchase_date="2018-05-20";
  remarks=""
}

データの削除

Web-API-DELETE
Invoke-RestMethod -Uri "http://localhost:8000/api/users/1" -Method DELETE

データの変更

Web-API-UPDATE
$hash = @{
  user_id = 1;
  user_name = "テストユーザさん";
}
$json = $hash | convertto-json
invoke-restmethod -uri "http://localhost:8000/api/users/1/" -body $json -method PUT -contenttype "application"
28
28
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
28
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?