Googleサーチコンソールを見ると、Djangoの複合キーについて調べて、以下ページにアクセスしてくれる人が多い。
そこで、GitHubで公開している複合主キー対応のパッケージについて、今更ながらREADMEの日本語版を。
https://github.com/Arisophy/django-compositepk-model
django-compositepk-modelパッケージ
複合主キーの対応のためのDjangoのModelクラスを拡張した CPkModelというクラスを提供する。Query クラスに対しても、複数カラムでのlookupsへの対応を追加したCPkQueryというサブクラスを提供する
本パッケージにより、複合主キーを使っているレガシーDBテーブルについて、サロゲートキーを追加することなくDjangoで扱えるようになる。
これはticket373に関する自分なりの解決方法です。
特徴
1. 利用方法が簡単
複合主キーを持つテーブルに対しては、基底クラスをModelクラスからCPkModelクラスに変更して、 Metaにunique_togetherを設定し、主キーを構成する各フィールド定義に primary_key=Trueを設定する。
(モデル定義の例)
from django.db import models from cpkmodel import CPkModel # Normal Model # primary_key is auto 'id' class Company(models.Model): name = models.CharField(max_length=100) established_date = models.DateField() company_code = models.CharField(max_length=100) class Meta: db_table = 'Company' # Child Model (CpkModel) # composite primary key: company_id, country_code class CompanyBranch(CPkModel): company = models.ForeignKey( Company, primary_key=True, # for CompositePK on_delete=models.CASCADE, ) country_code = models.CharField( max_length=100, primary_key=True, # for CompositePK ) name = models.CharField(max_length=100) established_date = models.DateField() class Meta: managed = False # for CompositePK *1 db_table = 'CompanyBranch' unique_together = (('company', 'country_code'),) # for CompositePK
それだけで、それ以外の追加定義、仮想フィールドの追加は必要ない。
*1: primary_key=Trueが複数カラムに設定されていると、マイグレーションが失敗するので、 managed = Falseとしている。レガシーDBのテーブルは既に存在するか、手でCREATEされると思うので。マイグレーションが使いたければ、migrationする時にprimary_key=Trueとmanaged=Falseをコメントアウトすること。
2. Admin画面が使える
CPkModelを継承したモデルも、DjangoのAdmin画面で使える。 複合キーの値は、 カンマ区切り表示される。 Change(Update)とDeleteは問題なく動く。 Add(Create) に関しては、 CreateViewがキー項目のそれぞれに対してユニークチェックをするので、最初の子レコードは登録できるけど、追加の子レコードが登録できなくなるという問題がある。 これは、CreateViewの問題なので、QuerySetやModelのメソッドを使うプログラムは問題ない。
3. 複数カラムでのlookupsに対応
複合主キーに対応するため、CPkQueryで複数カラムを指定したlookup(以下)に対応している。
obj = CompanyBranch.objects.get(pk=(1,'JP')) qs = CompanyBranch.objects.filter(pk='1,JP') qs = CompanyBranch.objects.filter(**{'company,country_code':(1,'JP')}) qs = CompanyBranch.objects.filter(**{'company_id,country_code':'1,JP'})
カンマの入ったLHS(左辺)は、主キーだけでなく、他のカラムの組み合わせもOK。
qs = CompanyBranch.objects.filter( **{'country_code,name':'JP,Google'})
IN句に対しても、複数カラムが使える。PostgreSQLは問題ないが、SQLite3についてはIN句に対応されていない(※Djangoの問題)のでエラーになる。
qs = CompanyBranch.objects.filter( pk__in=[(1,'JP'),(1,'US'),(2,'JP'),]) qs = CompanyBranch.objects.filter( **{'country_code,name__in':[('JP','HONDA'),('CN','SONY'),]})
4. bulk_updateが使える(v1.0.2)
bulk_updateメソッドも、PostgreSQLについて利用可能。SQLite3はサポートされていないので、不可。
Album.objects.bulk_update( albums, ['num_stars',])
制約
1. マイグレーション(table作成)
primary_key=Trueが複数カラムに設定されていると、マイグレーションが失敗するので、 managed = Falseとしている。 レガシーなテーブルは既に存在するか、手でCREATEされるので。マイグレーションが使いたければ、migrationする時にはprimary_key=Trueとmanaged=Falseをコメントアウトすること。
2. Admin画面でのCREATE(CreateViewの問題)
CreateViewがキー項目のそれぞれに対してユニークチェックをするので、レコードが一部しか登録できなくなる。 これは、CreateViewの問題なので、QuerySetやModelのメソッドを使うプログラムには問題ない。
3. 外部キー
孫テーブルへのリレーションをサポートするにはForeignKeyからCPkForeignKeyというクラスを作る必要があるが、まだ未対応。
4. SQLiteでIN句が使えない
Djangoのモデルは 、SQLiteのIN句に対応していない。
5. レコード登録は、saveよりCREATEが良い
INSERTについては、CPKModelのsaveを使うより、CPKQuerySetのcreateメソッドを使う方がいい。何故なら、Modelのsaveでは、キー値が設定されている場合、最初にUPDATEを行うので(その次にINSERTを実行)。 オプションでforce_insert=Trueを指定することで、避けることは可能ではあるが。
CompanyBranch.objects.create(**params) または obj = CompanyBranch(**params) obj.save(force_insert=True)
インストール方法
pip install django-compositepk-model
リンク
https://code.djangoproject.com/ticket/373
https://code.djangoproject.com/wiki/MultipleColumnPrimaryKeys
https://gijutsu.com/2021/01/19/django-composite-primary-key/
https://gijutsu.com/2021/03/16/django-ticket373/