PUROGU LADESU

ポエムがメインのブログです。

【Django】クエリを作成する

クエリを作成する | Django ドキュメント | Django

データベースからオブジェクトを取得するには、モデルクラスの Manager から QuerySet を作ります。
各モデルは少なくとも一つの Manager を持ち、デフォルトでは objects という名前を持ちます。
SQL 文においては、 QuerySet は SELECT 句、フィルターは WHERE や LIMIT のような絞り込みに用いる句に対応しています。

特定のオブジェクトの取得

Entry.objects.all()
filter() 一致するもの
exclude() 一致しないもの
それぞれメソッドチェーンもできる。

getは条件のデータが存在しない場合、2つ以上マッチした場合は例外を発生する(DoesNotExist, MultipleObjectReturned)
filter()[0]でスライスした場合は、IndexErrorとなる

指定がない場合exactでマッチされる。
iexactは大文字小文字を判別しない。
contains部分一致

リレーションを横断

model__field='zzz'
データがなかった場合、リレーションがなかったのか、値がなかったのか判別できない

pk

ぜんぶ同じ
id_exact=1
id=1
pk=1

F()

クエリの中でモデルフィールドを参照するときに使う
演算も対応。
Entry.objects.filter(number_of_comments__gt=F('field_name') * 2)

アンダースコア2つで連結して、関連モデルのデータを参照できる
F('blog__name')

時間の計算もできる
F('pub_date') + timedelta(days=3)

LIKE

下記のLIKEが使われるクエリでは、%と_はSQLではエスケープされる
iexact, contains, icontains, startswith, istartswith, endswith and iendswith

クエリのキャッシュ

QuerySetはデータベースアクセスを最小限にするために内部キャッシュを持つ
QuerySetを変数に格納しておくと、評価する際にキャッシュが使われる。

queryset = Entry.objects.all() # 一旦格納

QuerySetの一部しか評価されなかった場合、キャッシュは使われない。
全体を一度評価した場合は、その後キャッシュが使われる。
いきなり queryset[5] をするとキャッシュが使われないが、一旦全部取得した後実行するとキャッシュが使われる。

[entry for entry in queryset] # 全てにアクセス

JSONFieldの検索

Noneを指定するとSQLのNULLが格納される。JSONとしてのnullはValue('null')を使う。
検索も同様にSQLのNULLはisnullを使う。

JSON内部の値を検索するにはダブルアンダースコアでチェーンする。
配列にはインデックス番号が指定できる。
データベース製品による挙動の違いに注意。

Dog.objects.filter(data__owner__other_pets__0__name='Fishy')

その他のメソッド
contained_by、has_key、has_keys、has_any_keys

Qオブジェクトを使った複雑な検索

from django.db.models import Q
Q オブジェクトは & や | 演算子を使って結合することができます

Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

SELECT * from polls WHERE question LIKE 'Who%'
AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

オブジェクトの比較

イコールを使ったオブジェクト同士の比較はプライマリーキーの比較と同等である。

オブジェクトのコピー

組み込みのコピー機能は無いが、pkをNoneにして保存すると新たなpkが生成されるのでコピーと同等である。
blog.pk = None
blog.save()

一括更新

更新できるのはメインのテーブルのみ。関連テーブルは更新できない。
リレーションの外部キーの更新はできるので、関連データのインスタンスを渡す。
F()を使ったフィールドの指定ができるが、joinは使えないので同一テーブルのみ。

Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

関連オブジェクト

関連オブジェクトがある場合のアクセスは、単純に外部キー指定されているフィールドを指定する。
反対に関連オブジェクト側から元のオブジェクトのmanagerにアクセス出来る。
名前xxx_setとなりモデル名は小文字となる。

ForeignKey定義時にrelated_nameオプションを指定している場合、xxx_setはそれで上書きされる。

独自のmanagerを使う場合はmanagerオプションに指定する。
entry_set(manager='entries')

メソッド
add, create, remove, clear, set

【Django】モデル

モデルを定義してマイグレーションしてDBを作成するのが目的です。
あと自動生成される管理画面の出来栄えはモデルに依存するでしょう。

モデル | Django ドキュメント | Django

models.Model

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

django.db.models.Model のサブクラスにします。
id フィールドは自動的に追加されますが、しないこともできます。
各フィールドはField クラスにする。フィールドはデータ型によって選択します。
フィールド名はpython予約語やダブルアンダースコアは使えない。

フィールドオプション

null: Trueでnull許容。デフォルトはFalse。nullはデータベース由来。
blank: Trueでブランクを許容。デフォルトはFalse。Falseはバリデーションで入力必須になる。
choices: 選択肢。タプルのリストで指定。タプルの1つめがDB値、2つ目が表示名になる。
表示名はget_FOO_display() メソッドで取得可能。

  SHIRT_SIZES = (
      ('S', 'Small'),
      ('M', 'Medium'),
      ('L', 'Large'),
  )

default: デフォルト値。
help_text: ウイジェットで表示できる説明。
primary_key: Trueにするとそのフィールドがモデルの主キーになる。値の変更は不可。ない場合はIntegerFieldが自動で追加される。

unique: 一意制約をつける。
verbose_name: 詳細フィールド名。最初の文字は大文字にしない。一部を除きキーワード引数を使わず第一引数に指定できる。

  • 自動インクリメント

id = models.AutoField(primary_key=True)
明示的にprimary_keyがどこにも設定されていない場合はAutoFieldのidフィールドが自動追加される。あれば追加されない。

リレーション

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

models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
親となるモデルをForeignKeyで定義する。on_deleteで親が消えたときに自分も削除する。

自己結合する場合は、models.ForeignKey('self')を使う。

リレーションシップフィールド
モデルフィールドリファレンス | Django ドキュメント | Django

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

toppings = models.ManyToManyField(Topping)
どちらか片方に定義する。両方にしない。

リレーションテーブルの追加フィールドを定義する場合は別途モデルを作成し
そこでForeignKeyを2つと追加フィールドをつける。

remove() 関連する中間モデルの全削除
clear()

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

user = models.OneToOneField

ForeignKeyとUniqueを設定したのと同じだが、関連モデルからの参照は単一オブジェクトを返す。
モデルを継承して拡張する方法もある。

モデルフィールドリファレンス | Django ドキュメント | Django

複数テーブルの継承
モデル | Django ドキュメント | Django


Meta

並び替えなど、フィールド以外のオプションを定義できる。

class Meta
  ordering = ["horn_length"]
  verbose_name_plural = "oxen"

Model Meta options | Django ドキュメント | Django


属性

  • objects

マネージャー。名前はobjects。クエリ操作のインターフェース。モデルのインスタンスを取り出す。

マネージャ | Django ドキュメント | Django

objects = models.Manager()

メソッド

モデルインスタンスを操作する

モデルインスタンスリファレンス
https://docs.djangoproject.com/ja/3.1/ref/models/instances/

モデルの継承

  • 抽象クラス

これでデータベースの作成はされなくなり、継承させる役割のみになる。継承させるならこちらのパターンになる場合が多いのでは。
逆にabstractを使わなければ、厳密に正規化されてベースモデルのテーブルが作成され、子モデルのテーブルは差分のみ作成される。one-to-oneの関係になり常に結合して使うことになる。

class Base(model.Model):
    class Meta:
      abstract = True
  • Meta の継承

定義しなければ親のMetaを継承。サブクラス化すれば親のMetaを拡張できる。

class CommonInfo(model.Model):
  class Meta(CommonInfo.Meta):
  • プロキシモデル

データベースに影響を与えず、既存のモデルの振る舞いだけ変えたい場合。
リリース済み既存のモデルを修正する場合は、プロキシモデルの利用を検討するとよいのかも。

class MyPerson(Person):
    class Meta:
        proxy = True
   ...

まとめ

意外とモデルの継承とone-to-one関係が重要な気がします。
モデルとしての役割分担だけでなく、どんなテーブル構成になるのか把握しておく必要があります。

【Django】フォームフィールド

フォームクラスを作る際、フィールド定義に使うオプションなど。

class UserForm(forms.Form):
  f = forms.EmailField(xxx=xxx)

Form fields | Django ドキュメント | Django

Field.clean(value)

f = forms.EmailField()
f.clean('xxx')
渡された値でフォームのバリデーションを行う。
問題があればValidationErrorを出し、問題なければチェック済みのその値を返す。

cleanをオーバーライドして独自のcleanを実装するのも有効。
clean_xxxxでフィールドごとのcleanも定義可能。

required

デフォルトでは必須入力となっているので、Noneや空文字を渡すとバリデーションエラーとなる。
スペースや0やFalseやTrueは問題ない。

必須にしない場合

f = forms.CharField(required=False)

Field.label

デフォルトのラベルは、単語の先頭を大文字に、アンダースコアを空白になるが、変えたいとき。
name = forms.CharField(label='Your name')

Field.label_suffix

ラベルの末尾につける文字。Formクラスで指定すれば、すべてのフィールドに反映される。
forms.IntegerField(label='2 + 2', label_suffix=' =')
f = ContactForm(label_suffix='?')

Field.initial

forms.URLField(initial='http://')
初期値の指定。
通常通りディクショナリでフォームにデータを渡してしまうとバリデーションが働いてしまうため、エラーが出てしまう。
unbound formsでのみ表示される。

initialは入力がなかったときの初期値にはならない。あくまでも表示用。
callableを渡すことも出来るが、これは定義されたときでなく表示されたときに発動する。

Field.widget

forms.ChoiceField(label='出版社', widget=forms.RadioSelect()
フィールドにはデフォルトの入力コントロールが割り当てられているが、それを変えることができる。

  • ビルトインのウイジェット

https://docs.djangoproject.com/ja/3.1/ref/forms/widgets/#built-in-widgets
TextInput,NumberInput,DateInput,FileInputなど

  • デフォルトのウイジェットはこちらで確認

https://docs.djangoproject.com/ja/3.1/ref/forms/fields/#built-in-fields
BooleanField,CharField,ChoiceField,FileField,ImageField,IntegerFieldなどなど

Field.help_text

sender = forms.EmailField(help_text='A valid email address, please.')
as_ul()などを使うときにフィールドの隣に表示される説明文。

Field.error_message

forms.CharField(error_messages={'required': 'Please enter your name'})
デフォルトのエラーメッセージを上書きする。

Field.validators

even_field = models.IntegerField(validators=[validate_even])
フィールドに対するバリデーション関数で、モデルでもフォームでも使えます。
自作もできるし、ビルトイン関数もあります。

バリデータ | Django ドキュメント | Django

Field.localize

revenue = forms.DecimalField(max_digits=4, decimal_places=2, localize=True)
そのフィールドがローカライズを有効にするかどうかを指定する。
USE_L10N, USE_I18N などを有効にする、
テンプレートで{% localize %} ブロックを使用してコントロールする、
翻訳ファイルをformats/en/formats.pyに置いて使用するなど。

表示形式のローカル化 | Django ドキュメント | Django

Field.disabled

Trueにセットするとユーザーが編集できないように表示するとともに、仮にサーバに送られても無視します。

Form.has_changed()

フィールドが変更されたかどうか。

まとめ

モデルクラスとフォームクラスの分担をどうするか。モデルの方はDBの都合を意識して、フォームの方は画面の都合を意識するか。
重要なのはウイジェットの選択とバリデーションでしょうか。

【Django】パッケージ化

パッケージ化して他でインストールできるようにする

高度なチュートリアル: 再利用可能アプリの書き方 | Django ドキュメント | Django

setuptoolsをインストールし、これを使ってパッケージ化する

pip/conda install setuptools

目的のアプリフォルダのみを別フォルダにまるごと移動する。
下記のファイルを作ってpython setup.py sdistを実行すると、distに圧縮ファイルが作成される。

README.rst
LICENSE
setup.cfg
setup.py
MANIFEST.in
docs/


パッケージを使うには、python pip install --user xxxxx.tar.gzのようにする

【Django】チュートリアル7 管理サイトの扱い

その7 管理サイトの扱い

管理サイトのカスタムができる

項目の並び替え

カスタマイズクラス作って、引数の二番目に渡す。

class QuestionAdmin(admin.ModelAdmin):
    fields = ['pub_date', 'question_text']

admin.site.register(Question, QuestionAdmin)

fieldsetsでグループ化できる。

親モデルの入力画面に子モデルの入力欄を表示する

一対多のモデルの場合、デフォルトでは子モデルの入力で親を選択するようになっているが、それは非効率なので。
modelを指定し、extraは入力欄の数。

# テーブル形式か、スタック形式か
class ChoiceInline(admin.TabularInline):
# class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 3

一覧画面の表示カラム設定

list_display = ('question_text', 'pub_date')

カスタムプロパティもソートできるようにする(モデル側に設定)
xxx.admin_order_field = 'pub_date'
マルバツ表示にする
xxx.boolean = True
カラム表示名を変える
xxx.short_description = 'Published recently?'

その他

指定カラムのフィルターを表示する
list_filter = ['pub_date']
検索欄を表示(like検索)
search_fields = ['question_text']

デザインのカスタマイズ

ベースディレクトリにtemplatesを作る
setting.pyを編集
'DIRS': [ BASE_DIR / 'templates'],

django/contrib/admin/templatesからadmin/base_site.htmlやindex.htmlを上記にコピーする。
templates/admin/xxx.html
それを編集する。

djangoのソースを探す
python -c "import django; print(django.__path__)"

まとめ

ちゃんとカスタマイズすればプロダクションでも使えるかも。

【Django】チュートリアル5〜6 テスト、静的ファイルの扱い

その5 テスト

Django におけるテスト
Django におけるテスト | Django ドキュメント | Django

各アプリのtests.pyに書く

python manage.py test xxxx で実行
モデルやビューごとに TestClass を分割する

class QuestionModelTests(TestCase):
  self.assertIs(q.something(), False)

アプリケーション内のtest.pyを実行する
TestCaseのサブクラスが対象
メソッドはtestで始める

self.assertXXXでチェックする
テスト用のデータベースが作成され、終了後クリーンアップされる

シェルでブラウザシミュレート

python manage.py shell

from django.test.utils import setup_test_environment
setup_test_environment()

from django.test import Client
client = Client()
response = client.get(reverse('polls:index'))
response.content
response.context['latest_question_list']

ビューのテスト

クライアントでアクセスして、レスポンスの中身を調べる。
Javascriptの振る舞いは確認できない。

response = self.client.get(reverse('polls:index'))
# ステータスコードのチェック
self.assertEqual(response.status_code, 200)
# 表示内容のチェック
self.assertContains(response, "No polls are available.")
# 返されるcontextのチェック
self.assertQuerysetEqual(response.context['latest_question_list'], [])
self.assertQuerysetEqual(response.context['latest_question_list'],['<Question: Past question.>'])

インタラクションのテスト

LiveServerTestCase



その6 静的ファイルの扱い

静的ファイル (画像、JavaScript、CSS など) を管理する | Django ドキュメント | Django

css, javascriptなど

静的ファイルの参照先はSTATICFILES_FINDERSで定義される。
デフォルトで各アプリのアブフォルダを参照してくれる。
設定 | Django ドキュメント | Django

アプリフォルダの中にstaticを作成し、さらにアプリ名のサブフォルダを作る。
polls/static/polls/style.css

テンプレート内でstaticで絶対パスを生成。

{% load static %}

{% static 'polls/style.css' %} 

画像

staticの中にimagesフォルダを作って置く。
polls/static/polls/images/image.gif

STATIC_ROOTを変更できるので相対パスを使うべき。

まとめ

ファイルの取扱は詳細を要確認。
ユーザーによるファイルアップロードなどは記載がないので、MEDIA_ROOTなどの設定を調べる。

【Django】チュートリアル3〜4 ビューの作成、フォーム

その3ビューの作成

urls.pyにルートを書いていく

urlpatternsのパラメータはviews.pyの引数の名前と一致させること。

path('<int:question_id>', views.detail, name='detail'),

def detail(request, question_id):
  return render(request, 'polls/detail.html', context)

テンプレートを用意する

アプリディレクトリの中にtemplatesディレクトリを作成、さらにその中にアプリ名のディレクトリを作成
polls/templates/polls/xxxxx.html

settings.pyのAPP_DIRSがTrueの場合、INSTALLED_APPS のそれぞれの "templates" を参照しに行く。

エラーの場合404を表示してくれる便利関数

get_object_or_404()
get_list_or_404()

URL名前空間

urls.pyのルート名に名前空間をつけられる。
nameに名前空間をあわせて指定できる。

app_name = 'polls' をurls.pyに追加するだけ。
テンプレートのurlタグやredirectで 'polls:xxxxx' でアクセス。

ループのカウンター

{{% for item in items %}}
{{ forloop.counter }}
{{% endfor %}}

その4 フォーム

POSTパラメータの受け取り

request.POST['choice']
例外KeyErrorはディクショナリのしていキーがなかった際に発生する。(getで取れば例外を回避可能)

リダイレクト

POSTを行った後にHttpResponseRedirectやredirectを返すことで、戻るボタンを押されたときの二重登録しないようにできる。

クラス型ビュー

典型的なビューはジェネリックビューを使うと簡単につくれる。
from django.views import generic

まとめ

templatesフォルダが最初から作られていないのが不親切。
templatesは各アプリのフォルダの中に入れるのが良さそう。サブフォルダを作らないといけないのが冗長に思えるが、後々パッケージ化する際に有利なのかもしれない。
クラス型ビューはちょっとブラックボックスすぎるかな。