#はじめに
勉強ドキュメントです。
チームメンバーで加筆しあった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がインストールされていることが前提です!
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する。
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のソースファイルの保存場所は以下のコマンドを実行すれば分かる。
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 に追記する。
INSTALLED_APPS = [
'django.contrib.admin',
:
:
'<app_name>',
]
アプリケーションをdjango adminから編集させるには、アプリケーションディレクトリ内の admin.py に追記する。
from .models import <model name>
admin.site.register(<model name>)
##Modelの定義
アプリケーションを作成したら、アプリケーションディレクトリ内のmodels.pyを編集する。
modelを作成するときの注意点
models.ForeignKey を使ったフィールドを作成する場合、models.pyの中で参照先となるクラスを先に定義する必要がある。
(上から順に読まれるので、最初に書けってこと。)
modelをいじったらやること
modelをいじったら、その変更をDBへ反映してあげる必要があります。
python manage.py check
python manage.py makemigrations <appname>
python manage.py migrate
create文など実行されるクエリは以下のコマンドで確認する。
python manage.py sqlmigrate <appname> <number>
modelの動作確認
対話型shellを使って動作確認できます。
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を利用している場合。
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
- Application Name
- admin.py
- apps.py
- models.py
- tests.py
- urls.py
- views.py
- Project Config
##URLとviews.pyとtemplatesフォルダーの関連付けについて
ブラウザから入力されるURLと、views.py内のクラスとの関連付けはurls.pyで行います。
まずは、Project Configフォルダー内にある urls.py ファイルが参照されることにご注意ください。
例えば、Project 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が以下のようになっているとします。
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の中で以下のように定義したとします。
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を利用する例で解説します。まずコード。
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が定義しています。(他にも定義してますが、省略しています。)
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も同じ要領で行けます。
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はチコチコ用意する必要があるので、そこは少々面倒かもしれません。
ただ、サイトを作りこんでいくと、フレームワークが用意してくれたもので満足できなくなると思われるので、まあ良いかという感じです。
{% 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には以下のパターンを追記します。
url(r'^create_pc$', PCCreateView.as_view()),
modelでForeignKeyの制約を掛けていると、勝手に入力フィールドがドロップダウンリストになったりしています。
このフォームは定義したModelに合わせてバリデーションチェックも勝手にやってくれます。
必須項目チェックとかデータ型とか。便利です。
ModelFormでフィールドの定義をオーバーライドする
ModelFormを利用した場合、外部キー制約を持たせているフィールドは、勝手にドロップダウンリストから選択する形式にします。
基本的には問題ないのですが、ドロップダウンリストの候補数が増えた場合に残念なUIとなるケースがあります。
そんな場合は、フォームのフィールドを定義をオーバーライドすることで解決します。
以下のケースでは、lend_userをオーバーライドし、テキストフィールドにした場合です。
もちろん、外部キー制約は有効なので制約違反の場合は勝手にバリデーションチェックで弾いてくれます。
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でインストールする。
pip install django-filter
setting.pyに以下を追記する。
INSTALLED_APPS = [
...
'django_filters',
]
正規表現でフィルタするパターン
アプリケーション名:management
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
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')
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),
]
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())
{% 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を定義している例で解説する。
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をインストールする。
pip install djangorestframework
settings.pyに以下を追記。
INSTALLED_APPS = [
'rest_framework',
:
REST APIを作成するには最低限以下の3つを定義する必要がある
- Serializer (モデルを API のデータに変換したり、逆に API のデータをモデルに変換したりする)
- ViewSet
- URL pattern
これらをAPI化したいModelに対してそれぞれ定義する。
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('user_id', 'user_name')
:
from rest_framework import viewsets, filters
from .serializer import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
:
from monitormng.views import UserViewSet,MonitorViewSet
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns = [
:
]
:
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を定義している例で解説する。
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を使った例は非常に多くヒットするので、迷うことはなさそう。
データの取得
オブジェクトをリストで取得
Invoke-RestMethod -Uri "http://localhost:8000/api/users/" -Method GET
オブジェクトを個別に取得
Invoke-RestMethod -Uri "http://localhost:8000/api/users/1" -Method GET
データの追加
$postdata = @{ user_id=10; user_name="あいつ" }
Invoke-RestMethod -Uri "http://localhost:8000/api/users/" -Method POST -Body $postdata
日付型や真偽型なども""でくくること。こんな感じ。
$postdata = @{
monitor_id=1010;
model_name=1;
status=1;
repair_period=3;
repair_info="";
onsite_repair="False";
purchase_date="2018-05-20";
remarks=""
}
データの削除
Invoke-RestMethod -Uri "http://localhost:8000/api/users/1" -Method DELETE
データの変更
$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"