これは JSL(日本システム技研) Advent Calendar 2020 - Qiita の12/21分の記事です。
ちょっとしたDjangoアプリを作る中で、
- モデルのデータソースはYAMLにしたい
- YAMLはマスタ的存在
- モデル間のリレーションは存在しない
- YAMLを書き換えた場合、Djangoアプリを再起動するだけで、データが反映されるようにしたい
- migrateやfixtureのようなコマンドを使いたくない
- YAMLに対して、QuerySetの抽出系メソッドを使いたい
ということがありました。
DBの代わりに、YAMLをモデルと紐付けられないかを調べましたが、標準ではそれらしいものがありませんでした。
そこでライブラリがないかを探したところ、 yamdl
がありました。
andrewgodwin/yamdl: ORM-queryable YAML fixtures for Django
2017/8以降にコミットがないものの、試してみたところ希望通りの動作だったため、メモを残します。
目次
環境
モデルのデータソースとするYAML
りんごとその種子親・花粉親がまとまっているYAMLがあり、ここからデータをQuerySet経由でデータを抽出したいとします。
- name: 'シナノゴールド' seed: 'ゴールデンデリシャス' pollen: '千秋' - name: 'フジ' seed: '国光' pollen: 'デリシャス' - name: 'シナノゴールド' seed: 'ゴールデンデリシャス' pollen: '千秋' - name: '秋映' seed: '千秋' pollen: 'ツガル' - name: '王林' seed: 'ゴールデンデリシャス' pollen: '印度'
Djangoアプリの作成
Djangoプロジェクトの作成
いつも通り作成します。
$ django-admin startproject config . $ python manage.py startapp myapp
モデル
上記のYAMLファイルに対応した項目を持つモデルを作成します。
yamdlのREADMEに従い、モデルの中に __yamdl__ = True
と指定することで、YAMLをデータソースとしたモデルであると明示します。
また、いつロードされたのかを把握するため、 created_at
で登録日時を保持しておきます。
from django.db import models from django.utils import timezone class Apple(models.Model): name = models.CharField('名前', max_length=20) seed = models.CharField('種子親', max_length=20) pollen = models.CharField('花粉親', max_length=20) created_at = models.DateTimeField(default=timezone.now) # yamdl用の設定を追加 __yamdl__ = True
settings.py
いくつか追加します。
INSTALLED_APPS
INSTALLED_APPS = [ # 自分のDjangoアプリ 'myapp.apps.MyappConfig', ... # yamdl用 'yamdl', ]
DATABASES
今回はSQLiteファイル自体を生成しないようにするため、defaultで設定してあるSQLiteをインメモリへと変更します。
また、yamdl用のエントリも追加します。
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', }, 'yamdl': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'file:yamdl-db?mode=memory&cache=shared', } }
YAMDL_DIRECTORIES
データソースとなるYAMLファイルの置き場を指定します。
今回はBASE_DIRの下に content
ディレクトリを指定し、その中にYAMLファイルを置くことにします。
YAMDL_DIRECTORIES = [
BASE_DIR / 'content',
]
DATABASE_ROUTERS
こちらはREADMEそのままです。
DATABASE_ROUTERS = [
"yamdl.router.YamdlRouter",
]
View
今回は、全件取得してJSONを返すようにします。
日本語をそのまま表示するために ensure_ascii
を使います。
また、モデルの項目 created_at
がDateTime型なので、シリアライズ可能にするために cls
も指定しておきます。
https://docs.djangoproject.com/ja/3.1/topics/serialization/#serialization-formats-json
from django.core.serializers.json import DjangoJSONEncoder from django.http import HttpResponse from django.views import View from myapp.models import Apple class AppleView(View): def get(self, request, *args, **kwargs): apples = Apple.objects.all().values() data_json = json.dumps(list(apples), ensure_ascii=False, cls=DjangoJSONEncoder) return HttpResponse(data_json, content_type='application/json')
urls.py
よくある形です。
config/urls.py
urlpatterns = [ path('', include('myapp.urls')), ]
myapp/urls.py
urlpatterns = [
path('', AppleView.as_view()),
]
YAMLファイル
settings.pyの YAMDL_DIRECTORIES
で指定した通り、 manage.py
と同じ階層に content
ディレクトリを作ります。
その下に、 <Djangoアプリ名>.<モデル名>
のディレクトリを作成します。今回は myapp.Apple
となります。
さらにその下に、データソースであるYAMLファイル (今回はapple.yaml) を置きます。
全体としてはこんな感じになります。
$ tree -I env . ... ├── content │ └── myapp.Apple │ └── apple.yaml ├── manage.py ...
マイグレーションファイルの作成
今回はマイグレーションファイルのみ作成し、マイグレーションは行いません。
$ python manage.py makemigrations
ローカルでの動作確認
runserver後に localhost:8000
を確認すると、YAMLの内容がJSONレスポンスとして表示されました。
[ { "id": 1, "name": "シナノゴールド", "seed": "ゴールデンデリシャス", "pollen": "千秋", "created_at": "2020-12-17T13:08:27.095Z" }, { "id": 2, "name": "フジ", "seed": "国光", "pollen": "デリシャス", "created_at": "2020-12-17T13:08:27.098Z" }, { "id": 3, "name": "シナノゴールド", "seed": "ゴールデンデリシャス", "pollen": "千秋", "created_at": "2020-12-17T13:08:27.098Z" }, { "id": 4, "name": "秋映", "seed": "千秋", "pollen": "ツガル", "created_at": "2020-12-17T13:08:27.099Z" }, { "id": 5, "name": "王林", "seed": "ゴールデンデリシャス", "pollen": "印度", "created_at": "2020-12-17T13:08:27.099Z" } ]
Herokuでの動作確認
ローカルで動作したものがHerokuでも動作するようであれば、読込専用データを表示するためだけにHeroku Postgresを使わなくても良さそうです。
そのため、Herokuでも動作確認をしてみました。
Herokuアプリとしてのセットアップは以下を参考に行いました。
DjangoアプリをHerokuにデプロイする方法 - Qiita
主な内容は以下の通りです。
- ファイルの追加
- .gitignore
- requirements.txt
- Procfile
- runtime.txt
- 設定変更
- settings.pyで
ALLOWED_HOSTS = ['*']
- settings.pyで
- Herokuの設定変更
- 今回はcollectstaticを使わないため、
heroku config:set DISABLE_COLLECTSTATIC=1
を実行
- 今回はcollectstaticを使わないため、
デプロイ後、時間をおいて確認してみたところ、 created_at
以外は同じデータが表示されました。想定通りでした。
初回
[ { "id": 1, "name": "シナノゴールド", "seed": "ゴールデンデリシャス", "pollen": "千秋", "created_at": "2020-12-17T14:53:44.772Z" }, { "id": 2, "name": "フジ", "seed": "国光", "pollen": "デリシャス", "created_at": "2020-12-17T14:53:44.812Z" }, { "id": 3, "name": "シナノゴールド", "seed": "ゴールデンデリシャス", "pollen": "千秋", "created_at": "2020-12-17T14:53:44.812Z" }, { "id": 4, "name": "秋映", "seed": "千秋", "pollen": "ツガル", "created_at": "2020-12-17T14:53:44.813Z" }, { "id": 5, "name": "王林", "seed": "ゴールデンデリシャス", "pollen": "印度", "created_at": "2020-12-17T14:53:44.814Z" } ]
時間をおいた後
[ { "id": 1, "name": "シナノゴールド", "seed": "ゴールデンデリシャス", "pollen": "千秋", "created_at": "2020-12-21T23:16:06.099Z" }, { "id": 2, "name": "フジ", "seed": "国光", "pollen": "デリシャス", "created_at": "2020-12-21T23:16:06.123Z" }, { "id": 3, "name": "シナノゴールド", "seed": "ゴールデンデリシャス", "pollen": "千秋", "created_at": "2020-12-21T23:16:06.124Z" }, { "id": 4, "name": "秋映", "seed": "千秋", "pollen": "ツガル", "created_at": "2020-12-21T23:16:06.125Z" }, { "id": 5, "name": "王林", "seed": "ゴールデンデリシャス", "pollen": "印度", "created_at": "2020-12-21T23:16:06.125Z" } ]
ソースコード
Githubに上げました。
https://github.com/thinkAmi-sandbox/django_yamdl-sample