投稿日:2019年11月9日
Djangoは簡単にWebアプリケーションを作成できるフレームワークです。この記事は初心者の方向けのDjangoチュートリアルです。
前回はDjangoの管理サイトでテーブルのレコードを操作しました。
今回の記事はそのQuerySetの解説とその使い方の練習で実際にコードは書きません。
実際にブログを運営するときには、ユーザーがサイトのURLにアクセスした時に、自動で適したテーブルのレコードを触る必要があります。
QuerySetはプログラムからテーブルのレコードを操作するためのDjangoのORM(Object-Relational Mapper)のAPIです。
ORMのAPIを使うことでそれぞれのデータベース(MySQL、PostgresSQL、SQLite、...)ごとのコードに違いを埋めることができます。どのDBを使用するかはsettings.pyのDATABASESの変数で設定されます。覚えておきましょう!
ここではQuerySetを試しに対話的にさわって行くのですが、pythonのshellではなくDjangoのshellを使います。
Djangoのプロジェクトで作成したコードはそのままpythonのshellではちゃんと動きません。
$ python
Python 3.6.8 (default, Oct 7 2019, 12:59:55)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from blog.models import Article
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/ogihara/django_tutorial/sample_blog/blog/models.py", line 3, in <module>
from django.contrib.auth.models import User
File "/home/ogihara/django_tutorial/myvenv/lib/python3.6/site-packages/django/contrib/auth/models.py", line 2, in <module>
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
File "/home/ogihara/django_tutorial/myvenv/lib/python3.6/site-packages/django/contrib/auth/base_user.py", line 47, in <module>
class AbstractBaseUser(models.Model):
File "/home/ogihara/django_tutorial/myvenv/lib/python3.6/site-packages/django/db/models/base.py", line 103, in __new__
app_config = apps.get_containing_app_config(module)
File "/home/ogihara/django_tutorial/myvenv/lib/python3.6/site-packages/django/apps/registry.py", line 252, in get_containing_app_config
self.check_apps_ready()
File "/home/ogihara/django_tutorial/myvenv/lib/python3.6/site-packages/django/apps/registry.py", line 134, in check_apps_ready
settings.INSTALLED_APPS
File "/home/ogihara/django_tutorial/myvenv/lib/python3.6/site-packages/django/conf/__init__.py", line 79, in __getattr__
self._setup(name)
File "/home/ogihara/django_tutorial/myvenv/lib/python3.6/site-packages/django/conf/__init__.py", line 64, in _setup
% (desc, ENVIRONMENT_VARIABLE))
django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.
作成したDjangoのクラスや関数をコマンドラインなどから使いたい場合は、Djangoのshellを使いましょう。
Djangoのshellは以下のコマンドから起動できます。
$ python manage.py shell # DjangoのShellの起動
manage.pyのshellは開いたまま次にすすんでください
QuerySetでレコードを作成してみましょう。
>>> from django.contrib.auth.models import User
>>> from blog.models import Article
>>> user = User.objects.get(username="marsquai")
>>> article = Article(title = "シェル記事", body="この記事はシェルで書かれました。", author=user)
>>> article.save()
管理サイトで確認するとこの様にちゃんとレコードの追加ができています。
簡単に解説します。
まずは前にcreatesuperuserで作成した管理者のUserレコードを取得するための以下のコードです。
user = User.objects.get(username="marsquai")
この様に、get()メソッドは条件を指定してテーブルからレコードを1つだけ取得することができます。このメソッドは条件に一致するレコードが必ず1つ存在することを前提としています。存在しなかった場合DoesNotExitというexception、複数存在した場合にはMultipleObjectReturnedというexceptionをなげられます。
>>> user = User.objects.get(username="aaaaaaaa")
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/ogihara/django_tutorial/myvenv/lib/python3.6/site-packages/django/db/models/manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/ogihara/django_tutorial/myvenv/lib/python3.6/site-packages/django/db/models/query.py", line 408, in get
self.model._meta.object_name
django.contrib.auth.models.User.DoesNotExist: User matching query does not exist.
なるほど、プログラム中でget()メソッドを単体で使う時にはtry-expectでエラーの管理をしっかりしなくてはいけませんね。
次にArticleクラスのインスタンスを作成している以下の部分です。
article = Article(title = "シェル記事", body="この記事はシェルで書かれました。", author=user)
引数に作成したいレコードのフィールドの値をいれます。
authorのようにmodels.ForeignKeyで定義した値は、get()メソッドで取得したインスタンスを渡しています。
インスタンスの生成の段階ではまだこのデータはメモリ上に乗っているだけでデータベースに変更が適用されていません。
次のsave()メソッドがながれて初めてテーブルに変更が適用されます。
article.save()
これは内部的にSQLのINSERT文が走っています。
この方法は一端メモリにオブジェクトを作成してそれをデータベースに永続化させるのですが、create()メソッドを使うとそれを一度に行うことができます。
Article.objects.create(title = "シェル記事", body="この記事はシェルで書かれました。", author=user)
レコードを作成するだけならこっちのほうが簡単ですね。
インスタンスの変数の値を変えてsave()メソッドを呼ぶことでレコードの変更をすることができます。
...
>>> article.title="Shellから変更された記事"
>>> article.save()
変数を変えたときにもsave()が呼ばれるまでデータベースに適用されません。変更の際にはsave()メソッドの時内部で実行されるSQLのUPDATE文が流れます。
全てのレコードのオブジェクトはall()メソッドで取得できます。
...
>>> article_all = Article.objects.all()
>>> print(article_all)
<QuerySet [<Article: エンジニアの健康維持の秘訣とは?>, <Article: 君にもできるスクレイピング!>, <Article: Shellから変更された記事>, <Article: シェル記事>, <Article: 今夜の料理は?>, <Article: 雑談日記>]>
filter()メソッドを使うことで条件を指定してその条件を満たすレコードのオブジェクトを取得できます。
例えば公開状態が公開中のもののみ取得する場合:
...
>>> articles = Article.objects.filter(status="published")
>>> articles
<QuerySet [<Article: エンジニアの健康維持の秘訣とは?>, <Article: 今夜の料理は?>]>
レコードの取得は__(ダブルハイフン)を使うことで細かい指定ができます。
例えば、以下の様にすると公開日(DateTime)の年だけからフィルタリングできます。
...
>>> articles = Article.objects.filter(publish__year=2019)
>>> articles
<QuerySet [<Article: エンジニアの健康維持の秘訣とは?>, <Article: 君にもできるスクレイピング!>, <Article: Shellから変更された記事>, <Article: シェル記事>, <Article: 今夜の料理は?>, <Article: 雑談日記>]>
外部キーの場合そのテーブルのフィールドも__(ダブルハイフン)で指定できます。
...
>>> articles = Article.objects.filter(author__username="marsquai")
>>> articles
<QuerySet [<Article: エンジニアの健康維持の秘訣とは?>, <Article: 君にもできるスクレイピング!>, <Article: Shellから変更された記事>, <Article: シェル記事>, <Article: 今夜の料理は?>, <Article: 雑談日記>]>
この__(ダブルハイフン)を使った条件指定は他にも指定した値以下のレコードを取得する__lteや、指定した値以上のレコードを取得する__gte、その他にもいろんな便利な指定方法があるので興味があれば調べてみましょう。
レコードのフィルタリングは複数の条件を指定することができます。複数の条件を指定する場合はfilter()メソッドをつなげるか、filter()メソッドの引数に指定したい条件をすべて渡してあげればOKです。
>>> articles = Article.objects.filter(author__username="marsquai", publish__year=2019)
>>> articles
<QuerySet [<Article: エンジニアの健康維持の秘訣とは?>, <Article: 君にもできるスクレイピング!>, <Article: Shellから変更された記事>, <Article: シェル記事>, <Article: 今夜の料理は?>, <Article: 雑談日記>]>
exclude()メソッドは指定した条件を満たさないレコードのObjectを検索できます。
>>> articles = Article.objects.exclude(status="draft")
>>> articles
<QuerySet [<Article: エンジニアの健康維持の秘訣とは?>, <Article: 今夜の料理は?>]>
order_by()メソッドを使うとレコードのオブジェクトを特定のフィールドごとに並び替えて取得できます。
例えば全てのレコードをタイトル順に並び替える場合:
>>> articles = Article.objects.order_by("title")
>>> articles
<QuerySet [<Article: Shellから変更された記事>, <Article: エンジニアの健康維持の秘訣とは?>, <Article: シェル記事>, <Article: 今夜の料理は?>, <Article: 君にもできるスクレイピング!>, <Article: 雑談日記>]>
渡す引数のフィールド名に - をつければ逆順に並べることができます。
>>> articles = Article.objects.order_by("-title")
>>> articles
<QuerySet [<Article: 雑談日記>, <Article: 君にもできるスクレイピング!>, <Article: 今夜の料理は?>, <Article: シェル記事>, <Article: エンジニアの健康維持の秘訣とは?>, <Article: Shellから変更された記事>]>
オブジェクトの並び順の優先順位は以下の順になっています。
レコードの削除にはオブジェクトのインスタンスのdelete()メソッドを呼び出します。
>>> article = Article.objects.get(id=1)
>>> article.delete()
(1, {'blog.Article': 1})
大体のQuerySetの使い方はこの記事で紹介できました。
詳しい使い方は公式ドキュメントを参照しましょう。
公式ドキュメント QuerySet:https://docs.djangoproject.com/ja/2.2/ref/models/querysets/
公式ドキュメント Queryhttps://docs.djangoproject.com/ja/2.2/topics/db/queries/