DjangoでDBのフィールドに選択肢を設定する
概要
やりたいこととしては、特定のフィールドに指定できる値をEnum化するようなイメージです。
選択肢リストを設定する
フィールドに choices を指定することで選択肢を設定することができます。
choices の中身ですが『2要素のタプルからなる iterable(リストまたはタプル)』と定義されています。
つまりはこんな感じ。
GENDER_CHOICES = ( (1, 'Male'), (2, 'Female'), ) class Person(models.Model): gender = models.IntegerField(choices=GENDER_CHOICES)
見てわかる通り、一つ目の要素がDBに格納する値で二つ目が表示名です。
選択肢を利用するメリット/デメリット
メリットとしては、そのフィールドに格納され得るデータがプログラム上で定義できるため、追加変更が楽です。
また、以下のようにして設定値に対応する表示名を得ることができます。個人的にはこれがやりたくて使ってるところもあります。
person = Person(gender=1) person.get_gender_display() 'Male'
デメリットですが、定義した選択肢の値が格納されることを強制できません。つまり設定した選択肢以外の値も保存され得ます。ですので、自分で入力チェックなりビジネスロジックを書いて意図しない値が入らないことを保証する必要があります。
MySQL のENUM型でいいじゃん
という方もいそうなので、この話題にも言及しておきましょう。
ENUMを実際に使うとこんな感じでしょうか。
CREATE TABLE person ( gender ENUM('Male', 'Female') );
MySQLのENUM型を使っても良いのですが、選択肢を増減するためにテーブル定義を変更する必要が出てくるため、運用を考えると使いたくないのです。また、DBに格納される値と同じEnum定数をプログラム上にも定義するケースが多く、定数の二重管理になりやすい点も無視できませんね。
ちなみにENUM型はSQLアンチパターン本でも扱われていますので、世間的にもよろしくないものと認知されていると思います。
- 作者: Bill Karwin,和田卓人,和田省二,児島修
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/01/26
- メディア: 大型本
- 購入: 9人 クリック: 698回
- この商品を含むブログ (42件) を見る
SQLアンチパターンによると、ENUM型を使ってよいとされているのは、ON/OFFのように変更されることのない相互排他的な値を扱う場合だけとのことです。
今回例として挙げた性別はこれに該当するでしょうか?
いいえ、該当しません。依頼人の要望で『内緒』を保存したいということもあり得ます。もはや、ENUMは封印してしまうべきでしょう。
定数を管理するテーブルを作ればいいじゃん
ENUM を使わない場合は、以下のように参照テーブルを作る方法が一般的ですね。Django 以外を使うときは私もそうします。
CREATE TABLE gender ( id VARCHAR(20), name VARCHAR(20) ); INSERT INTO gender (key, name) VALUES (1, '男性'), (2, '女性'); CREATE TABLE person ( gender_id INT(11), FOREIGN KEY (gender_id) REFERENCES gender(id) ON UPDATE CASCADE );
確かにこれならやりたいことは実現できます。しかし、いつも思うのですが、表示名を取得するの面倒じゃないですか?わざわざ gender テーブルをジョインして name フィールドを引っ張ってくる必要がありますよね。それを避けるためには、結局プログラム上にIDと表示名を管理する Enum を定義することになり、定数の二重管理に逆戻りです。
結局、どうやってもプログラム上に Enum を定義することになるのですから、それだけで全部管理できるようにするのがベストだとは思いませんか?
本記事にて『入力チェックなりビジネスロジックを書いて意図しない値が入らないことを保証する必要がある』というデメリットをお伝えしましたが、そんなに難しいことではありませんよね。選択肢にない値を保存しようとしたらエラーにするだけで良いのですから、一か所に簡単なロジックを書くだけで済みます。
結論
ENUM型を使うのはやめてフィールドに選択肢を設定しよう!
Django REST frameworkでAPIにアクセス権を実装する方法
Django REST framework シリーズです。今回はアクセス権のお話をします。
Django REST framework を使ったことがないという方はこちらをどうぞ。
はじめに
API を実装する上で欠かせないのが、アクセス権の管理ですね。API 毎に実行可能かどうかをチェックする処理を入れるのは大変ですし、対応漏れも起きやすいです。かといって、自前で共通化するのも大変ですね。
Django REST framework では permission
という概念でAPI毎にアクセス権を設定できるようになっていますので、本記事ではよくあるアクセス権を例に挙げて実装方法について紹介してみようと思います。
具体的な解説に入る前に、サンプルのAPIを作っておきます。
api.py
from rest_framework.generics import GenericAPIView from rest_framework.response import Response class SampleView(GenericAPIView): permission_classes = () def get(self, request, format=None): return Response()
urls.py に以下を追加。
url(r'^sample', SampleView.as_view()),
SampleView に見慣れない permission_classes
というフィールドが登場しましたが、Django REST framework ではここに Permission クラスを設定することでアクセス権を設定するようになります。
認証済みの場合のみアクセスさせたい
お決まりのやつですね。認証済みかどうかを検証するパーミッションについては、 Django REST Framework にて標準で用意されている IsAuthenticated がありますので、こちらを使うだけです。
from rest_framework.generics import GenericAPIView from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response class SampleView(GenericAPIView): permission_classes = (IsAuthenticated,) def get(self, request, format=None): return Response()
permission_classes
に IsAuthenticated を指定しました。これで認証済みでないと実行できないAPIとなります。
管理者のみアクセス可能
これもよくあるパターンですね。今回は特定のメールアドレス(admin@racchai.com)であれば管理者として判定するパーミッションを作成してみます。
作り方は簡単で、rest_framework.permissions.BasePermission
を継承したクラスにて has_permission
関数を定義するだけです。has_permission 内でアクセス可能な条件を設定してあげれば、それだけでパーミッションクラスの完成となります。
from rest_framework.generics import GenericAPIView from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.permissions import BasePermission class IsAdmin(BasePermission): def has_permission(self, request, view): return request.user and request.user.email == 'admin@racchai.com' class SampleView(GenericAPIView): permission_classes = (IsAdmin,) def get(self, request, format=None): return Response()
IsAdmin パーミッションを作成し、それを SampleView に設定しました。たったこれだけで、管理者しかアクセスできないAPIとなりました。
管理者のみ変更可能でそれ以外は閲覧のみさせたい
さきほど作成した IsAdmin パーミッションをさらに発展させてやるだけで実現できますね。新しいパーミッションは IsAdminOrReadOnly
として作成します。
from rest_framework.generics import GenericAPIView from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.permissions import BasePermission, SAFE_METHODS class IsAdmin(BasePermission): def has_permission(self, request, view): return request.user and request.user.email == 'admin@racchai.com' class IsAdminOrReadOnly(BasePermission): def has_permission(self, request, view): return request.method in SAFE_METHODS or IsAdmin().has_permission(request, view) class SampleView(GenericAPIView): permission_classes = (IsAdminOrReadOnly,) def get(self, request, format=None): return Response() def post(self, request, format=None): return Response()
閲覧かどうかは、HTTP メソッドの種類で判別します。フレームワークの方で SAFE_METHODS(GET, HEAD, OPTIONS) が定義されているので、リクエストメソッドがそのどれかであれば True を返します。SAFE_METHODSでない場合は、IsAdmin パーミッションを評価することで実行可能かを判定するようになっています。
まとめ
簡単すぎてつまらない内容だったかもしれませんね。本当はパーミッションの仕組みでオブジェクトベースのアクセス権を設定することもできるのですが、長くなりそうなのでそれはまた別な機会にご紹介することにします。
それでは今回はこのへんで!
django-rest-framework-jwtの認証をカスタマイズする方法
django-rest-framework-jwt については以前こちらの記事でご紹介しました。
django-rest-framework-jwt では認証APIを標準で用意してくれているので、通常はそれを使うことになります。
ですが、論理削除されたユーザーの場合は認証を失敗させたりだとか、認証処理をカスタマイズしたい場合は自前で認証してアクセストークンを返す処理を用意する必要があります。
本記事では、企業コード/メールアドレス/パスワードにて認証するケースを想定したカスタマイズ方法についてご紹介します。
Django および Django REST framework が初めてという方はこちらをどうぞ!
では、さっそく始めます。
プロジェクトを作成する
さくっと作成していきます。
$ django-admin startproject racchai $ cd racchai
settings.py にて INSTALLED_APPS
と DATABASES
を編集。
INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'rest_framework', + 'rest_framework_jwt', + 'racchai', ) DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'racchai', 'USER': 'root', 'PASSWORD': 'root', 'HOST': 'localhost', } }
ついでにMySQLにデータベースを作成しておきましょう。
$ mysql -uroot -p -e "CREATE DATABASE racchai";
はい、以上です。
認証モデルを定義する
Django のユーザーテーブルには企業コードは存在しませんので、追加しておく必要があります。
ユーザーテーブルのカス高い図については、ここでは詳しい解説は割愛します。よくわからないという方はこちらをご参照ください。
今回は models.py を以下のように作成するだけでひとまずはOKです。
from django.db import models from django.contrib.auth.models import AbstractBaseUser, BaseUserManager class RacchaiUserManager(BaseUserManager): def create_user(self, email, password=None, **extra_fields): if not email: raise ValueError('Users must have a email address') email = RacchaiUserManager.normalize_email(email) user = self.model(email=email, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, password): return self.create_user(email, password) class RacchaiUser(AbstractBaseUser): email = models.EmailField(max_length=128, unique=True) company = models.CharField(max_length=128, null=True) USERNAME_FIELD = 'email' objects = RacchaiUserManager() class Meta: db_table = 'racchai_user' swappable = 'AUTH_USER_MODEL'
本当は ./manage.py createsuperuser
で企業コードも設定できるようにしたいところですが、今回はそこまではやりません。
RacchaiUserを作成したら、settings.py に以下を追記して認証モデルを差し替えておきます。
AUTH_USER_MODEL = 'racchai.RacchaiUser'
最後にマイグレーションを実行したら完了です。
$ ./manage.py migrate
念のためテーブル定義を確認。問題なさそうですね。
mysql> desc racchai_user; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | password | varchar(128) | NO | | NULL | | | last_login | datetime(6) | YES | | NULL | | | email | varchar(128) | NO | UNI | NULL | | | company | varchar(128) | NO | | NULL | | +------------+--------------+------+-----+---------+----------------+
Serializerを定義する
次はAPIの入力情報を扱うSerializerを定義します。今回は入力値が3つあるので、以下のようになります。
jwt_auth.py
from rest_framework import serializers class AuthInputSerializer(serializers.Serializer): company = serializers.CharField() email = serializers.EmailField() password = serializers.CharField()
View を定義する
最後に API 本体の実装です。さきほど Serializer を定義した jwt_auth.py に以下を追記しましょう。
from django.contrib.auth import authenticate from rest_framework_jwt.settings import api_settings from rest_framework.generics import GenericAPIView from rest_framework.exceptions import AuthenticationFailed from rest_framework.response import Response jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER class AuthView(GenericAPIView): serializer_class = AuthInputSerializer def post(self, request): serializer = AuthInputSerializer(data=request.data) serializer.is_valid(raise_exception=True) user = authenticate(**serializer.data) if not user: raise AuthenticationFailed() # 以下2行が今回のカスタマイズ部分 if user.company != serializer.data['company']: raise AuthenticationFailed() payload = jwt_payload_handler(user) return Response({ 'token': jwt_encode_handler(payload), })
最後に urls.py に以下を追記して /jwt_auth
でアクセスできるようにします。
from racchai.jwt_auth import AuthView urlpatterns = [ url(r'^jwt_auth', AuthView.as_view()), ]
これで認証ができるようになったはずです。
試してみる
API を試す前に、まだユーザーを登録していなかったので先にユーザーを作成しておきます。
$ ./manage.py createsuperuser Email: jwt@racchai.com Password: jwt Password (again): jwt Superuser created successfully.
前述の通り、この時点ではユーザーレコードに会社コードが設定されていませんので、SQL で直接会社コードを設定してしまいます。会社コードは仮に0001
としておきます。
$ mysql -uroot -p racchai -e "update racchai_user set company = '0001'"
準備が整ったところで、開発サーバーを起動して認証APIを実行してみます。
$ ./manage.py runserver .... $ curl http://localhost:8000/jwt_auth -d "email=jwt@racchai.com&password=jwt&company=0001" {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imp3dEByYWNjaGFpLmNvbSIsInVzZXJfaWQiOjIsImVtYWlsIjoiand0QHJhY2NoYWkuY29tIiwiZXhwIjoxNDYzNjI1NDQ5fQ.YX6a_9680xDDYOiqwXX1kAZA1dMH3rLIXq7oGTaOawc"}
無事JWTアクセストークンを取得できましたね!
念のため、会社コードを間違えたらどうなるのか見ておくことにします。
$ curl http://localhost:8888/jwt_auth -d "email=jwt@racchai.com&password=jwt&company=0002" {"detail":"Incorrect authentication credentials."}
ちゃんと認証に失敗することも確認できましたね。
まとめ
だいぶ早足でしたが、カスタマイズ自体は難しいことではないってことだけ伝われば幸いです!
DjangoでJWTを使ったトークン認証を実装する
JWT とは
Json Web Token の略。一言でいってしまうとJSONに署名したもの。こちらの記事で詳しく解説されています。
はじめに
さっそく環境を作っていきます。最初はお決まりのプロジェクト作成です。
詳しい手順と説明はこちらの記事をご参照ください。
- 作者: Julia Elman,Mark Lavin
- 出版社/メーカー: O'Reilly Media
- 発売日: 2014/11/04
- メディア: Kindle版
- この商品を含むブログを見る
ではプロジェクトを作成します。
$ django-admin startproject racchai
$ cd racchai
今回は認証を扱うので、データベースとユーザーも作成しておきます。
$ ./manage.py migrate │ Operations to perform: │ Synchronize unmigrated apps: staticfiles, messages │ Apply all migrations: admin, contenttypes, auth, sessions │ Synchronizing apps without migrations: │ Creating tables... │ Running deferred SQL... │ Installing custom SQL... │ Running migrations: │ Rendering model states... DONE │ Applying contenttypes.0001_initial... OK │ Applying auth.0001_initial... OK │ Applying admin.0001_initial... OK │ Applying contenttypes.0002_remove_content_type_name... OK │ Applying auth.0002_alter_permission_name_max_length... OK │ Applying auth.0003_alter_user_email_max_length... OK │ Applying auth.0004_alter_user_username_opts... OK │ Applying auth.0005_alter_user_last_login_null... OK │ Applying auth.0006_require_contenttypes_0002... OK │ Applying sessions.0001_initial... OK $ ./manage.py createsuperuser Username (leave blank to use 'kon'): racchai Email address: jwt@racchai.com Password: racchai Password (again): racchai Superuser created successfully.
何度やっても楽ちんですね。
トークンを取得してみる
今回は django-rest-framework-jwt を使うのでインストールしておきます。
$ pip install djangorestframework-jwt
インストールができたらracchai/settings.py に以下を追記します。
REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ), 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', ), 'NON_FIELD_ERRORS_KEY': 'detail', 'TEST_REQUEST_DEFAULT_FORMAT': 'json' }
お次は racchai/urls.py を以下のように編集。
+ from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ + url(r'^jwt-token', obtain_jwt_token), ]
基本設定は以上です。あっという間ですね。では開発サーバーを起動してトークンを取得してみましょう。
$ ./manage.py runserver $ curl http://localhost:8000/jwt-token -d "username=racchai&password=racchai" {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJhY2NoYWkiLCJ1c2VyX2lkIjoxLCJlbWFpbCI6Imp3dEByYWNjaGFpLmNvbSIsImV4cCI6MTQ2MTY1MzY0N30.UBZrYz79xeI25-Uufmh2zdwZXXzHjamh9rHeOS2Fuqk"}
無事取得できました。簡単すぎて本当に動いてるのか疑いたくなりますね。念のためパスワードを間違えてみましょう。
$ curl http://localhost:8000/jwt-token -d "username=racchai&password=jwt" {"detail":["Unable to login with provided credentials."]}
ちゃんと認証失敗しました。安心してください、動いてます。
トークンを使って認証が必要なAPIを実行してみる
まずは認証が必要なAPIを用意します。django-rest-framework-jwt を使っているのですから、APIも django-rest-framework で作ることにします。
django-rest-framework の初期設定についてはこちらの記事で紹介しています。
次は、ログインユーザー名で返事するだけのAPIを作成しましょう。
racchai/views.py
from rest_framework.generics import GenericAPIView from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework import status class PingViewSet(GenericAPIView): permission_classes = (IsAuthenticated,) def get(self, request, format=None): return Response(data={'username': request.user.username}, status=status.HTTP_200_OK)
APIが作成できたら racchai/urls.py
を以下のように編集します。
from django.conf.urls import url from rest_framework_jwt.views import obtain_jwt_token + from racchai import views urlpatterns = [ url(r'^jwt-token', obtain_jwt_token), + url(r'^ping', views.PingViewSet.as_view()), ]
準備ができたら作成したAPIにアクセスしてみましょう。
$ curl http://localhost:8000/ping {"detail":"Authentication credentials were not provided."}
認証情報が指定されてないと怒られてしまいました。では JWT トークンをリクエストに乗せて再度トライ。
$ curl http://localhost:8888/ping -H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJhY2NoYWkiLCJ1c2VyX2lkIjoxLCJlbWFpbCI6Imp3dEByYWNjaGFpLmNvbSIsImV4cCI6MTQ2MTY1MzY0N30.UBZrYz79xeI25-Uufmh2zdwZXXzHjamh9rHeOS2Fuqk" {"username":"racchai"}
無事認証が通ってログインユーザーの情報を取得できていますね!
まとめ
django-rest-framework-jwt を使うことで、一瞬でトークンベースの認証を実現することができました。携帯アプリと連携する際には重宝する機能だと思いますので、ぜひご活用ください。
- 作者: 株式会社ビープラウド
- 出版社/メーカー: 秀和システム
- 発売日: 2015/05/21
- メディア: Kindle版
- この商品を含むブログを見る
Djangoを使ったメールの送信方法まとめ
Python の smtplib モジュール に慣れている人には不要かもしれませんが、Django にもメール送信するための仕組みが備わっていますので簡単に紹介してみます。
Django 自体使ったことがないという方はこちらをどうぞ。
はじめに
本記事はこちらのコマンドでダミーSMTPサーバを起動している前提で話を進めます。
$ sudo python -m smtpd -n -c DebuggingServer -d localhost:25
settings.py に上記のサーバーへのアクセス情報を記載しておきましょう。これだけで Django がSMTPサーバを認識してくれます。
EMAIL_HOST = 'localhost' EMAIL_PORT = 25
なお、AWSの SES 等を利用する場合は当然認証情報が必要ですので、以下の項目を追加しておいてください。
EMAIL_HOST_USER = ユーザー
EMAIL_HOST_PASSWORD = パスワード
EMAIL_USE_TLS = True
単純なメール送信
それでは Django のインタラクティブコンソールでメールを送ってみます。使うのは一番単純なメール送信関数である send_mail
です。
$ ./manage.py shell >>> from django.core.mail import send_mail >>>send_mail('hello', 'this is message', 'from@racchai.com', ['to@racchai.com']) 1
返り値は送信に成功した数なので、今回は1が返れば成功です。起動しておいたダミーSMTPサーバのコンソールを確認してみます。
MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Subject: hello From: from@racchai.com To: to@racchai.com Date: Wed, 27 Apr 2016 08:54:50 -0000 Message-ID: <20160427085450.80900.67436@ubuntu.local> X-Peer: 127.0.0.1 this is message
無事メールが送れていますね。
複数のメールを一度に送信する
メール本文に名前を入れる等の理由で送信先毎に内容が異なるような場合には send_mass_mail
を使います。
$ ./manage.py shell >>> from django.core.mail import send_mass_mail >>> message1 = ('message1 subject', 'this is message1', 'from@racchai.com', ['to1@racchai.com']) >>> message2 = ('message2 subject', 'this is message2', 'from@racchai.com', ['to2@racchai.com']) >>> send_mass_mail((message1, message2)) 2
2通のメール送信に成功しました。
---------- MESSAGE FOLLOWS ---------- MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Subject: message1 subject From: from@racchai.com To: to1@racchai.com Date: Wed, 27 Apr 2016 09:04:22 -0000 Message-ID: <20160427090422.80900.33900@ubuntu.local> X-Peer: 127.0.0.1 this is message1 ------------ END MESSAGE ------------ ---------- MESSAGE FOLLOWS ---------- MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Subject: message2 subject From: from@racchai.com To: to2@racchai.com Date: Wed, 27 Apr 2016 09:04:22 -0000 Message-ID: <20160427090422.80900.62258@ubuntu.local> X-Peer: 127.0.0.1 this is message2 ------------ END MESSAGE ------------
SMTP サーバの方にも確かに2通分届いていますね。
ちなみに send_mass_mail
では複数のメールを送る場合でも一度しかSMTPサーバとの接続を張らないようになっているため、大量の接続が張られてしまう心配はありません。
メールにファイルを添付する
send_mail
と send_mass_mail
にはファイルを添付する機能がありません。シンプルにテキストメールを送ることしかできないのです。そのため、ファイル添付のようなことをやりたくなったら、ある程度は自分で処理を書く必要があります。とはいっても大した処理ではありません。
添付ファイルを送る処理はこのようになります。
>>> from django.core.mail import EmailMessage >>> message = EmailMessage('subject', 'this is message', from_email='from@racchai.com', to=['to@racchai.com']) >>> message.attach('sample.txt', 'contents', 'text/plain') >>> message.send() 1
では実際に送信されるメールを見てみます。
---------- MESSAGE FOLLOWS ---------- Content-Type: multipart/mixed; boundary="===============5970738729801732969==" MIME-Version: 1.0 Subject: subject From: from@racchai.com To: to@racchai.com Date: Wed, 27 Apr 2016 12:28:09 -0000 Message-ID: <20160427122809.80900.57799@ubuntu.local> X-Peer: 127.0.0.1 --===============5970738729801732969== MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit this is message --===============5970738729801732969== MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="sample.txt" contents --===============5970738729801732969==-- ------------ END MESSAGE ------------
multipart なメッセージになり、sample.txt が添付されていますね。
ちなみに、CC や BCC も EmailMessage を使うことで実現できます。以下のように cc と bcc を指定してあげればOKです。
EmailMessage('subject', 'this is message', from_email='from@racchai.com', to=['to@racchai.com'], cc=['cc@racchai.com'], bcc=['bcc@racchai.com'])
複数の EmailMessageを一度に送信する
send_mass_mail
の中で、複数のメールを送る際に一度しかSMTPサーバとの接続を張らないようになっているという話をしました。以下のようにすることで、send_mass_mail
と同様に複数のEmailMessage を効率的に送信することができるようになります。
>>> from django.core import mail >>> connection = mail.get_connection() >>> message1 = mail.EmailMessage('subject', 'this is message1', from_email='from@racchai.com', to=['to1@racchai.com']) >>> message2 = mail.EmailMessage('subject', 'this is message2', from_email='from@racchai.com', to=['to2@racchai.com']) >>> connection.send_messages([message1, message2]) 2 >>> connection.close()
connection を手動で張っているため、クローズするのを忘れないように注意しましょう。
ひとこと
Django でのメール送信には多少のコードを書く必要がありますが、それでも smtplib モジュールを使うよりは楽に送信することができますね。それではまた!
Django×Python (LLフレームワークBOOKS)
- 作者: 露木誠
- 出版社/メーカー: 技術評論社
- 発売日: 2009/02/16
- メディア: 単行本(ソフトカバー)
- 購入: 10人 クリック: 129回
- この商品を含むブログ (36件) を見る
Djangoで認証ユーザーモデルをカスタマイズする
Djangoで認証を行ったりした際に得られる User オブジェクトですが、デフォルトのままだと不要なデータが多かったり、必要なフィールドがなかったりします。
そんなときのために、今回はデフォルトのモデルである django.contrib.auth.models.User
をカスタマイズして、新しい User モデルを作成する方法について解説します。
プロジェクトを作成
お決まりの racchai プロジェクトを作成します。
$ django-admin startproject racchai
django のインストールがまだという方はこちらを参考にインストールしておきましょう。
プロジェクトが作成できたら、racchai プロジェクトをINSTALLED_APPS
に追加しておきます。
# Application definition INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'racchai', )
プロジェクトの作成はこれでおしまいです。
データベースの設定
racchai/settings.pyを見てみましょう。
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }
初期設定では sqliteを使うことになっていますね。本記事では MySQL を使って進めていきますので、MySQL を使うように設定を変更しておきます。
mysql のユーザー名およびパスワードについては自身の環境にあわせて置き換えてください。
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'racchai', 'USER': 'root', 'PASSWORD': 'root', 'HOST': 'localhost', } }
上記の設定で、ローカルにインストールされた MySQL の racchai というデータベースを参照するようになりました。
さっそくデータベースとテーブルを作成してみましょう。
っとその前に、まだ python から MySQL へアクセスするためのドライバをインストールしていませんので、インストールしておく必要があります。
$ pip install MySQL-python
これで準備が整いましたので、データベースとテーブルを作成します。
$ mysql -uroot -p -e "CREATE DATABASE racchai"; $ ./manage.py migrate Operations to perform: Synchronize unmigrated apps: staticfiles, messages Apply all migrations: admin, contenttypes, auth, sessions Synchronizing apps without migrations: Creating tables... Running deferred SQL... Installing custom SQL... Running migrations: Rendering model states... DONE Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying sessions.0001_initial... OK
はい、作成されました。
ではユーザーモデルに対応するテーブル情報を見てみましょう。
$ mysql -uroot -p racchai mysql> desc auth_user; +--------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | password | varchar(128) | NO | | NULL | | | last_login | datetime(6) | YES | | NULL | | | is_superuser | tinyint(1) | NO | | NULL | | | username | varchar(30) | NO | UNI | NULL | | | first_name | varchar(30) | NO | | NULL | | | last_name | varchar(30) | NO | | NULL | | | email | varchar(254) | NO | | NULL | | | is_staff | tinyint(1) | NO | | NULL | | | is_active | tinyint(1) | NO | | NULL | | | date_joined | datetime(6) | NO | | NULL | | +--------------+--------------+------+-----+---------+----------------+
いろいろ定義されていますね。次はいよいよカスタマイズする方法です。
RacchaiUserモデルを作成する
今回は以下のようなモデルを作成してみます。
- テーブル名を
racchai_user
とする - ログイン名として
email
フィールドを持つ - twitter の URL を持つ
ではやってみます。
racchai/models.py というファイルを作り、その中で RacchaiUser
クラスを定義しましょう。
from django.db import models from django.contrib.auth.models import AbstractBaseUser, BaseUserManager class RacchaiUserManager(BaseUserManager): def create_user(self, email, password=None, **extra_fields): if not email: raise ValueError('Users must have a email address') email = RacchaiUserManager.normalize_email(email) user = self.model(email=email, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, password): return self.create_user(email, password) class RacchaiUser(AbstractBaseUser): email = models.EmailField(max_length=128, unique=True) twitter_url = models.URLField() USERNAME_FIELD = 'email' objects = RacchaiUserManager() class Meta: db_table = 'racchai_user' swappable = 'AUTH_USER_MODEL'
RacchaiUser
を作成すると同時に RacchaiUserManager
も作成していますが、ユーザーモデルをカスタマイズする際はこれらをセットで作成する必要があるということだけ覚えておいてもらえれば大丈夫です。
次は、Django が参照しているモデルクラスを差し替えます。racchai/settings.py にて以下を追記してください。
AUTH_USER_MODEL = 'racchai.RacchaiUser'
できましたか?
たったこれだけでカスタマイズしたUserモデルに差し替えることができています。マイグレーションして確認してみましょう。
$ ./manage.py makemigrations racchai Migrations for 'racchai': 0001_initial.py: - Create model RacchaiUser $ ./manage.py migrate Operations to perform: Synchronize unmigrated apps: staticfiles, messages Apply all migrations: admin, contenttypes, sessions, auth, racchai Synchronizing apps without migrations: Creating tables... Running deferred SQL... Installing custom SQL... Running migrations: Rendering model states... DONE Applying racchai.0001_initial... OK The following content types are stale and need to be deleted: auth | user Any objects related to these content types by a foreign key will also be deleted. Are you sure you want to delete these content types? If you're unsure, answer 'no'. Type 'yes' to continue, or 'no' to cancel: yes
マイグレーションは無事成功です。次は肝心のテーブル情報を見てみましょう。
$ mysql -uroot -p racchai mysql> desc racchai_user; +-------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | password | varchar(128) | NO | | NULL | | | last_login | datetime(6) | YES | | NULL | | | email | varchar(128) | NO | UNI | NULL | | | twitter_url | varchar(200) | NO | | NULL | | +-------------+--------------+------+-----+---------+----------------+
racchai_user
テーブルが作成されており、中身はさきほど RacchaiUser
クラスで定義したものになっていることがわかりますね。
動作確認
では実際にDjangoが参照するユーザーモデルが差し替わっているのか確認してみます。
ユーザー作成
createsuperuser
コマンドで racchai_user
テーブルにデータが登録されるかを確認してみます。
$ ./manage.py createsuperuser Email: test@racchai.com Password: test Password (again): test Superuser created successfully. $ mysql -uroot -p racchai -e "SELECT * FROM racchai_user" +----+-------------------------------------------------------------------------------+------------+---------------+-------------+ | id | password | last_login | email | twitter_url | +----+-------------------------------------------------------------------------------+------------+---------------+-------------+ | 1 | pbkdf2_sha256$20000$MlRFZRiDro4P$lIhUqpljqcqWDiaj0AnCTB8Y2NWBE5wempF31hY6jws= | NULL | test@racchai.com | | +----+-------------------------------------------------------------------------------+------------+---------------+-------------+
入りました!
認証
ユーザー認証の結果として得られるユーザーオブジェクトが差し替わっているかを確認します。
$ ./manage.py shell >>> from django.contrib.auth import authenticate >>> authenticate(email='test@racchai.com', password='test') <RacchaiUser: test@racchai.com>
無事認証結果が RacchaiUser
オブジェクトになってますね!
まとめ
今回はユーザーモデルをカスタマイズする方法についてご紹介してみました。
多少手間ではありますが、サービスに合ったユーザーモデルにカスタマイズするだけで開発効率が全然違ってくると思いますので、ぜひ試してみてください。
Django REST framework でバリデーションしてみよう
みなさんお待ちかねの Django REST framework シリーズです。
バリデーションって大事なんだけど仕組みを作るのって地味にめんどくさいですよね。
Django REST framework ではそこそこ簡単に書けるようになってますので、ご紹介します。
事前準備
まずは簡単にプロジェクトを作成します。
詳しい手順と説明はこちらの記事をご参照ください。
ではプロジェクトを作成します。
$ django-admin startproject racchai
$ cd racchai
Serializer クラスを作成する
前回の記事でも触れましたが、バリデーションは Serializer を使用して実現します。
簡単な Serializer を用意しましたので、作成しましょう。
$ cat > racchai/serializers.py <<EOF from rest_framework import serializers class SimpleValidationSerializer(serializers.Serializer): integer= serializers.IntegerField() EOF
これだけで、string フィールドは必須で、文字列のみというバリデーションができるSerializerが作成できました。
検証してみる
インタラクティブコンソールで検証してみましょう。
$ ./manage.py shell >>> from racchai.serializers import SimpleValidationSerializer >>> s = SimpleValidationSerializer(data={'integer': 1}) >>> s.is_valid() True
string に文字列を指定すると、もちろん True となります。
次は 空データで試してみましょう。
>>> s = SimpleValidationSerializer(data={})
>>> s.is_valid()
False
今度は失敗しました。バリデーションが実行されていそうですね。
バリデーションメッセージを取得する
バリデーションに失敗したら、バリデーションメッセージを取得してみましょう。
>>> s = SimpleValidationSerializer(data={}) >>> s.is_valid() False >>> s.errors {'integer': [u'This field is required.']}
integer フィールドが指定されていないことが原因だということがわかりますね。
試しに文字列を指定した場合のメッセージも見てみましょう。
>>> s = SimpleValidationSerializer(data={'integer': 'str'}) >>> s.is_valid() False >>> s.errors {'integer': [u'A valid integer is required.']}
ちゃんと数値でない値を指定したことが原因であることがわかります。
バリデーションメッセージをカスタマイズする
バリデーションメッセージは自前で用意することが多いですよね。
今回はシンプルに Serializer 内でメッセージを差し替える方法を紹介します。
SimpleValidationSerializer を以下のように変更してみましょう。
class SimpleValidationSerializer(serializers.Serializer): integer= serializers.IntegerField(error_messages={'required': 'racchai!'})
ではバリデーションエラーを表示してみます。
>>> from racchai.serializers import SimpleValidationSerializer >>> s = SimpleValidationSerializer(data={}) >>> s.is_valid() False >>> s.errors {'integer': ['racchai!']}
racchai!
成功です。
意図しないデータをフィルタリングする
バリデーションを通過したら、その結果のデータを取得することになります。
その際、Serializer は自身に定義されていないパラメータを除去してくれるようになっています。
試しに、不要なデータをバリデーションにかけてみます。
>>> s = SimpleValidationSerializer(data={'integer': 1, 'not_defined': 1}) >>> s.is_valid() True >>> s.validated_data OrderedDict([(u'integer', 1)])
ちゃんと not_defined
が除去されて、Serializer で定義されたフィールドだけが取得できましたね。
ちなみに、validated_data
は is_valid
を呼び出した後でないとアクセスできません。
でないと以下のようなエラーになります。
AssertionError: You must call `.is_valid()` before accessing `.validated_data`.
エラーメッセージを読めばすぐ原因がわかるものではありますが、このエラーを見て困惑する人は多い気はしますね。
おまけ
is_valid
関数には、raise_exception=True
を与えることで erorrs 情報をラップした ValidationError を投げてくれる機能がついています。
>>> s = SimpleValidationSerializer(data={'integer': 'str'}) >>> s.is_valid(raise_exception=True) ValidationError: {'integer': [u'A valid integer is required.']}
自分で API を実装するときは、楽なのでこの方法を使うことにしています。
最後に
いかがでしたでしょうか。
シンプルな設計なので、わかってしまえば直観的にサクサク実装を進められそうですね。
今回はカスタムバリデーターを作成する方法については触れませんでしたが、近いうちにご紹介できればと思います。
それではまた!