Skip to content

Instantly share code, notes, and snippets.

@yano3nora
Last active November 29, 2021 09:50
Show Gist options
  • Save yano3nora/b924f708f2a31ed74f38ca34d1db01d8 to your computer and use it in GitHub Desktop.
Save yano3nora/b924f708f2a31ed74f38ca34d1db01d8 to your computer and use it in GitHub Desktop.
[django: Django note] Django - The web framework for perfectionists with deadlines. #django #python

OVERVIEW

djangoproject.com
django/django - github.com
Django documentation - docs.djangoproject.com

Django at a glance - docs.djangoproject.com - 概要、ここ見とけばだいたいわかる
Contents - docs.djangoproject.com
API Reference - docs.djangoproject.com

Python のデファクトスタンダードな WEB アプリフレームワーク。Laravel や Rails などと比較されるリッチめなフレームワークで Youtube, Dropbox, Instagram などが代表例。MVC ではなく MVT - Model / Template / View というデザインになっていて View がプレゼンテーション層 ( どんな風に提供するか? ) を決めるコントローラ的役割 を担い、Template がいわゆるビューテンプレートという考え方。

フレームワークとしては「フルスクラッチ」とは言うものの、一般的な WEB サイト / アプリに寄せた便利機能や API は殆どない。暗黙知が少なく、ピュアな Python モジュールの集合で構成されつつ、本格的な WEB システムを構築するのに最低限な API 群とルールが提供されている ... といった感じ。

Installation

はじめての Django アプリ作成 - docs.djangoproject.com
プロジェクトと再利用可能アプリ - docs.djangoproject.com

Docker 環境でのインストール手順は yano3nora/djangdock を参照。

django-admin / manage.py

django-admin and manage.py - docs.djangoproject.com

Django はアプリケーションの管理用 CLI ツールとして django-admin ( 管理者権限コマンドツール ) と、manage.py ( その他なんでも ) を提供している。プロジェクトの作成やマイグレーション、シェルの起動など開発では様々な操作をこの CLI から行う。

Project & Applications

Django ベスト・プラクティス

Django のディレクトリ / ファイル構成は Project 配下に再利用可能な Application を複数個入れ込んでいくことが可能な立て付けになっている。以下は django-admin startproject django . したのち python manage.py startapp app して開発を進めた一般的な構成例。また Django は静的ファイル ( いわゆる public とか html ディレクトリ ) の扱いが独特 ... Django staticファイル まとめ を参照のこと。

/
    manage.py
    static/  デプロイ時の collectstatic 先になる
    django/  ここが django プロジェクトの管理アプリ的な感じになる
        __init__.py
        settings.py
        urls.py
        wsgi.py
    app/
        __init__.py
        admin.py
        migrations/
            __init__.py
            0001_initial.py
        models.py
        static/
            app/  collectstatic 時の名前衝突防止のため入れ子にする
                images/
                    background.gif
                style.css
        templates/
            app/  settings.py 設定の際に衝突するためここも入れ子に
                detail.html
                index.html
                results.html
        tests.py
        urls.py
        views.py
        # models / views を分ける場合は大体こんな感じみたい
        # models/
        #     __init__.py < from .models.user import User ...
        #     user.py
        # views/
        #     __init__.py < from .views.user_view from UserView ...
        #     user_view.py

カスタム User モデル

Custom User Model - docs.djangoproject.com

Django はデフォルトで、ユーザモデルとして django.contrib.auth モジュール内の django.contrib.auth.models.User を利用している。このモデルについて多くの認証系 API が用意されており、デフォルトのままでも十分使えるのだが、後でカスタムしたくなったときのために プロジェクトスタート時にカスタム User モデルを用意する ことが推奨される。

但し、認証モデルを User に絞り、これに Blongs To するロジックフルな Author みたいなモデルをぶら下げる設計の場合はこの限りではない。このへんは設計によるが、認証系はシンプルな User で一元管理して、フィールドが異なったり、ロジックをたくさん持つ Admin / Authoer / Manager / User とかは別モデルにした方が好み。いずれにせよ保険的に AbstractUser の拡張クラスは作っておいて損はない。

# settings.py
AUTH_USER_MODEL = 'myapp.MyUser'
# myapp/models.py

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass
# admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User

admin.site.register(User, UserAdmin)

Shell

ipython をインストールした方がよき。

$ pip install ipython
$ python manage.py shell
> exit

# 大体これでリロードできる
> %load_ext autoreload
> %autoreload 2

Clear cache

$ python manage.py shell
> from django.core.cache import cache
> cache.clear()

SMTP

Sending Email

Django でよく利用する python の Docker は sendmail ちゃん入ってなくて送信できない。sendmail のインストールや立ち上げ、Docker からのメール送信はしんどいうえに意味があんまりないので、開発中はコンソールで確認するか、例によって Gmail で適当なアカウント作って SMTP 踏み台に使うのが楽。

# settings.py

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
                # 'django.core.mail.backends.console.EmailBackend'
                # メールを送信せずにコンソール出力する場合は上記だけで OK
EMAIL_HOST          = 'smtp.gmail.com'
EMAIL_PORT          = 587
EMAIL_HOST_USER     = 'username@gmail.com'
EMAIL_HOST_PASSWORD = 'your-password-here'
EMAIL_USE_TLS       = True
# 送信テスト
from django.core.mail import send_mail

send_mail('Subject', 'Message Body', 'from-address@example.com', [
  'to-address1@example.com',
  'to-address2@example.com',
])

Logging

Logging - docs.djangoproject.com

# import the logging library
import logging

# Get an instance of a logger
logger = logging.getLogger(__name__)  # モジュール名をつける慣例
logger.error('Something went wrong!')

# Log level
logger.debug()
logger.info()
logger.warning()
logger.error()
logger.critical()

HTTPS

以下で HTTP アクセスについて HTTPS へ強制リダイレクトを実施できる。Nginx などでリバースプロキシを行っているような場合は無限リダイレクトの可能性があるので注意。

# settings.py

SECURE_SSL_REDIRECT = True

X-Forwarded-Proto header

X-Forwarded-Proto - developer.mozzila.org
SECURE_PROXY_SSL_HEADER

Nginx でリバースプロキシ → アプリサーバ → django に流すようなケースでは、プロキシが 443 (HTTPS) で受けたリクエストを HTTP でアプリサーバに転送するため そのリクエストがセキュアだったか を判定するのに工夫が必要になる。

具体的には、前段でリクエストを受けるリバースプロキシ or ロードバランサーが X-Forwarded-Proto ヘッダを付与し、後段のアプリサーバに読ませるという手法がデファクトスタンダードになっている。

django では SECURE_PROXY_SSL_HEADER 定数で 前段のプロキシから渡された SSL ヘッダを信頼する 設定を行う。

# settings.py

# リクエストを受けた際に、X-Forwarded-Proto ヘッダで
# 「さっきまでこのリクエストは https でした」って言われたら信頼する
#
# この設定をすることで、当然ユーザからの生リクエストでヘッダ偽装されたとき危険になる
# リバースプロキシ構成でのみ設定すること
#
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

MODULES

Django は多くの標準機能 / API をモジュールとして提供している。インストールされているアプリは settings.py > INSTALLED_APPS にて確認。

django.conf

Using settings in Python code

from django.conf import settings

if settings.DEBUG:
    # Do something

django.utils

timezone

python3のdatetimeとtimezoneとpytzについて

from django.utils import timezone

utc_now = timezone.now()         # utc
local_now = timezone.localtime() # local

start_time = timezone.localtime()
end_time   = start_time + timezone.timedelta(minutes=10)

# モデルの created_at をビュー以外で出す時とか
print(timezone.localtime(user.created_at))

django.contrib.auth

User authentication in django - docs.djangoproject.com

django.contrib.auth におけるユーザ認証 Auth の構成要素は以下。OAuth やログイン試行数の制限などより高度なものはさーどぱーちーのライブラリ使えやボケって Django さんは仰っている。

  • User: User モデル
  • Permission: ユーザが特定のタスクを実行できるかどうかを指定するバイナリ ( yes/no ) フラグ
  • Group: 複数のユーザーにラベルとパーミッションを付与する一般的な方法
  • Hash: 設定変更可能なパスワードハッシュシステム
  • View/Form Template: ユーザーログインのためのフォームやビューツール
  • Backend: プラガブルなバックエンドシステム

既に標準モジュール django.contrib.admin をインストールしているなら /admin からユーザのこねこねが可能。

Managing users in the admin - djangoproject.com

# Create super user for django.contrib.auth.
$ python manage.py createsuperuser --username=joe --email=joe@example.com

set_password / authenticate

例によってパスワードはハッシュ保存/照合方式。パスワードのセットには User.set_password を利用。

$ python manage.py shell
>>> from django.contrib.auth import get_user_model
>>> User = get_user_model()
>>> u = User.objects.get(username='john')
>>> u.set_password('new password')  # To hash!!
>>> u.save()

照合時には authenticate を利用する。

from django.contrib.auth import authenticate, login, logout

user = authenticate(username='john', password='secret')
if user is not None:
    login(request, user)  # request セッションに認証情報をぶち込み
else:
    logout(request)       # ログアウトの例、本来こんな実装はしないケド

request.user

例によってセッションにユーザの Auth 認証セッションが入っている。Views / Templates で参照可能。

if request.user.is_authenticated:
    # Do something for authenticated users.
    ...
else:
    # Do something for anonymous users.
    ...
{% if user.is_authenticated %}
  <a href="{% url 'account_logout' %}" class="uk-link-text">
    <span uk-icon="user"></span> {% user_display user %}
  </a>
{% endif %}

login_required decorator / LoginRequired mixin

Limiting access to logged in users - docs.djangoproject.com

Views や Views のアクションについて django.contrib.auth の認証領域下にしたい場合、 views.py でアクションを生やしているなら Decorator が、クラスベースのビューを利用しているなら Mixin が利用できる。

# views.py
from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    # ...
# views/my_view.py
from django.contrib.auth.mixins import LoginRequiredMixin

# LoginRequiredMixin は一番左でロードしてやる
class MyView(LoginRequiredMixin, View):
    login_url = '/login/'
    redirect_field_name = 'redirect_to'

django.contrib.auth.models / AUTH_USER_MODEL / get_user_model

Django Userモデルへの参照方法(AUTH_USER_MODEL, get_user_modelの使い方)

User モデルオブジェクト自体は以下のように django.contrib.auth.models から User モデルを参照可能ではある。但し、基本 User モデルはカスタム User モデルを配置することになるため、モデル定義時の ForeignKey なんかに突っ込むときは settings.AUTH_USER_MODELget_user_model() で参照する。

from django.contrib.auth import get_user_model
from django.db import models

class Article(models.Model):
    author = models.ForeignKey(
        get_user_model(),
        on_delete=models.CASCADE,
    )
    title = models.CharField(max_length=100)
    text = models.TextField()

django.contrib.admin

The Django admin site - docs.djangoproject.com

モデルの管理アプリ。これを開発者ツールとするか、ユーザの管理画面とするかはアナタ次第。Django としては「adminサイトは、あなたのサイトのフロントエンドやその周辺を含んだ全体を作成することを意図していません」だが、例によってカスタマイズ用のフックが多数提供されている。

DjangoのAdmin やりたいこと逆引き(基本)

↑ 読めば大体雰囲気わかるかな。

ModelAdmin

ModelAdmin objects - docs.djangopeorject.com

Admin アプリにモデルを認識させる場合は、ModelAdmin クラスを生成し register で登録してやる。この ModelAdmin クラスをこねこねして Admin アプリ内でのふるまいを決めてやる。

models.py に一緒に書く記事が多いが、ModelAdmin オブジェクトは views 層のロジックが多くなりがちなので、別モジュールで切り出した方がいい。 ( models.py は Django セットアップ初期でロードされる関係で、依存する import は最上位モジュール・モデルだけに絞り view 関連の import を排除する必要があるため )

# nyapp/models.py

from django.db import models

class User(models.Model):
    # ...

class Article(models.Model):
    # ...
# myapp/admins.py

from django.contrib import admin
from myapp.models import Article

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    #
    # 存在する Attributes ( プロパティかメソッド ) を指定
    # 関連モデル系の Attribute は文字列評価を受けるため
    # __str__ でお名前を返却するようにしてやる
    #
    list_display = ['title', 'body']
    list_editable = ['title']  # リストで編集可能にしてくれる
    ordering = ['title']
    search_fields = ['name']  # 検索 input を表示、ここのカラムが検索対象に
# myapp/admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from myapp.models import User
from myapp.admins import *  # UserAdmin 以外で管理画面に表示する通常モデルをロード

# Django 管理画面 (django.contrib.admin) の
# 認証・管理で利用する UserAdmin を User モデルにセット
#
admin.site.register(User, UserAdmin)
ModelAdmin への Action 追加

Adding actions to the ModelAdmin - docs.djangoproject.com

from django.contrib import admin
from myapp.models import Article

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    actions = [make_published]

    def make_published(self, request, queryset):
        queryset.update(status='p')
    make_published.short_description = 'Mark selected stories as published'
Template / Response をカスタマイズ

How to add a custom button to Django change view page?

ModelAdmin クラスには change_form_template のような「編集画面のテンプレートファイルこれ使って」というフックプロパティや、response_change のような「POST リクエスト時のレスポンス変えて」というフックメソッドが用意されている。これらを利用して各モデルオブジェクトに対する「カスタム機能実行ボタン」を生やしたりできる。

# Model class.
class Villain(Entity):
    ...
    is_unique = models.BooleanField(default=True)


# Template html.
{% extends 'admin/change_form.html' %}
{% block submit_buttons_bottom %}
  {{ block.super }}
  <div class="submit-row">
    <input type="submit" value="Make Unique" name="_make-unique">
  </div>
{% endblock %}


# Model Admin class.
@admin.register(Villain)
class VillainAdmin(admin.ModelAdmin, ExportCsvMixin):
    ...
    change_form_template = "entities/villain_changeform.html"

    def response_change(self, request, obj):
        if "_make-unique" in request.POST:
            matching_names_except_this = self.get_queryset(request).filter(name=obj.name).exclude(pk=obj.id)
            matching_names_except_this.delete()
            obj.is_unique = True
            obj.save()
            self.message_user(request, "This villain is now unique")
            return HttpResponseRedirect(".")
        return super().response_change(request, obj)
ModelAdmin.lookup_allowed

ModelAdmin.lookup_allowed(lookup, value)
Django管理画面 list_filterのカスタマイズ
https://django.readthedocs.io/en/stable/ref/contrib/admin/index.html#django.contrib.admin.ModelAdmin.lookup_allowed
https://django.readthedocs.io/en/stable/ref/contrib/admin/index.html#django.contrib.admin.ModelAdmin.list_filter

  • モデルの一覧 (index) ページでは ?hoge_id=3 みたいなクエリでフィルタリングができる
  • list_filter プロパティのカスタムで表示制御できる
  • リレーションを使ったフィルタリングは、セキュリティの関係で lookup_allowed をいじって許可する必要ある
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin)

    # lookup_allowed をオーバライドして 
    # Comment の index で ?article__user__id=3 みたいな
    # フィルタリングを可能にするための変更
    #
    def lookup_allowed(self, lookup, value):
        if lookup in ('article__user__id'):
            return True

        return super(lookup, value)

PACKAGES

Django 2.0 x django-rest-framework でカンバンアプリ - codezine.jp - 実装の雰囲気
Django のおすすめライブラリ - 古め

Django 自体は WEB アプリフレームワークとしてリッチだが、Rails や新興の Laravel なんかと比べると本体の機能はそこまで多くなく最低限といったところ。このへんは Python のエコシステムに存在する Django 向けの Python パッケージを利用して拡張していくような使い方をしているみたい。

django-extensions

django-extensions/django-extensions - github.com - ユーティリティつめあわせ

$ pip install django-extensions
# settings.py

INSTALLED_APPS = [
    # ...
    'django_extensions',
]
# DB の ALL DROP
$ python manage.py db_reset

# ルーティング確認
$ python manage.py show_urls

django-import-export

django-import-export/django-import-export - github.com

CSV くれくれおじさん対策。django.contrib.admin に CSV のインポート/エクスポートを追加する。

django-debug-toolbar

jazzband/django-debug-toolbar - github.com

高機能デバッガ、こういうの結局使わない

django-storages

jschneier/django-storages - github.com

S3 とかとの連携

django-allauth

pennersr/django-allauth
django-allauth.readthedocs.io

OAuth 対応のユーザ認証系ぜんぶやるライブラリ。django.contrib.auth および django.contrib.sites の拡張で提供される。テーブル/モデルに auth_user を利用するが、通常作成したユーザは is_superuser ぢゃないので /admin ログインはできない。

また、メールによる認証を有効にした場合は「ユーザ登録」時にメール送信が挟まるので settings.py ならびに実行環境上でメール送信の設定をしてないとコケるの注意。

OAuth の設定はこのへんを参照 ... Providers / TwitterアカウントでのSNSログイン

$ pip install django-allauth
# settings.py

# django.contrib.sites で allauth サイトにするやつの ID
SITE_ID = 1

# この辺はルーティングや /profile 的なものを作るかどうか
LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '/email/'
ACCOUNT_LOGOUT_REDIRECT_URL = '/'

# https ならこいつを変更、デフォルトは http
ACCOUNT_DEFAULT_HTTP_PROTOCOL = 'https'

# この辺はお好み
# ACCOUNT_USER_MODEL_USERNAME_FIELD = None
# ACCOUNT_EMAIL_REQUIRED = True
# ACCOUNT_USERNAME_REQUIRED = False
# ACCOUNT_AUTHENTICATION_METHOD = 'email'

INSTALLAED_APPS = (
    # The following apps are required:
    'django.contrib.auth',
    'django.contrib.messages',
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    # ... include the providers you want to enable:
    # 'allauth.socialaccount.providers.twitter',
)

TEMPLATES = [
    # ...
    'DIRS': [
        # テンプレートのオーバライド用
        #
        # @see https://github.com/pennersr/django-allauth/tree/master/allauth/templates
        #
        # 上記を参考にオーバライドしたいやつだけディレクトリ / html を作成
        # extends でレイアウト変更したいだけなら base.html だけ変えれば OK
        # 作成してないディレクトリは以下で指定しないこと
        #
        # os.path.join(BASE_DIR, 'your-project', 'templates'),
        # os.path.join(BASE_DIR, 'your-project', 'templates', 'allauth'),
        # os.path.join(BASE_DIR, 'your-project', 'templates', 'allauth', 'account'),
        # os.path.join(BASE_DIR, 'your-project', 'templates', 'allauth', 'socialaccount'),
    ],
    'OPTIONS': [
        'context_processors': [
            # ...
            'django.template.context_processors.request',
        ]
    ]
]

AUTH_USER_MODEL = 'myapp.User'  # カスタム User クラス指定
AUTHENTICATION_BACKENDS = (
    # ...
    # AUTH バックエンドを allauth 固有仕様 ( e-mail 強制 ) に寄せる
    'allauth.account.auth_backends.AuthenticationBackend',
    # 従来の username 方式を採用する場合は以下
    # 'django.contrib.auth.backends.ModelBackend',
)
# urls.py
urlpatterns = [
    # ルーティングやデフォルトテンプレートの利用については
    # 案件によってカスタマイズすればよろし
    #
    path('', include('allauth.urls')),
    path('', lambda r: HttpResponseRedirect('login')),
]
$ python manage.py migrate

ここまでやったら /admin/sites から SITE_ID で指定した django.contrib.sites サイトを選択して、ドメインを設定。ドメインを 192.168.99.100 にした場合、ルーティング /ドメイン/accounts/signup でユーザ登録へ行ける。全ルーティングは django-extensions の show_urls でもみて。

また、E メール確認をしてからユーザを承認 ... みたいな細かなふるまいは settings.py で Configuration を参照しながら設定する感じ。

django.contrib.auth 拡張なので、Django 公式の認証系トピックをあされば大体の操作は可能。その他 allauth 特有のユーザインスタンス操作 API や、メール文など実際のプロダクトでのカスタマイズはマニュアルを参照。

テンプレートタグはこんな感じ。

{% load account %}

{% user_display user as user_display %}
{% blocktrans %}{{ user_display }} has logged in...{% endblocktrans %}

{% load socialaccount %}
{% providers_media_js %}
{% get_social_accounts user as accounts %}
{{accounts.twitter}} -- a list of connected Twitter accounts
{{accounts.twitter.0}} -- the first Twitter account
{% if accounts %} -- if there is at least one social account
{% get_providers as socialaccount_providers %}
<a href="{% provider_login_url "openid" openid="https://www.google.com/accounts/o8/id" next="/success/url/" %}">Google</a>
<a href="{% provider_login_url "twitter" %}">Twitter</a>

また django.contrib.auth.admin に対して django-allauth の認証を前段にかましてやる場合は以下。

# admin.py
from django.contrib import admin
from django.contrib.auth.decorators import login_required

admin.site.login = login_required(admin.site.login)

django-rest-framework

django-rest-framework.org encode/django-rest-framework - github.com
Django REST Frameworkを使って爆速でAPIを実装する

Django での REST Web API 実装フレームワーク。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment