[Django]複合主キー

0.Djangoは複合主キーをサポートしている?

https://docs.djangoproject.com/ja/3.1/faq/models/#do-django-models-support-multiple-column-primary-keys

https://code.djangoproject.com/wiki/MultipleColumnPrimaryKeys

Djangoでは複合主キーのサポートは公式にはされていない。

「primary_key=True」が指定されているフィールドが無い場合は、自動で「id」が主キーとして追加されて動作するようなので、従来システムのDBを使っている場合、モデルを使った更新、削除が、このままでは動作しない。テーブルにidフィールドを追加するか、独自のクエリで処理する必要がある。

1.解決方法

案1:サロゲートキーを追加する

サロゲートキー(“id”)をDBに追加して、複合キーには一意制約(unique_together)を設定すれば、Djangoのモデルがそのまま使える。

(問題)DBの再構築に時間がかかり、計画停止が必要。

案2:独自クエリ―にて実装する

ORMに頼らずに、自分でクエリ―を書く実装にする。クエリの性能面等を自分でコントロールできる。

(問題)コードとテストが増える。

案3:Modelを拡張する

Djangoプロジェクトでも以前に検討されたようだが、最近はどうなっているかは、よく分からない。

https://code.djangoproject.com/ticket/373

https://code.djangoproject.com/wiki/MultipleColumnPrimaryKeys#CurrentStatus

色々、挑戦した人もいるみたいだが、結局、どれも未完か?

とりあえず、自分でモデルを拡張することで、自分の仕事で使えるように、Modelのサブクラスを作成する。とりあえずは、Djangoのすべての機能をカバーする必要はないので、あくまでも自分が必要とする単体モデルのINSERT,UPDATE,DELETEがカバーできるものならできそう。

(問題)現状のModelのコードを理解する必要がある。

案4:別のORMを作る

モデルまわりのコードを見たが、少しスパゲッティな感じもした。ざっと実装を調べていて感じたのは、Model、Manager、QuerySetでの役割がきれいに分かれてないように見受けられた。複合キー対応も含めて、簡素な自分のORM作るほうがいいような気もする。

(問題)そんな時間は無い!

案5:既にある別のORMを使う

以下は、主キーではなくて、あくまで複合外部キーのためか?

https://github.com/Arisophy/django-composite-foreignkey

(問題)適切なものが見つからない

2.結論

今のプロジェクトは、Ruby On Railsで作られたレガシーなシステムを、徐々にDjangoに移行しているところで、DBに手を加えたくない。

結論として、案2と案3で進めることにした。

INSERT,UPDATE,DELETEについては、案3でModelの拡張クラスで対応する。実際に作ってみたら、少ないコードで、いい感じで動いている。このままその拡張モデルを使いながら、プロジェクトを進めていく。

SELECTについては、そもそも、すべてORMに頼るわけにはいかないので、独自クエリ―も利用する。

拡張クラスをどう作ったかは、後日、公開します。

(2021/01/29)

以下で、パッケージを公開しました。

https://github.com/Arisophy/django-compositepk-model

(2021/06/06)

パッケージの日本語説明

(2021/03/16)

Djangoのticket373についての自分の見解は以下。

[Django]DB操作

1.モデル

https://docs.djangoproject.com/ja/3.1/topics/db/models/

1.1 オブジェクトの取得

オブジェクトを取得する

1)全件取得

MyModel.objects.all()

2)1件の取得

get() を用いて1つのオブジェクトを取得する

3)複雑な条件

以下で様々な条件が作れる。

Q オブジェクトを用いた複雑な検索

Field lookups

集計して、さらにその条件で検索するような場合は、以下を参考。

アグリゲーション

Filtering on annotations

1.2 リレーション

リレーション

関係により、以下クラスでFieldを定義する。リレーションを定義された側のモデルでは、自動で小文字のモデル名でフィールドが作成されて利用できるようになる。

反対向きのリレーション

1)多対一 (many-to-one) 関係

class ForeignKey(to, on_delete, **options)

必須ではありませんが、ForeignKey フィールド名 (上記の例では manufacturer はモデル名を小文字にしたものをおすすめします。

Djangoドキュメント:モデル-リレーション

2)多対多 (many-to-many) 関係

class ManyToManyField(to, **options)

3)一対一 (one-to-one) 関係

class OneToOneField(to, on_delete, parent_link=False, **options)

オブジェクトのhasattrは、以下でやる。

hasattr(object, 'field_name')

https://docs.djangoproject.com/ja/3.1/topics/db/examples/one_to_one/#one-to-one-relationships

4)実装例

Examples of model relationship API

5)その他

・キーは、無指定の場合、モデル名+’_id’となる。違う定義の場合は、db_columnで指定する。

Database Representation

・逆側のリレーションを作りたくない場合は、「related_name=’+’」

https://docs.djangoproject.com/ja/3.1/ref/models/fields/#django.db.models.ForeignKey.related_name

2.独自のクエリ

1)カスタムSQLの操作

https://docs.djangoproject.com/en/3.1/topics/db/sql/#executing-custom-sql-directly

2)トランザクション制御

https://docs.djangoproject.com/ja/3.1/topics/db/transactions/

明示的にトランザクションをコントロールする

3)素の SQL 文の実行

https://docs.djangoproject.com/ja/3.1/topics/db/sql/#performing-raw-sql-queries

3.複合主キーの扱い

https://docs.djangoproject.com/ja/3.1/faq/models/#do-django-models-support-multiple-column-primary-keys

https://code.djangoproject.com/wiki/MultipleColumnPrimaryKeys

複合主キーはサポートされていなくて、「primary_key=True」指定されているフィールドが無い場合は、「id」が主キーとして動作するので、従来システムのDBを使っている場合、モデルを使った更新、削除が、このままでは動作しない。テーブルにidフィールドを追加するか、独自のクエリで処理する必要がある。

3.Manager

3.1 カスタムマネージャーの作成

DB取得等のロジックをViewの中に色々書くのもいまいちなので、カスタムマネージャーを作成する。

(Django公式)マネージャーのカスタマイズ

上記の日本語版は、翻訳が微妙で間違った解釈になるところがあるので、英語版を読んだ方が良さそう。

(Django公式)Custom managers

拡張クラスにメソッドを定義、あるいはget_querysetを書き換える等をコーディングするなり、QuerySetを定義するなりする。以下に4パターンの方法が記載されている。

(Django公式ドキュメント)

①カスタムMangerにメソッド追加する方法

Adding extra manager methods

②カスタムMangerの初期クエリセットを変更する方法

Modifying a manager’s initial QuerySet

③QuerySetを拡張してメソッド追加する方法

Calling custom QuerySet methods from the manager

④QuerySetを複数拡張する場合に、Managerをクエリセットから定義できる方法

Creating a manager with QuerySet methods

上記から適切な方法で実装していく。

4.QuerySet

https://docs.djangoproject.com/ja/3.1/ref/models/querysets/

Pythonコーディング

0)ドキュメント

Pythonでのコーディングする時の参考ページ。

(ゼロからのパイソン講座)

https://www.python.jp/train/index.html

(Pythonドキュメント)

https://docs.python.org/ja/3/

https://docs.python.org/ja/3/contents.html

4.8. 間奏曲: コーディングスタイル

(コーディング規約:PEP 8)

https://pep8-ja.readthedocs.io/ja/latest/#

https://www.python.org/dev/peps/pep-0008/

1)Nullチェック

Pythonでは、NullではなくてNone。

None オブジェクト

チェックには、「is」演算子がある。

6.10.3. 同一性の比較

2)例外

4.3. 例外

8.4. try

例外を投げるのは、throwではなくて、raiseとのこと。

7.8. raise 文

組み込み例外

3)可変長引数

4.7. 関数定義についてもう少し

4.7.4. 任意引数リスト

argsについては記載されているが、kwargsの記載は見つけれらず。完全な文法仕様を見ると定義されている。

10. 完全な文法仕様

arguments:
    | args [','] &')' 
args:
    | ','.(starred_expression | named_expression !'=')+ [',' kwargs ] 
    | kwargs 
kwargs:
    | ','.kwarg_or_starred+ ',' ','.kwarg_or_double_starred+ 
    | ','.kwarg_or_starred+
    | ','.kwarg_or_double_starred+
starred_expression:
    | '*' expression 
kwarg_or_starred:
    | NAME '=' expression 
    | starred_expression 
kwarg_or_double_starred:
    | NAME '=' expression 
    | '**' expression 

4)型(class)チェック

class type(object)

型オブジェクト

isinstance(object, classinfo)

isinstance を使って、以下のように判断できる。

# 文字かのチェック
if isinstance(value, str):
    :

# 整数型か文字かのチェック
if isinstance(value, [int, str]):
    :

5)文字列の操作

文字列メソッド

6)辞書型

マッピング型 — dict

7)クラスについて

https://docs.python.org/ja/3/tutorial/classes.html

7-1) 静的メソッドとクラスメソッド

クラスメソッド:classmethod

静的メソッド:staticmethod

共に、「@」を付けてデコレータとして使うらしいが、普通に関数として@を付けずに使うこともできるらしい。クラスメソッドと静的メソッドの違いは、継承された場合に出てくる。

クラスメソッドが派生クラスから呼び出される場合は、その派生クラスオブジェクトが暗黙の第一引数として渡されます。

Pythonドキュメント:@classmethod
  • クラスメソッドは派生(継承)したクラスで動くので、派生クラスに影響される。
  • 静的メソッドは派生クラスに影響されない。

7-2) クラス名からクラスの取得

klass = globals()[classname]

8)多重継承

9.5.1. 多重継承

9.5.1. Multiple Inheritance ※一応、原文で確認したほうがいい

検索の基本は以下。

  1. 深さ優先
  2. 左から右

9)日付型

datetime

10)列挙型

https://docs.python.org/ja/3/library/enum.html#module-enum

11)複数の配列を同時処理する場合

zipで配列をまとめればいい。

https://docs.python.org/ja/3/library/functions.html#zip

ちなみに、ループカウンタを同時に使いたい場合は、以下を使う。

https://docs.python.org/ja/3/library/functions.html#enumerate

12)タプルについて

タプルの場合、リストの内包表記のように書くとGenaratorとなるので、以下のように「tuple」で明示的に変換する必要がある。

names= tuple((f.name for f in keys))

名前付きのタプルを使うことで、プログラムが見やすくなる。

https://docs.python.org/ja/3/library/collections.html#collections.namedtuple

名前付きタプルの一部値を変更した新しいタプルの作成は以下。

https://docs.python.org/3/library/collections.html#collections.somenamedtuple._replace

13)メタクラス

https://docs.python.org/ja/3/reference/datamodel.html#metaclasses

14)ソート

https://docs.python.org/ja/3/howto/sorting.html

15)イテレータ型

https://docs.python.org/ja/3/library/stdtypes.html?highlight=dictionary#iterator-types

・最後の要素は、インデックスが-1で取得できる。後ろから3要素なら、data[-3]で取得できる。

DjangoでWebアプリ

既存システムのDBを使って、Djangoで新たなWebアプリを作っている作業メモ。

1.開発環境と既存DBの取り込み

とりあえず、Visual Studio Community 2019で、既存DBのモデルに取り込みまでは、以下で行った。

2.アプリ開発

チュートリアルを参考に改造していく。

(Django公式)はじめての Django アプリ作成、その 1 | Django ドキュメント

2.1 DBの内容をページに表示

まずは、既存DBのモデルにてデータを取得し、ページに表示する。流れとしては、Viewでmodelを使ってデータを取得し、それをテンプレートに渡して表示させる。

1)ビュー

以下を参考に、モデルを取り込んで、データをテンプレートに渡すようにする。

(Django公式)実際に動作するビューを書く

app/views.pyを修正していく。

①読み込みたいmodelをimport
from .models import DBモデル
②modelを操作してデータを取得

データベース操作について、公式サイトのリンクが以下にまとめてくれてあるので、ありがたく参考にさせてもらう。

(Qiita)Django データベース操作 についてのまとめ

2)テンプレート

app/templates/app以下のテンプレートを修正していく。テンプレートの記述言語についての説明は以下ページ。

(Django公式)

テンプレート

The Django Template Language

Viewから渡されたデータは、「{{ 変数名 }}」と記述して使えるので、それをページ内に表示させる。

2.2 カスタムマネージャーの作成

DB取得等のロジックをViewの中に色々書くのもいまいちなので、カスタムマネージャーを作成する。

(Django公式)マネージャーのカスタマイズ

上記の日本語版は、翻訳が微妙で間違った解釈になるところがあるので、英語版を読んだ方が良さそう。

(Django公式)Custom managers

拡張クラスにメソッドを定義、あるいはget_querysetを書き換える等をコーディングするなり、QuerySetを定義するなりする。以下に4パターンの方法が記載されている。

(Django公式ドキュメント)

①カスタムMangerにメソッド追加する方法

Adding extra manager methods

②カスタムMangerの初期クエリセットを変更する方法

Modifying a manager’s initial QuerySet

③QuerySetを拡張してメソッド追加する方法

Calling custom QuerySet methods from the manager

④QuerySetを複数拡張する場合に、Managerをクエリセットから定義できる方法

Creating a manager with QuerySet methods

上記から適切な方法で実装していく。

1)カスタムマネージャーの定義

app/managers.pyを追加して、カスタムマネージャーを定義する。

"""
Definition of DB managers.
"""

from django.db import models

# Managerの拡張クラスを定義
class XxxxxxManager(models.Manager):
    :

データの絞り込みは、filterを使う。

(Django公式ドキュメント)

フィルタを用いて特定のオブジェクトを取得する

フィールドルックアップ

2)モデルの修正

app/models.pyの修正して、モデルのobjectsを書き換える、あるいは目的に応じたmanagerを取得するメソッドと追加する。

3)有効期間内のデータを対象とする例

テーブル(Information)に有効期間として、from_timeとto_timeを持っていて、現在有効なデータだけを対象とする場合の例。「②カスタムMangerの初期クエリセットを変更する方法」で実装。

① manager.py
"""
Definition of DB managers.
"""

from django.db import models
from django.db.models import Q
from django.db.models.functions import Now


# Information
class InformationManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(
            Q(from_time__lte=Now()) & Q(to_time__gt=Now()))

filterに、「Q(from_time__lte=Now()) & Q(to_time__gt=Now())」とすることで、「from_time <= CURRENT_TIMESTAMP AND to_time > CURRENT_TIMESTAMP」という条件になる。

(Django公式ドキュメント)

Q オブジェクトを用いた複雑な検索

Now関数

② models.pyの修正
"""
Definition of models.
"""

from django.db import models
from .managers import InformationManager


# Create your models here.

class Information(models.Model):
    id_information = models.AutoField(primary_key=True)
    from_time = models.DateTimeField(blank=True, null=True)
    to_time = models.DateTimeField(blank=True, null=True)
    title = models.CharField(max_length=128, blank=True, null=True)
    detail = models.TextField(blank=True, null=True)
    created_at = models.DateTimeField()
    updated_at = models.DateTimeField()

    objects = models.Manager() # The default manager.
    news = InformationManager() # Custom manager

    class Meta:
        managed = False
        db_table = 'information'

Viewでは、Information.newsで対象データを取得する。

2.3 デバッグ

Django Debug Toolbarを入れる。

Django Debug Toolbar

ドキュメントに従い設定したが、Visual Studio からDjangoの開発用サーバーで実行すると、ツールバーが表示されない。

Chromeのエラーを見ると、toolbar.jsの読み込みがはじかれている。

Failed to load module script: The server responded with a non-JavaScript MIME type of "text/plain".

とりあえず、setting.pyに以下を追加することで解決。

# For Debug Toolbar
if DEBUG:
    import mimetypes
    mimetypes.add_type("text/javascript", ".js", True)

3.UIデザイン

デザインは苦手なので、Bootstrapのサンプルを参考にする。

https://getbootstrap.jp/docs/4.5/examples/

1)最新のBootstrapを入れる

app/static/app/content,scripts下のBootstarp、JQuery、Popper等の必要なものを入れる。

CDNを利用するのがいいのかもしれないが、とりあえず開発時点ではstaticに取り込む。

2)サンプルのCSS,JSを入れる

使うサンプルのCSS,JSもapp/static/app/content,scripts下に入れる。

3)サンプルを参考にテンプレートを作成していく。

トップページは、Pricingを参考に。管理画面はDashboardを基に作ることにする。

4.セキュリティー設定

1)admin

adminについて、必要ないなら消す。カスタマイズして使っていく予定なら、IP制限を入れる。以下を利用した。

django-admin-ip-restrictor

Visual StudioでDjango

Visual Studio Community 2019 で、Djangoで開発を始めたので、作業メモ。とりあえず、Microsoftのドキュメントからチュートリアルを参考に。

(MS公式)チュートリアル: Visual Studio での Django Web フレームワークの概要

チュートリアルは前の2017バージョンだけど参考に、軽く動かしながら、実際に作りたいものに変えていく。

【主な変更作業】

Webプロジェクト テンプレートから、自分用に変更していく。

(MSサイト)手順 4: Django Web プロジェクト テンプレートを使用する

1 Viewの変更

まずは見た目からということで、自分の作りたいページ構成に変えていく。

  • app/templates/layout.html:タイトル、メニュー、フッター等を修正
  • app/views.py:templateの中で表示するデータを修正

2 DB変更

DBを、業務で使っているPostgreSQLに変更する。使うドライバとしては、以下のPsycopgがよく使われているらしいので、それを使うことにする。

(Psycopg公式)Psycopg – PostgreSQL database adapter for Python

1)ドライバのインストール

psycopg2パッケージをインストールする。インストール方法は以下ページを参考に。

(MS公式)[Python 環境] ウィンドウ タブ リファレンス

ソリューションエクスプローラーの[Python環境]>[Env]を右クリックして[Pythonパッケージの管理]からも行ける。

2)DB接続の設定

以下ページを参考に、settings.pyのDATABAESを修正。

(django公式)djangoドキュメント – Settings -DATABASES

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydatabase',
        'USER': 'mydatabaseuser',
        'PASSWORD': 'mypassword',
        'HOST': '111.111.111.1111',
        'PORT': '', # 空白の場合はデフォルト
    }
}

3)既存の業務DBの定義を取り込む

以下を参考に、models.pyに既存DBの定義を追加する。

(Djangoi公式)レガシーなデータベースと Django の統合

ソリューションエクスプローラーから[Python環境] を右クリックして、[すべてのPython環境を表示]して、実行環境を選択して、下の[PowerShell で開く]。

PowerShellで、以下コマンドを実行。

$ python manage.py inspectdb

以下のエラーが出て、うまく定義を作成できないよう。

Unable to inspect table 'XXXXXXXXXX'
The error was: syntax error at or near "WITH ORDINALITY"
LINE 6: FROM unnest(c.conkey) WITH ORDINALITY co…

PostgreSQLがVer9.2と古くて対応していないので、いい機会なのでバージョンアップすることに。

PostreSQLをバージョン13にしたら、問題なくinspectdbができたので、models.pyに定義を追加する。

4)DBのマイグレーション

PostgreSQLにadmin関係のテーブルを作成する。Microsoftのドキュメントの手順をい参考に。

(MS公式)手順 6-1:プロジェクトを作成し、データベースを初期化する

ソリューションエクスプローラーで、プロジェクトを右クリックして、[Python]>[Django で移行を実行する]を行う。その後、[Django でスーパーユーザーを作成する]を実行。

3.setting.pyの修正

日本語設定等を行う。

(Django公式)設定 | Django ドキュメント

とりあえず、変えたところは以下。

LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'

【アプリ作成】

Django公式のチュートリアルを参考に、仕組みを勉強しながら作業。

4.Djangoのバージョンアップ

Visual Studio 2019 のプロジェクトテンプレートは、Django2.2なので、最新のDjango3.1に変更した。

プロジェクトフォルダ直下のrequirement.txtのバージョンを修正する。

django~=3.1

Python環境の実行環境(ディフォルトならenv)を選択して、[パッケージ(PyPI)]を選択して、表示されている「Django2.2.*」の横の上矢印でバージョンアップするか、プロジェクトを再読み込みすれば画面上部にメッセージが出るのでクリックしてバージョンアップ。

テンプレートファイルの、staticsfilesの読み込みが、エラーになるので、テンプレートファイルを修正する。

【旧】{% load staticfiles %}
↓
【新】{% load static %}

5.テストについて

https://docs.microsoft.com/ja-jp/visualstudio/python/unit-testing-python-in-visual-studio?view=vs-2019