Djangoプロジェクトを構築する際、ウェブアプリケーションの基盤となる要素には、環境設定からURLの処理、データの永続化まで多岐にわたります。本稿では、Django開発の主要な構成要素であるルーティング、ビュー、テンプレート、ORM(Object-Relational Mapping)、そして基本的なセッション管理について解説します。
開発環境のセットアップと初期設定
Djangoプロジェクトを開始するにあたり、まず仮想環境を設定し、必要なパッケージをインストールします。
# 仮想環境の作成
python -m venv myproject_env
# 仮想環境のアクティベート (Windowsの場合)
myproject_env\Scripts\activate
# Djangoのインストール (特定のバージョンを指定)
pip install django==4.2.0
プロジェクトの構成要素として、URLの定義、ビュー関数の作成、そしてルーティング設定が挙げられます。これらは、ユーザーからのリクエストを適切に処理し、レスポンスを返すための基本的な枠組みとなります。
URLルーティングの基礎
Djangoでは、urls.pyファイルを使用してURLパターンとそれに対応するビュー関数をマッピングします。主な関数としてpath()とre_path()があります。
path()によるURLマッピング
path()関数は、シンプルなURLパターンを定義するのに使われます。URLの一部を動的に取り込むには「パスコンバータ」を利用します。
# project_name/urls.py
from django.contrib import admin
from django.urls import path
from myapp import views # myappというアプリケーションのviews.pyをインポート
urlpatterns = [
path('admin/', admin.site.urls),
# 静的なURLパターン
path('greeting/hello/', views.hello_view),
# パスコンバータを使用した動的なURLパターン
# 例: /article/100 にアクセスすると、ビューに100が渡される
path('article//', views.article_detail_view),
]
myapp/views.pyでのビュー関数の定義例です。
# myapp/views.py
from django.http import HttpResponse
def hello_view(request):
return HttpResponse("こんにちは!これは静的なページです。")
def article_detail_view(request, article_id):
# パスコンバータで抽出された値(article_id)を利用
html_content = f"<h3>記事番号: {article_id} の詳細ページ</h3>"
return HttpResponse(html_content)
path()のマッチングは上から順に行われ、最初に一致したパターンが採用されます。
re_path()による正規表現ルーティング
より複雑なURLパターンを扱う場合、正規表現を使用するre_path()関数が有効です。
# project_name/urls.py
from django.urls import path, re_path
from myapp import views
urlpatterns = [
# ... 他のパス ...
# 正規表現を使用して、2桁までの整数と特定の演算子を組み合わせたURLを定義
# 例: /calculate/12/plus/34 にアクセス
re_path(r'^calculate/(?P<num1>\d{1,2})/(?P<operation>\w+)/(?P<num2>\d{1,2})/$', views.calculator_view),
# 誕生日フォーマットのURL(YYYY/MM/DDまたはMM/DD/YYYY)
re_path(r'^event/date/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/$', views.event_date_view),
]
myapp/views.pyでのビュー関数の定義例です。
# myapp/views.py
from django.http import HttpResponse
def calculator_view(request, num1, operation, num2):
n1 = int(num1)
n2 = int(num2)
result = 0
message = ""
if operation == 'plus':
result = n1 + n2
elif operation == 'minus':
result = n1 - n2
elif operation == 'multiply':
result = n1 * n2
elif operation == 'divide':
if n2 != 0:
result = n1 / n2
else:
message = "0での除算はできません。"
else:
message = "サポートされていない演算子です。"
return HttpResponse(f"エラー: {message}")
if message:
return HttpResponse(f"エラー: {message}")
else:
return HttpResponse(f"計算結果: {n1} {operation} {n2} = {result}")
def event_date_view(request, year, month, day):
return HttpResponse(f"指定されたイベント日付: {year}年{month}月{day}日")
HTTPリクエストとレスポンス
ウェブアプリケーションは、クライアントからのHTTPリクエストを受け取り、処理後にHTTPレスポンスを返します。
リクエストオブジェクト (HttpRequest)
ビュー関数にはHttpRequestオブジェクトが渡され、リクエストに関する様々な情報にアクセスできます。
request.method: リクエストメソッド (例: 'GET', 'POST')request.path_info: リクエストされたパスの情報request.GET: GETパラメータを辞書のように扱えるオブジェクト (QueryDict)request.POST: POSTパラメータを辞書のように扱えるオブジェクト (QueryDict)request.META: リクエストのメタデータ (HTTPヘッダーなど)request.COOKIES: クライアントから送信されたCookieを辞書のように扱えるオブジェクト
# myapp/views.py
from django.http import HttpResponse
def handle_request(request):
print(f"パス情報: {request.path_info}")
print(f"メソッド: {request.method}")
print(f"GETパラメータ: {request.GET}")
print(f"フルパス: {request.get_full_path()}")
# 例: クライアントIPアドレス
print(f"リモートアドレス: {request.META.get('REMOTE_ADDR')}")
if request.method == 'GET':
param_a = request.GET.get('param_a', '未指定')
list_items = request.GET.getlist('items') # 同じキーで複数値を取得
return HttpResponse(f"GETリクエストを受信しました。param_a: {param_a}, items: {list_items}")
elif request.method == 'POST':
user_name = request.POST.get('username', '匿名')
return HttpResponse(f"POSTリクエストを受信しました。ユーザー名: {user_name}")
else:
return HttpResponse(f"サポートされていないメソッド: {request.method}")
# project_name/urls.py
from django.urls import path
from myapp import views
urlpatterns = [
# ...
path('process_data/', views.handle_request),
]
HTMLフォームからPOSTリクエストを送信する例です。
<!-- templates/myapp/data_form.html -->
<form action="/process_data/" method="post">
{% csrf_token %} <!-- DjangoのCSRF保護トークン -->
<label for="username">ユーザー名:</label>
<input type="text" id="username" name="username">
<button type="submit">送信</button>
</form>
注意: DjangoはデフォルトでCSRF (Cross-Site Request Forgery) 保護を有効にしています。POSTフォームには{% csrf_token %}を記述する必要があります。開発段階で一時的に無効にする場合は、settings.pyのMIDDLEWAREから'django.middleware.csrf.CsrfViewMiddleware'をコメントアウトできますが、本番環境では決して推奨されません。
レスポンスオブジェクト (HttpResponse)
サーバーからクライアントへ返されるHTTPレスポンスは、HttpResponseオブジェクトで構築されます。
# myapp/views.py
from django.http import HttpResponse
def simple_response(request):
# レスポンスボディとステータスコードを指定
return HttpResponse("成功しました。", status=200, content_type="text/plain")
def redirect_example(request):
from django.http import HttpResponseRedirect
# 他のURLへリダイレクト
return HttpResponseRedirect("/greeting/hello/")
一般的なHTTPステータスコード:
- 200 OK: リクエスト成功
- 301 Moved Permanently: 恒久的なリダイレクト
- 302 Found (または Moved Temporarily): 一時的なリダイレクト
- 404 Not Found: リソースが見つからない
- 500 Internal Server Error: サーバー内部エラー
Djangoテンプレートシステム
Djangoは、動的なHTMLページを生成するための強力なテンプレートシステムを提供します。MVC (Model-View-Controller) パターンのバリエーションであるMTV (Model-Template-View) パターンに従います。
- Model: データ構造とデータベースとの相互作用を定義します。
- Template: ユーザーに表示されるUIロジックとコンテンツを定義します。
- View: リクエストを処理し、モデルとテンプレートを連携させてレスポンスを生成します。
テンプレートの作成と読み込み
テンプレートは通常、プロジェクトルートまたは各アプリケーション内のtemplatesディレクトリに配置します。
- プロジェクトルートに
templatesフォルダを作成。 settings.pyのTEMPLATES設定でDIRSにこのフォルダのパスを追加。# settings.py TEMPLATES = [ { # ... 'DIRS': [os.path.join(BASE_DIR, 'templates')], # ここに追加 # ... }, ]- ビュー関数からテンプレートをレンダリング。
テンプレートをレンダリングする方法には、loaderモジュールを使う方法と、render()ショートカット関数を使う方法があります。
loaderモジュールを使用する例
# myapp/views.py
from django.template import loader
from django.http import HttpResponse
def show_product_list_loader(request):
template = loader.get_template('myapp/product_list.html')
context = {
'products': [
{'name': 'Laptop', 'price': 1200},
{'name': 'Mouse', 'price': 25},
]
}
html = template.render(context, request) # contextとrequestを渡す
return HttpResponse(html)
render()ショートカット関数を使用する例 (推奨)
# myapp/views.py
from django.shortcuts import render
def show_product_list_render(request):
context = {
'products': [
{'name': 'Keyboard', 'price': 75},
{'name': 'Monitor', 'price': 300},
],
'app_name': 'MyAwesomeShop'
}
return render(request, 'myapp/product_list.html', context) # request, テンプレート名, context
<!-- templates/myapp/product_list.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{{ app_name }} - 商品リスト</title>
</head>
<body>
<h1>商品一覧</h1>
<ul>
{% for product in products %}
<li>{{ product.name }}: ¥{{ product.price }}</li>
{% empty %}
<li>商品はありません。</li>
{% endfor %}
</ul>
</body>
</html>
テンプレート変数とタグ
ビュー関数からテンプレートに渡されたデータは「コンテキスト」と呼ばれ、テンプレート内で{{ variable_name }}構文を使ってアクセスできます。
テンプレートで扱えるデータ型は、文字列、整数、リスト、タプル、辞書、関数、クラスのインスタンスなど多岐にわたります。
# myapp/views.py
from django.shortcuts import render
def demonstration_view(request):
class Pet:
def __init__(self, name):
self.name = name
def bark(self):
return f"{self.name}はワンワン吠える!"
def get_current_year():
from datetime import datetime
return datetime.now().year
context = {
'simple_string': 'Hello Django!',
'number_value': 123,
'item_list': ['Apple', 'Banana', 'Cherry'],
'user_info': {'name': 'Taro', 'age': 30},
'callable_func': get_current_year,
'pet_object': Pet('Pochi'),
}
return render(request, 'myapp/demo_template.html', context)
<!-- templates/myapp/demo_template.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テンプレートデモ</title>
</head>
<body>
<h3>文字列: {{ simple_string }}</h3>
<h3>数値: {{ number_value }}</h3>
<h3>リスト全体: {{ item_list }}</h3>
<h3>リストの最初の要素: {{ item_list.0 }}</h3>
<h3>辞書全体: {{ user_info }}</h3>
<h3>辞書の'name'キー: {{ user_info.name }}</h3> <!-- ドット記法でキーにアクセス -->
<h3>関数呼び出し: {{ callable_func }}</h3>
<h3>オブジェクトのメソッド呼び出し: {{ pet_object.bark }}</h3>
</body>
</html>
テンプレートタグ: {% tag %}構文は、条件分岐やループなどの制御構造をテンプレートに埋め込むために使われます。
<!-- templates/myapp/conditional_loop.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>条件とループ</title>
</head>
<body>
{% if score >= 80 %}
<p>素晴らしい!合格です。</p>
{% elif score >= 60 %}
<p>もう少し頑張りましょう。</p>
{% else %}
<p>残念ながら不合格です。</p>
{% endif %}
<h3>参加者リスト</h3>
<ul>
{% for participant in participant_list %}
{% if forloop.first %}<li>--- リスト開始 ---</li>{% endif %}
<li>{{ forloop.counter }}: {{ participant.name }} (年齢: {{ participant.age }})</li>
{% if forloop.last %}<li>--- リスト終了 ---</li>{% endif %}
{% empty %}
<li>参加者はいません。</li>
{% endfor %}
</ul>
</body>
</html>
forloopオブジェクトは、ループの現在のイテレーションに関する情報を提供します (例: forloop.first, forloop.last, forloop.counter)。
テンプレートフィルター
フィルターは、変数の値を表示時に加工するために使用されます。{{ variable|filter_name:argument }}という構文で適用します。
{{ my_string|lower }}: 文字列をすべて小文字に変換{{ my_string|upper }}: 文字列をすべて大文字に変換{{ html_content|safe }}: HTMLエスケープを行わない (危険なHTMLを表示する可能性があるため注意){{ number|add:"5" }}: 数値に指定した値を加算{{ long_text|truncatechars:"20" }}: 指定された文字数で文字列を切り詰め、末尾に「...」を追加
<!-- templates/myapp/filters_example.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>フィルターデモ</title>
</head>
<body>
<p>元の文字列: {{ my_text }}</p>
<p>小文字変換: {{ my_text|lower }}</p>
<p>大文字変換: {{ my_text|upper }}</p>
<p>数値加算 (10 + 5): {{ 10|add:"5" }}</p>
<p>切り詰め: {{ long_description|truncatechars:"30" }}</p>
<p>HTMLエスケープなし (安全でない場合がある): {{ raw_html|safe }}</p>
</body>
</html>
テンプレートの継承
テンプレート継承は、共通のレイアウトを持つ複数のページを作成する際に、コードの重複を避けるための強力な機能です。親テンプレートで{% block block_name %}を定義し、子テンプレートでそれを{% extends 'parent.html' %}と{% block block_name %} ... {% endblock %}でオーバーライドします。
<!-- templates/base.html (親テンプレート) -->
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{% block title %}デフォルトタイトル{% endblock %}</title>
<style>
body { font-family: sans-serif; margin: 20px; }
.header { background-color: #f0f0f0; padding: 10px; }
.content { margin-top: 20px; border: 1px solid #ccc; padding: 15px; }
.footer { margin-top: 30px; border-top: 1px solid #eee; padding-top: 10px; font-size: 0.8em; text-align: center; }
</style>
</head>
<body>
<div class="header">
<h1>私のウェブサイト</h1>
<nav>
<a href="/">ホーム</a> |
<a href="/news/">ニュース</a> |
<a href="/about/">会社概要</a>
</nav>
</div>
<div class="content">
{% block main_content %}
<p>ここにメインコンテンツが表示されます。</p>
{% endblock %}
</div>
<div class="footer">
© 2023 My Website. All rights reserved.
</div>
</body>
</html>
<!-- templates/news/index.html (子テンプレート) -->
{% extends 'base.html' %}
{% block title %}最新ニュース{% endblock %}
{% block main_content %}
<h2>本日のトップニュース</h2>
<p>詳細はこちらをご覧ください。</p>
<ul>
<li>記事1</li>
<li>記事2</li>
</ul>
{% endblock %}
URLの逆引き (URL Reversing)
URLの逆引きとは、ビュー関数やテンプレート内でURLパターンに割り当てられた「名前(name)」を使って、実際のURLパスを動的に生成する機能です。これにより、URLパターンを変更しても、コード内のリンクを修正する必要がなくなります。
# project_name/urls.py
from django.urls import path
from myapp import views
urlpatterns = [
path('homepage/', views.home_page, name='home'), # name='home'で逆引き可能
path('products//', views.product_detail, name='product_detail'),
]
テンプレートでの逆引き
<!-- templates/myapp/some_page.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>URL逆引きのデモ</title>
</head>
<body>
<p>ホームページへのリンク: <a href="{% url 'home' %}">こちら</a></p>
<p>商品ID 105 の詳細ページ: <a href="{% url 'product_detail' product_id=105 %}">商品105</a></p>
</body>
</html>
ビュー関数内での逆引き
# myapp/views.py
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.shortcuts import render
def home_page(request):
return HttpResponse("ようこそホームページへ!")
def product_detail(request, product_id):
return HttpResponse(f"商品ID {product_id} の詳細です。")
def redirect_to_home(request):
# 'home'という名前のURLパターンに対応するパスを逆引きしてリダイレクト
url = reverse('home')
return HttpResponseRedirect(url)
def show_some_page(request):
return render(request, 'myapp/some_page.html')
静的ファイルの配信
CSS、JavaScript、画像などの静的ファイルは、ウェブアプリケーションの重要な要素です。Djangoでは、これらのファイルを効率的に管理し、配信する仕組みが提供されています。
- プロジェクトルートに
staticフォルダを作成。 settings.pyでSTATICFILES_DIRSにこのフォルダのパスを追加。# settings.py STATIC_URL = '/static/' STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'),)- テンプレートで静的ファイルを使用。
<!-- templates/myapp/static_demo.html -->
{% load static %} <!-- staticタグを使用するためにロード -->
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>静的ファイルのデモ</title>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<h1>静的ファイルのテスト</h1>
<img src="{% static 'images/sample_image.jpg' %}" alt="サンプル画像" width="200">
<script src="{% static 'js/script.js' %}"></script>
</body>
<html>
staticディレクトリの内部構造は自由に決められます (例: static/css/, static/images/, static/js/)。
Djangoアプリケーションと分散ルーティング
Djangoプロジェクトは、複数の「アプリケーション」で構成することができます。アプリケーションは独立した機能モジュールであり、独自のモデル、ビュー、テンプレート、URL設定を持つことができます。
アプリケーションの作成
manage.pyスクリプトを使ってアプリケーションを作成します。
python manage.py startapp blog
作成後、settings.pyのINSTALLED_APPSリストに新しいアプリケーションを追加します。
# settings.py
INSTALLED_APPS = [
# ...
'blog', # 作成したアプリケーション
]
分散ルーティング
プロジェクトのメインのurls.pyでは、すべてのURLを直接処理するのではなく、各アプリケーションのurls.pyにルーティングを委譲することができます。
# project_name/urls.py (プロジェクトのメインURL設定)
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')), # 'blog/'以下のURLをblogアプリのurls.pyに委譲
]
# blog/urls.py (blogアプリケーションのURL設定)
from django.urls import path
from . import views # blogアプリケーションのviews.pyをインポート
urlpatterns = [
# 例: /blog/posts/ にアクセス
path('posts/', views.post_list_view, name='post_list'),
# 例: /blog/posts/5/ にアクセス
path('posts//', views.post_detail_view, name='post_detail'),
]
# blog/views.py
from django.http import HttpResponse
from django.shortcuts import render
def post_list_view(request):
return HttpResponse("ブログ記事の一覧です。")
def post_detail_view(request, post_id):
return HttpResponse(f"ブログ記事ID {post_id} の詳細ページです。")
アプリケーションごとのテンプレート
各アプリケーションは独自のtemplatesディレクトリを持つことができます。settings.pyのTEMPLATES設定で'APP_DIRS': Trueを設定していれば、Djangoは自動的に各アプリケーション内のtemplatesフォルダを検索します。
テンプレートの解決順序:
settings.TEMPLATES[0]['DIRS']に指定されたディレクトリ (プロジェクトルートのtemplates)INSTALLED_APPSにリストされた順序で、各アプリケーション内のtemplatesディレクトリ
名前の衝突を避けるため、通常はアプリケーション内のテンプレートをtemplates/app_name/template_file.htmlのようにサブディレクトリに配置することが推奨されます。
Django ORM (Object-Relational Mapping)
ORMは、Pythonオブジェクトを使ってデータベースと対話するための技術です。SQLクエリを直接書く代わりに、Pythonのクラスとオブジェクトを操作することで、データベースのレコードを作成、読み取り、更新、削除 (CRUD) できます。
データベースのセットアップ
DjangoはSQLiteをデフォルトで使用しますが、MySQLなどの他のデータベースもサポートしています。MySQLを使用する場合、settings.pyでデータベース接続情報を設定します。
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # MySQLを使用
'NAME': 'mydjangodb', # データベース名
'USER': 'root', # データベースユーザー名
'PASSWORD': 'your_password', # データベースパスワード
'HOST': '127.0.0.1', # データベースホスト
'PORT': '3306', # データベースポート
}
}
MySQLを使用するには、mysqlclientなどのPythonパッケージをインストールする必要があります: pip install mysqlclient。
モデルの定義
モデルはdjango.db.models.Modelを継承するPythonクラスであり、データベースのテーブルとそのフィールドを表現します。
# myapp/models.py
from django.db import models
class Product(models.Model):
name = models.CharField('商品名', max_length=100, default='')
description = models.TextField('説明', blank=True, null=True)
price = models.DecimalField('価格', max_digits=10, decimal_places=2)
stock = models.IntegerField('在庫数', default=0)
created_at = models.DateTimeField('作成日', auto_now_add=True)
updated_at = models.DateTimeField('更新日', auto_now=True)
is_available = models.BooleanField('販売中', default=True)
class Meta:
db_table = 'products' # データベースのテーブル名を指定
verbose_name = '商品'
verbose_name_plural = '商品'
def __str__(self):
return self.name
フィールドタイプとオプションの例
models.BooleanField(): 真偽値 (DBではTINYINT(1))models.CharField(max_length=...): 短い文字列 (VARCHAR)。max_lengthは必須。models.TextField(): 長い文字列 (LONGTEXT)。models.IntegerField(): 整数 (INT)。models.DecimalField(max_digits=..., decimal_places=...): 固定小数点数。max_digitsは数値の総桁数、decimal_placesは小数点以下の桁数。models.DateField(auto_now_add=True/auto_now=True): 日付。auto_now_addはオブジェクト初回作成時、auto_nowは保存時に自動更新。models.DateTimeField(): 日付と時刻。models.EmailField(): メールアドレス (CharFieldのラッパー)。models.ImageField(upload_to=...): 画像ファイル。
マイグレーション
モデル定義を変更したら、以下のコマンドでデータベーススキーマを同期します。
# マイグレーションファイルの作成 (モデル定義から変更を検出)
python manage.py makemigrations myapp
# データベースへの変更適用 (実際にテーブルを作成・変更)
python manage.py migrate
CRUD操作 (Create, Read, Update, Delete)
Django ORMは、MyModel.objectsマネージャを通じてデータベース操作を行います。これはDjango Shell (python manage.py shell) で試すのが便利です。
# python manage.py shell を実行
from myapp.models import Product
# --- データ作成 (Create) ---
# 方法1: オブジェクトを作成して保存
p1 = Product(name='デスクチェア', price=150.00, stock=50)
p1.save()
# 方法2: create() メソッドで一度に作成
p2 = Product.objects.create(name='PCモニター', price=300.00, stock=30, description='27インチ高解像度モニター')
print(f"作成された商品1: {p1.name}")
print(f"作成された商品2: {p2.name}")
# --- データ読み取り (Read) ---
# 全てのレコードを取得
all_products = Product.objects.all()
for p in all_products:
print(f"商品: {p.name}, 価格: {p.price}")
# 特定のIDのレコードを取得 (存在しない場合や複数ヒットでエラー)
try:
chair = Product.objects.get(id=1)
print(f"ID 1 の商品: {chair.name}")
except Product.DoesNotExist:
print("指定されたIDの商品は存在しません。")
# 条件に一致するレコードをフィルタリング
expensive_products = Product.objects.filter(price__gte=200.00) # price >= 200
for p in expensive_products:
print(f"高額商品: {p.name}")
# 条件に一致しないレコードを除外
not_available_products = Product.objects.exclude(is_available=True)
print(f"販売停止中の商品数: {not_available_products.count()}")
# 特定のフィールドのみを選択 (辞書のリストを返す)
product_names_prices = Product.objects.values('name', 'price')
print(f"商品名と価格 (辞書): {list(product_names_prices)}")
# 特定のフィールドのみを選択 (タプルのリストを返す)
product_names_only = Product.objects.values_list('name', flat=True) # flat=Trueで単一フィールドのリスト
print(f"商品名のみ (タプル/リスト): {list(product_names_only)}")
# ソート
sorted_products = Product.objects.order_by('price') # 価格の昇順
reverse_sorted_products = Product.objects.order_by('-created_at') # 作成日の降順
# --- 非等値クエリ (Query Lookups) ---
# __exact: 完全一致 (デフォルトなので省略可能)
p = Product.objects.get(name__exact='デスクチェア')
# __contains: 部分文字列を含む (大文字小文字を区別)
monitors = Product.objects.filter(name__contains='モニター')
# __icontains: 部分文字列を含む (大文字小文字を区別しない)
monitors_case_insensitive = Product.objects.filter(name__icontains='モニター')
# __startswith, __endswith: 特定の文字列で始まる/終わる
starts_with_pc = Product.objects.filter(name__startswith='PC')
# __gt, __gte, __lt, __lte: より大きい、以上、より小さい、以下
products_over_100 = Product.objects.filter(price__gt=100.00)
# __in: 指定されたリストの値のいずれかに一致
selected_ids = [1, 3, 5]
specific_products = Product.objects.filter(id__in=selected_ids)
# __range: 指定された範囲内
products_in_range = Product.objects.filter(price__range=(50.00, 200.00))
# --- データ更新 (Update) ---
# 単一レコードの更新: 取得 -> 変更 -> 保存
chair_to_update = Product.objects.get(name='デスクチェア')
chair_to_update.price = 160.00
chair_to_update.save()
print(f"更新後のデスクチェア価格: {Product.objects.get(name='デスクチェア').price}")
# 複数レコードの一括更新
# 在庫が0の商品を全て販売停止にする
Product.objects.filter(stock=0).update(is_available=False)
print(f"販売停止中の商品数 (更新後): {Product.objects.filter(is_available=False).count()}")
# --- データ削除 (Delete) ---
# 単一レコードの削除: 取得 -> 削除
try:
product_to_delete = Product.objects.get(name='古い在庫品')
product_to_delete.delete()
print("商品 '古い在庫品' を削除しました。")
except Product.DoesNotExist:
print("削除対象の商品 '古い在庫品' は存在しません。")
# 複数レコードの一括削除
# 在庫が少ない商品をすべて削除
Product.objects.filter(stock__lt=10).delete()
print("在庫が10未満の商品を削除しました。")
# --- 論理削除 (Soft Delete) ---
# 実際の削除ではなく、フラグで非アクティブ化
# 例えば is_active=True のフィールドを追加し、削除時に False に設定
# 上記のProductモデルでは is_available を利用
p_to_soft_delete = Product.objects.get(name='PCモニター')
p_to_soft_delete.is_available = False
p_to_soft_delete.save()
print(f"PCモニターの販売状況: {Product.objects.get(name='PCモニター').is_available}")
FオブジェクトとQオブジェクト
Fオブジェクト: データベースのフィールド値をPythonで取得せずに直接操作したり、異なるフィールド間で比較したりする際に使用します。
from django.db.models import F
# 全商品の価格を10%値上げする
Product.objects.all().update(price=F('price') * 1.1)
# 在庫数よりも価格が高い商品を検索する
expensive_by_stock = Product.objects.filter(price__gt=F('stock'))
for p in expensive_by_stock:
print(f"在庫より価格が高い商品: {p.name}, 価格: {p.price}, 在庫: {p.stock}")
Qオブジェクト: 複雑な論理条件 (AND, OR, NOT) を組み合わせたクエリを作成する際に使用します。
from django.db.models import Q
# 価格が200より低い、または在庫が50を超える商品を検索
filtered_products = Product.objects.filter(Q(price__lt=200) | Q(stock__gt=50))
for p in filtered_products:
print(f"条件に合う商品: {p.name}, 価格: {p.price}, 在庫: {p.stock}")
# 価格が100より高く、かつ販売中の商品を検索
active_and_priced = Product.objects.filter(Q(price__gt=100) & Q(is_available=True))
# 特定の名前ではない商品を検索 (NOT)
not_desk_chair = Product.objects.exclude(Q(name='デスクチェア'))
集計クエリと生SQL
集計クエリ
aggregate()やannotate()メソッドを使用して、集計関数 (SUM, COUNT, AVG, MAX, MIN) を実行できます。
from django.db.models import Count, Sum, Avg, Max, Min
# 全商品の平均価格を計算
avg_price = Product.objects.aggregate(average_price=Avg('price'))
print(f"全商品の平均価格: {avg_price['average_price']}")
# 商品の総数をカウント
total_products = Product.objects.aggregate(total_count=Count('id'))
print(f"総商品数: {total_products['total_count']}")
生SQLクエリ
ORMで表現できない複雑なクエリが必要な場合、MyModel.objects.raw()を使用して生SQLを実行できます。ただし、SQLインジェクションのリスクがあるため慎重に扱う必要があります。
# from myapp.models import Product
raw_products = Product.objects.raw('SELECT id, name, price FROM products WHERE price > %s ORDER BY name', [100])
for p in raw_products:
print(f"生SQLで取得: {p.name}, 価格: {p.price}")
Django Admin
Djangoには強力な管理サイトが組み込まれており、モデルを登録するだけでデータのCRUD操作を行うためのウェブインターフェースが自動的に生成されます。
管理ユーザーの作成
まず、管理サイトにログインするためのスーパーユーザーを作成します。
python manage.py createsuperuser
モデルの登録
admin.pyファイルでモデルを登録します。
# myapp/admin.py
from django.contrib import admin
from .models import Product
# Productモデルを管理サイトに登録
admin.site.register(Product)
これで、/admin/にアクセスし、作成したスーパーユーザーでログインすると、Productモデルのデータを管理できるようになります。
ModelAdminクラスによるカスタマイズ
管理サイトの表示や動作をより詳細に制御するには、ModelAdminクラスを継承したカスタムクラスを作成し、admin.site.register()に渡します。
# myapp/admin.py
from django.contrib import admin
from .models import Product
class ProductAdmin(admin.ModelAdmin):
# リスト表示するフィールド
list_display = ['id', 'name', 'price', 'stock', 'is_available', 'created_at']
# リンクとして詳細ページに遷移するフィールド
list_display_links = ['name']
# フィルターとして使用するフィールド (サイドバーに表示)
list_filter = ['is_available', 'created_at']
# 検索ボックスで検索対象とするフィールド
search_fields = ['name', 'description']
# リストページで直接編集可能なフィールド
list_editable = ['price', 'stock', 'is_available']
# ページネーションあたりの表示数
list_per_page = 20
admin.site.register(Product, ProductAdmin)
リレーションシップ (関係性)
リレーショナルデータベースでは、異なるテーブル間に関係性を設定できます。Django ORMでは、これらの関係性をモデルのフィールドで表現します。
One-to-Many (一対多)
一つのレコードが複数の他のレコードに関連付けられる関係です。例: 一つのカテゴリに複数の商品が属する。
多側のモデル (Product) にForeignKeyフィールドを設定します。
# myapp/models.py
from django.db import models
class Category(models.Model):
name = models.CharField('カテゴリ名', max_length=50, unique=True)
def __str__(self):
return self.name
class Product(models.Model):
# ... (上記Productモデルのフィールド) ...
category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='カテゴリ')
# on_delete: 関連するCategoryが削除されたときの動作を定義
# - CASCADE: Category削除時にProductも削除
# - PROTECT: CategoryにProductが紐づいている場合、削除を禁止
# - SET_NULL: Category削除時にProductのcategory_idをNULLに設定 (null=Trueが必要)
# - SET_DEFAULT: Category削除時にProductのcategory_idをデフォルト値に設定 (defaultが必要)
# - DO_NOTHING: 何もしない (データベースで参照整合性エラーが発生する可能性あり)
def __str__(self):
return self.name
関連するオブジェクトへのアクセス:
# python manage.py shell
from myapp.models import Category, Product
# カテゴリと商品を作成
electronics = Category.objects.create(name='エレクトロニクス')
books = Category.objects.create(name='書籍')
product_laptop = Product.objects.create(name='高性能ノートPC', price=1200.00, category=electronics)
product_keyboard = Product.objects.create(name='メカニカルキーボード', price=150.00, category=electronics)
product_django_book = Product.objects.create(name='Django入門書', price=40.00, category=books)
# 商品からカテゴリへアクセス
print(f"ノートPCのカテゴリ: {product_laptop.category.name}")
# カテゴリから関連する商品へアクセス (リバースリレーション)
# デフォルトで category_set という名前になる (related_nameで変更可能)
electronics_products = electronics.product_set.all()
print(f"エレクトロニクス関連商品:")
for p in electronics_products:
print(f" - {p.name}")
Many-to-Many (多対多)
両方のモデルが互いに複数のレコードと関連付けられる関係です。例: 一つの記事に複数のタグが付けられ、一つのタグが複数の記事に付けられる。
どちらかのモデルにManyToManyFieldフィールドを設定します (どちらに設定しても同じ結果になりますが、意味的に自然な方を選ぶと良いでしょう)。
# myapp/models.py
from django.db import models
class Tag(models.Model):
name = models.CharField('タグ名', max_length=50, unique=True)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField('タイトル', max_length=200)
content = models.TextField('内容')
tags = models.ManyToManyField(Tag, verbose_name='タグ') # ManyToManyFieldを定義
def __str__(self):
return self.title
関連するオブジェクトへのアクセス:
# python manage.py shell
from myapp.models import Tag, Post
# タグと記事を作成
tag_python = Tag.objects.create(name='Python')
tag_web = Tag.objects.create(name='Web開発')
tag_django = Tag.objects.create(name='Django')
post1 = Post.objects.create(title='Djangoでブログを構築する', content='...')
post2 = Post.objects.create(title='Pythonの基礎', content='...')
# 記事にタグを追加
post1.tags.add(tag_web, tag_django)
post2.tags.add(tag_python)
post2.tags.add(tag_web)
# 記事からタグへアクセス
print(f"'{post1.title}' のタグ:")
for tag in post1.tags.all():
print(f" - {tag.name}")
# タグから関連する記事へアクセス (リバースリレーション)
print(f"'{tag_django.name}' タグが付いた記事:")
for post in tag_django.post_set.all(): # デフォルトで post_set になる
print(f" - {post.title}")
CookiesとSession
HTTPはステートレスなプロトコルであるため、ユーザーの状態を維持するためにCookieとSessionが使われます。
Cookies
Cookieは、クライアント (ブラウザ) 側に保存される小さなデータです。キーと値のペアで構成され、有効期限を持つことができます。
- ドメインごとに隔離され、異なるドメインのCookieにはアクセスできない。
- リクエストごとにサーバーへ送信されるため、サイズが大きすぎるとパフォーマンスに影響を与える可能性がある。
Cookieの設定
# myapp/views.py
from django.http import HttpResponse
def set_user_cookie(request):
response = HttpResponse("Cookieを設定しました。")
# 'username'という名前で'john_doe'という値を設定し、有効期限を600秒 (10分) にする
response.set_cookie('username', 'john_doe', max_age=600)
return response
Cookieの取得
# myapp/views.py
from django.http import HttpResponse
def get_user_cookie(request):
username = request.COOKIES.get('username') # 'username'キーのCookieを取得
if username:
return HttpResponse(f"あなたのユーザー名は: {username}")
else:
return HttpResponse("Cookie 'username' が見つかりません。")
Session
Sessionは、サーバー側に保存されるユーザー固有のデータです。クライアントにはSession IDのみがCookieとして保存され、このIDを使ってサーバーが対応するSessionデータを識別します。
- 各クライアントはサーバー上で独立したSessionを持つ。
- 機密性の高いデータをサーバーに保存できる。
- Djangoはデフォルトでデータベースバックエンドを使ってSessionを管理する。
Sessionの使用
HttpRequestオブジェクトのsession属性を通じて、辞書のようにSessionデータを操作できます。
# myapp/views.py
from django.http import HttpResponse
def set_session_data(request):
# Sessionに値を保存
request.session['user_id'] = 123
request.session['is_logged_in'] = True
request.session['cart_items'] = ['Item A', 'Item B'] # リストも保存可能
return HttpResponse("Sessionデータを設定しました。")
def get_session_data(request):
user_id = request.session.get('user_id', '未設定') # デフォルト値を指定して取得
is_logged_in = request.session.get('is_logged_in', False)
cart_items = request.session.get('cart_items', [])
return HttpResponse(f"ユーザーID: {user_id}, ログイン済み: {is_logged_in}, カート: {cart_items}")
def delete_session_data(request):
if 'user_id' in request.session:
del request.session['user_id'] # 特定のキーのデータを削除
request.session.clear() # 全てのSessionデータをクリア
return HttpResponse("Sessionデータを削除しました。")