git diffで^Mが表示された時の対応方法(改行コードをCRLFからLFに変換)

背景

Gitを使っていると他の方が作成したファイルを変更することも多々あります。

今回のそのケースで、変更後git diffで変更点を確認すると、

1
2
3
+        },^M
+ {^M
}

という形で見慣れた^Mが表示されてしまいました。

最初はコピペ元が良くないのかと思っていたのですが、そうではなかったです。原因と対策を以下に記載します。

原因

原因は改行コードの不一致でした。元のファイルの改行コードはCRLFでわたしの環境ではLF(Mac)だったため、CRが足りずに^Mという表示がされてしまったようです。

対策

元のファイルの改行コードをLFに変換します。vscodeを使って行います。

まず、該当のファイルを開きます。ウインドウの右下に今開いているファイルの文字コードや改行コード(CRLF)が表示されています。

改行コードをクリックすると、ウインドウの上部に改行コードの選択と表示されて、LFとCRLFを選択できるようになります。そこでLFを選択し、保存します。

見た目は何もかわっていないのですが、git diffで確認すると、全ての行が変更されているのがわかります。

その後、変更を加えても^Mは表示されなくなりました。

まとめ

改行コードの違いについて理解しているものの、^Mが表示されるとなんでこうなった?と軽めのパニック的な感じになってしまいます。Windowsを使っている方と一緒にプロジェクトに取り組む場合はよく発生すると思います。

今後はこういったことのないようにこの記事を思い出して対応しようと思います。

ジェネレータで特定のディレクトリ配下にcontrollerを作成する

背景

APIの実装する場合など、pathにプレフィックスがつくことが多い(apiv1など)。その場合に、rails g controllerでそのディレクトリ配下にコントローラーを作成する方法がわからなかかったので調べてみました。

パスの指定方法

作成方法は2通りあるようです。
(今回は、app/controllers/api/v1/配下にtests_controller.rb作ることとします。)

/で区切るパターン

1
$ rails g controller api/v1/tests

namespace(::)で区切るパターン

1
$ rails g controller api::v1::tests

不要なファイルを生成しないための方法

今回はAPIなので、viewファイルなどは不要でした。なので、generatorのオプションでviewファイルを生成しないようにできないかを調べてみました。helpを見てみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Usage:
rails generate controller NAME [action action] [options]

Options:
[--skip-namespace], [--no-skip-namespace] # Skip namespace (affects only isolated engines)
[--skip-collision-check], [--no-skip-collision-check] # Skip collision check
[--skip-routes], [--no-skip-routes] # Don't add routes to config/routes.rb.
[--helper], [--no-helper] # Indicates when to generate helper
# Default: true
-e, [--template-engine=NAME] # Template engine to be invoked
# Default: erb
-t, [--test-framework=NAME] # Test framework to be invoked
# Default: rspec

Rspec options:
[--request-specs], [--no-request-specs] # Generate request specs
# Default: true
[--controller-specs], [--no-controller-specs] # Generate controller specs
[--view-specs], [--no-view-specs] # Generate view specs
[--routing-specs], [--no-routing-specs] # Generate routing specs
[--helper-specs], [--no-helper-specs] # Indicates when to generate helper specs

Runtime options:
-f, [--force] # Overwrite files that already exist
-p, [--pretend], [--no-pretend] # Run but do not make any changes
-q, [--quiet], [--no-quiet] # Suppress status output
-s, [--skip], [--no-skip] # Skip files that already exist

Description:
Generates a new controller and its views. Pass the controller name, either
CamelCased or under_scored, and a list of views as arguments.

To create a controller within a module, specify the controller name as a
path like 'parent_module/controller_name'.

This generates a controller class in app/controllers and invokes helper,
template engine, assets, and test framework generators.

Example:
`bin/rails generate controller CreditCards open debit credit close`

CreditCards controller with URLs like /credit_cards/debit.
Controller: app/controllers/credit_cards_controller.rb
Test: test/controllers/credit_cards_controller_test.rb
Views: app/views/credit_cards/debit.html.erb [...]
Helper: app/helpers/credit_cards_helper.rb

今回は--no-helper--no-request-specsを指定してみます。

1
2
3
4
5
$ rails g controller api/v1/tests --no-helper --no-request-specs
create app/controllers/api/v1/tests_controller.rb
invoke erb
create app/views/api/v1/tests
invoke rspec

APIなのでviewsディレクトリも不要なのですが、ジェネレータのオプションで生成を制御できないようだったので、生成後削除しました。

まとめ

今回はジェネレータを使って特定のディレクトリ配下にcontrollerを作成する方法について調べてみました。
ヘルプを見ればわかる話でしたが、2通りの方法があることがわかりました。
また、ジェネレータ実行時のオプションについても調べました。

ジェネレータは便利ですし、カスタムジェネレータの作成もできます。
いろいろ便利なので、深く掘って理解したいと思います。

letter_openerの設定

ローカル開発環境でメール受信

ローカル開発環境でのメール受信はletter_openerを使います。プロジェクトの初期設定で必須になると思いますが、忘れがちなのでメモします。

gemの追加

letter_opener_webというgemがあるので追加します。Gemfileを編集します。

1
2
3
4
group :development do
gem "letter_opener_web"
end
(その他のgemは省略しています)

bundle installします

1
$ docker compose run --rm web ./bin/bundle install

インストールができました。GemfileとGemfile.lockをcommitしておきます。

routesの設定

http://localhost:3000/letter_openerでアクセスできるようconfig/routes.rbに以下の行を追加します

1
2
3
Rails.application.routes.draw do
mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
end

ActionMailerの設定

最後にActionMailerの設定をします。config/environments/development.rbに以下の2行を追加します。

1
2
3
config.action_mailer.default_url_options = { host: "localhost:3000"}

config.action_mailer.delivery_method = :letter_opener_web

config.action_mailerが記載されている付近に追加します。

1行目の設定はこの設定がないと、メールにURLを記載する場合のドメインがわからずエラーになってしまいます。

2行目の設定は、letter_opener_webでメールを送るという設定で、この設定がないとhttp://localhost:3000/letter_openerにアクセスしてもメールが見れません(メール送信のログが表示されて送られているように見えるがどこにも届かない)

まとめ

Railsの開発環境構築はいろいろ設定することが多いですが、設定する機会が少なく忘れてしまいます。出だしで躓かないようメモしておきました。

annotateの設定

テーブルのスキーマがわからなくなるときの対応

Railsで開発を行なっているときに、まだ馴染みのないモデルに遭遇すると、どういったカラムがあるかなど、スキーマがわからずに困ることがよくあります。

db/schema.rbを確認すればわかるのですが、モデルのファイルを見ながらdb/schema.rbを見るのは不便です。

何かしらの方法でテーブルのスキーマを確認しつつモデル内でロジックを実装したい、その場合に利用すると便利なgemがannotateです。

gemの追加

annotateというgemがあるので追加します。Gemfileを編集します。

1
2
3
4
group :development do
gem "annotate"
end
(その他のgemは省略しています)

bundle installします

1
2
3
4
$ docker compose run --rm web ./bin/bundle install
...
Fetching annotate 3.2.0
Installing annotate 3.2.0

インストールができました。GemfileとGemfile.lockをcommitしておきます。

annotateの設定ファイルの作成

annotateの設定ファイルを生成するgeneratorがあるので実行します。

1
2
$ docker compose run --rm web ./bin/rails g annotate:install
create lib/tasks/auto_annotate_models.rake

annotateのカスタマイズ

カスタマイズに関しては、先程作成したタスクのAnnotate.set_defaultsメソッドの引数を編集することで行います。

わたしが変更した内容を記載します。

position_in_xxxx

xxxxにはclassfactoryなどが入ります。それぞれのファイルのどの部分にannotateによるドキュメントを追加するか、という設定になります。
コードの後ろに追加した方が見通しがいいかなと思い、afterに変更しています(デフォルトはbefore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
       'routes'                      => 'false',
'models' => 'true',
- 'position_in_routes' => 'before',
- 'position_in_class' => 'before',
- 'position_in_test' => 'before',
- 'position_in_fixture' => 'before',
- 'position_in_factory' => 'before',
- 'position_in_serializer' => 'before',
+ 'position_in_routes' => 'after',
+ 'position_in_class' => 'after',
+ 'position_in_test' => 'after',
+ 'position_in_fixture' => 'after',
+ 'position_in_factory' => 'after',
+ 'position_in_serializer' => 'after',
'show_foreign_keys' => 'true',

with_comment

trueだとカラム名の横にそのカラムに設定されているコメントが追加されます。ですが、カラムを削除する際に、annotateが正しく動作しなかったので、falseに設定します。

1
2
3
4
5
6
       'wrapper_open'                => nil,
'wrapper_close' => nil,
- 'with_comment' => 'true'
+ 'with_comment' => 'false'
)
end

動作確認

ここで正しく動作するか確認します。

1
2
3
$ docker compose run --rm web ./bin/bundle exec annotate

Annotated (ファイル数): ファイル名, ....

このように表示されればOKです。ファイル数にはコメントが追加されたファイル数、ファイル名にはコメントが追加されたファイルのパスが表示されます。

まとめ

annotateはいつも使っている便利なgemです。慣れている人なら馴染み深いものではありますが、始めたばかりの人にとっては知らないけど知っているととても便利なものという感じかと思います。ぜひ使ってみてください。

参考URL

RSpecの初期設定

RSpecの初期設定

RailsのプロジェクトでテストフレームワークはRSpecを使っています。

プロジェクト開始時に設定するのですが、あまり設定することもないので、毎回調べて時間がかかってしまいます。なので、備忘録として手順を記載します。

gemの追加

まずgemを追加します。FactoryBotも利用するのでついでに追加します。

1
2
3
4
group :development, :test do
gem "rspec-rails"
gem "factory_bot_rails"
end

bundle installします

1
$ docker compose run --rm web ./bin/bundle install

インストールができました。GemfileとGemfile.lockをcommitしておきます。

RSpecの設定

RSpecの設定ファイル等を生成するgeneratorがあるので実行します。

1
2
3
4
5
$ docker compose run --rm web ./bin/rails g rspec:install
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb

出力のカスタマイズ

出力をドキュメント形式にするために、.rspecに以下の行を追加します。

1
2
3
$ vi .rspec
--require spec_helper
--format documentation

(2行目を追加)

動作確認

ここで正しく動作するか確認します。

1
2
3
4
5
$ docker compose run --rm web bundle exec rspec
No examples found.

Finished in 0.00087 seconds (files took 0.171 seconds to load)
0 examples, 0 failures

このように表示されればOKです。

binstubを作成する

毎回bundle exec rspecとタイプするのは面倒なので、binstubを作成して./bin/rspecとシェルの補完機能も使いながらタイプ数を減らしたいと思います。

1
$ docker compose run --rm web bundle binstubs rspec-core

bin/rspecというファイルが作成されました。

今度はbinstubを利用して実行してみます。

1
2
3
4
5
$ docker compose run --rm web ./bin/rspec 
No examples found.

Finished in 0.0012 seconds (files took 0.15613 seconds to load)
0 examples, 0 failures

同じように実行できました。

RSpecを利用するよう設定

generatorで自動生成するファイルをRSpecのファイルにしたいので、利用するテストフレームワークがRSpecだということをRailsに伝えます。

1
2
3
4
5
6
7
8
9
$ vi config/application.rb
...(省略)
config.generators do |g|
g.test_framework :rspec,
view_specs: false,
helper_specs: false,
routing_specs: false
end
...(省略)

上記のコードを追加することで、generatorがRSpecのファイルを生成してくれます。

まとめ

今後もテストフレームワークはRSpecを利用していくと思うので、導入手順をまとめました。

参考図書にリンクを記載したeveryday-railsですが、著者の伊藤さんが日本語版独自の改訂を続けてくれています。一度購入するとその後の改訂版は無料で受け取ることができます。とてもためになる書籍なので読んでみることを強くおすすめします。

参考図書

docker compose buildでpermission deniedが出た時の対応

docker compose buildでエラー発生

久しぶりにDockerfileを編集したので、イメージをビルドしようと思い、以下のコマンドを実行しました

1
$ docker compose build web

(docker-compose.yml上でwebというサービスが設定されている前提です)

何もなければこのまま終わるはずですが、エラーが発生して実行できてないようです。

1
2
$ docker compose build web
open /User/user/.docker/buildx/current: permission denied

対処法

いろいろ調べたのですが、手軽な対処法がありました。環境変数を設定し、buildxを利用しないようにするというものです。docker compose buildを実行するターミナルで以下のコマンドを実行します。

1
$ export DOCKER_BUILDKIT=0

docker compose buildを試します。

1
2
$ docker compose build web
buildが実行できるログ

まとめ

buildxはDocker Desktopを利用していると自動的に有効になるようです。原因がわからない場合は一旦buildxを無効にするのが簡単な対応方法です。

参考図書

host.docker.internalとは?

ローカル開発環境かどうかの判定

普段Railsを使って開発していると、環境の判定はRails.envで行うことができる。ローカル開発環境ではもちろん

1
2
3
irb(main):001:0> Rails.env
=> "development"
irb(main):002:0>

となる。

また、Rails.env == "development"としなくても

1
2
3
4
irb(main):002:0> Rails.env.development?
=> true
irb(main):003:0> Rails.env.production?
=> false

このように判定してくれるメソッドがある。

ところが、そうでないWebアプリケーションフレームワークもあるようで(ほんとにその機能がないかは定かではない)、ローカル開発環境かどうかの判定に

1
if 'host.docker.internal' in os.environ['S3_URL']:

という条件分岐を利用しているコードに出会った。

察しのいい方はわかると思いますが、S3にアクセスする際に、ローカルで稼働しているMinIOコンテナにアクセスすべきか、S3にアクセスすべきかの条件分岐のif文です。

この条件文が正しく動作せず苦労しました。

host.docker.internalについて

host.docker.internalというドメインが初見だったので調べてみると、Docker Desktop for xxで利用できる、コンテナから参照する場合のhostを指すドメインだということです。わたしの環境はDocker Desktop for Macを利用しているので利用できるはずです。

参考: コンテナからホスト上のサービスに対して接続したい

なるほど、であれば、コンテナからアクセスする際にhost.docker.internal:9000にアクセスすれば、コンテナからMinIOへアクセスできるはずですね。

host.docker.internalを利用する

先程のコードのようにS3_URLをhttp://host.docker.internal:9000とし、アプリケーションからファイルをPUTしてみました。

すると、正しくファイルをPUTできました。

host.docker.internalにアクセスできない場合

いろいろ調べてみるとhost.docker.internalというドメインにアクセスできないケースがあるようです。

その場合の対処法としては、docker-compose.ymlにextra_hostsの設定を行います。

extra_hosts

extra_hostsの説明は以下にありました。

https://docs.docker.jp/compose/compose-file/#extra-hosts

/etc/hostsに対してホストのマッピングを追加することができるようです。

対処法の例としては、hosts.docker.internalにアクセスするコンテナに対して

1
2
extra_hosts:
- "host.docker.internal:host-gateway"

を追加してくださいとのことです。host-gatewayはホストのアドレスになるようですね。

/etc/hostsの確認

extra_hostsを設定した状態でコンテナの/etc/hostsファイルを確認します

1
2
3
4
5
6
7
8
9
10
$ docker compose run --rm app /bin/bash
bash-4.2# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
192.168.65.2 host.docker.internal
172.18.0.6 af51d5a15783

host.docker.internalのレコードが追加されていました。

まとめ

コンテナからホストにアクセスする際にはhost.docker.internalというドメインでアクセスできる。

host.docker.internalでアクセスできない場合は、extra_hostsを利用して明示的に/etc/hostsファイルにレコードを追加する。

参考図書

committee-railsを使ってスキーマ駆動開発を始める

フロントエンドエンジニアとの連携

わたしはRailsでバックエンドAPIを実装しているのですが、開発当初、フロントエンドエンジニアから以下のような提案がありました。

「OpenAPIでスキーマを記述してお互いコンセンサスを取ってから実装しませんか?」

スキーマ駆動開発のことを言っているんだなと思い、

「いいと思うよ、それでいこう」

と答えました。答えたものの、やり方がわかっていないので、調べたことを記載していきます。

committee-railsを使う

“Rails スキーマ駆動開発”と調べると、いろんな例が出てきます。大体の事例がcommittee-railsを使っているので、これが良さそうです。早速インストールします。(テストのときにしか使わないのでtest groupに追加します)

1
2
3
4
5
6
7
8
9
10
$ git diff Gemfile
diff --git a/api/Gemfile b/api/Gemfile
--- a/api/Gemfile
+++ b/api/Gemfile
@@ -76,6 +76,7 @@ group :test do
gem "capybara"
gem "selenium-webdriver"
gem "webdrivers"
+ gem "committee-rails"
end
1
2
3
4
5
6
7
8
9
10
11
$ docker-compose run --rm api ./bin/bundle install
Using devise-i18n 1.10.2
Using devise_token_auth 1.2.1
Installing openapi_parser 0.15.0
Installing json_schema 0.21.0
Fetching committee 4.4.0
Installing committee 4.4.0
Fetching committee-rails 0.6.1
Installing committee-rails 0.6.1
Bundle complete! 29 Gemfile dependencies, 114 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

インストールが完了しました。

committee-railsの設定

公式のGitHubにも記載がありますが、spec/rails_helper.rbにcommittee-rails用の設定を追記します。
この設定を行うことで、assert_response_schema_confirmメソッドなどが利用できるようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git diff spec/rails_helper.rb
diff --git a/api/spec/rails_helper.rb b/api/spec/rails_helper.rb
--- a/api/spec/rails_helper.rb
+++ b/api/spec/rails_helper.rb
@@ -60,4 +60,12 @@ RSpec.configure do |config|
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
+
+ config.include Committee::Rails::Test::Methods
+ config.add_setting :committee_options
+ config.committee_options = {
+ schema_path: Rails.root.join("open_api.yaml").to_s,
+ }

specの追加

committee-railsが正しく動作するかどうか確認するための簡単なspecを用意します。devise_token_authでメールアドレス登録のAPIをテストします。(OpenAPIのスキーマは記載済です)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require "rails_helper"

RSpec.describe "API::Auth::RegistrationsController", type: :request do
describe "POST /api/v1/auth" do
it "returns 200" do
post "/api/v1/auth", params: { email: "example@example.com" }
assert_response_schema_confirm(200)
end

it "returns 422" do
post "/api/v1/auth", params: { password: "password" }
assert_response_schema_confirm(422)
end
end
end

specの実行

では実行します。

1
2
3
4
5
6
7
8
9
10
$ docker-compose run --rm api ./bin/rspec spec/api/v1/auth/registrations_controller_spec.rb

(省略)

Failures:
1) API::Auth::RegistrationsController POST /api/v1/auth return 200
Failure/Error: <p><%= link_to t('.confirm_account_link'), confirmation_url(@resource, {confirmation_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url']}).html_safe %></p>

ActionView::Template::Error:
Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true

エラーが出てしまいました。どうやらメールのドメインが未設定だったようで、以下のように設定しました。
config/environments/test.rb

1
+  config.action_mailer.default_url_options = { host: 'localhost:3000'}

再度実行します。

1
2
3
4
5
6
7
8
$ docker-compose run --rm api ./bin/rspec spec/api/v1/auth/registrations_controller_spec.rb

(省略)

API::Auth::RegistrationsController
POST /api/v1/auth
[DEPRECATION] Committee: please set query_hash_key = rack.request.query_hash because we'll change default value in next major version.
[DEPRECATION] Committee: please set parse_response_by_content_type = false because we'll change default value in next major version.

正常に実行はできましたが、DEPRECATIONの警告が出ています。
公式サイトをよく見てみると、https://github.com/willnet/committee-rails#normal-usage にちゃんと書かれています

1
2
3
4
5
6
7
def committee_options
@committee_options ||= {
schema_path: Rails.root.join('schema', 'schema.json').to_s,
query_hash_key: 'rack.request.query_hash',
parse_response_by_content_type: false,
}
end

query_hash_keyparse_response_by_content_typeの設定を追加します。

1
2
3
4
5
6
7
+  config.include Committee::Rails::Test::Methods
+ config.add_setting :committee_options
+ config.committee_options = {
+ schema_path: Rails.root.join("open_api.yaml").to_s,
+ query_hash_key: "rack.request.query_hash",
+ parse_response_by_content_type: false,
+ }

再度実行します。

1
2
3
4
5
6
7
8
9
10
11
$ docker-compose run --rm api ./bin/rspec spec/api/v1/auth/registrations_controller_spec.rb

(省略)

API::Auth::RegistrationsController
POST /api/v1/auth
returns 200
returns 422

Finished in 0.5116 seconds (files took 9.9 seconds to load)
2 examples, 0 failures

無事実行できました。

おまけ

OpenAPIのschemaでresponse200と422の場合を記述していますが、422のときには値が設定されない場合があります。その場合はそのパラメータにnullableを設定しないといけないのですが、nullableかどうかだけの違いのためにschemaを全て書く必要があり、大変だと思いました。allOfでoverrideできればいいのですが、できないようで、現実的には変更のある箇所のみを別で定義して、allOfで組み合わせるのが良さそうです。

まとめ

今回はcommittee-railsを使ってスキーマ駆動開発の環境を準備し、動作確認用のサンプルを実行するとこまで行いました。

今後スキーマ駆動開発を進めていくとより難しい問題に直面しそうな気がします。その時にはまた記載したいと思います。

参考図書

ローカル開発環境でメールを送信する

ローカルでメール送信がしたい

Djangoでメール送信機能を実装するために、ローカルでテストする方法を調べました。

Railsだとletter_opener_webがあるので楽ですが、Djangoではどうなのでしょうか?

MailCatcherを利用

Djangoではletter_opener_webのようなパッケージは見つかりませんでした。

どうしようかなと考えていたところ、以前、別のシステムのステージング環境でMailCatcherを利用していたことを思い出しました。

MailCatcher用のコンテナを立てて、そのコンテナをSMTPサーバとして指定することで簡単にメール送信環境が構築できると思います。

MailCatcherの設定

ではMailCatcherを利用するための設定を行っていきます。

MailCatcherコンテナイメージ

DockerHubで検索してみるとMailCatcherのコンテナイメージがありました。

検索結果の一番上に出てきたこちらを利用しようと思います。

docker-compose.ymlの設定

ローカルでコンテナを起動するようdocker-compose.ymlを設定していきます。

MailCatcherコンテナの設定

設定自体はとてもシンプルです。現在はwebとdbのコンテナを起動するようにしていますが、そのならびにsmtpというホスト名でコンテナを追加するだけです。

1
2
3
4
5
6
7
8
9
10
services:
web:
(省略)
db:
(省略)
smtp:
image: schickling/mailcatcher
ports:
- "1080:1080"
- "1025:1025"

設定したこととしては、imageの指定と、ポートの指定のみです。1080はメールを確認する際のWebインターフェースのポート番号、1025はSMTPで利用するポート番号になっています。

webコンテナの設定

webコンテナでは、SMTPのエンドポイントとポート番号を指定する必要があります。

STMPのエンドポイントはsmtp、ポート番号は1025になります。

1
2
3
4
web:
environment:
EMAIL_HOST: smtp
EMAIL_PORT: 1025

これでコンテナの設定は完了です。

Djangoの設定

Djangoでメール送信設定を行います。

Djangoのメール送信設定については、SendGridのページがよくまとまっていたので参考にしました。

settings.pyに以下を追記します。

1
2
3
4
5
EMAIL_HOST = os.getenv('EMAIL_HOST', '')
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', '')
EMAIL_PORT = os.getenv('EMAIL_PORT', 587)
EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS', False)

これでMailCatcherを利用するための設定は完了です。

メールの送信テスト

ではメール送信ができるかどうかテストを行います。

コンテナの起動

docker-compose upでsmtpコンテナを起動しておきます。

1
$ docker-compose up

次に実際にメールを送信します。shell_plusを利用して、メール送信関数を実行します。

1
2
3
4
$ docker-compose run --rm web python manage.py shell_plus
>>> from django.core.mail import send_mail
>>> send_mail('subject', 'message', 'mail_from@example.com', ['mail_to@example.com'])
1

1がかえってきました。ドキュメントをみると、

1
The return value will be the number of successfully delivered messages (which can be 0 or 1 since it can only send one message).

と書かれていて、配送できたメールの数が戻り値になっているようです。今回は一通しか送っていないため、1がかえってきています。受信者が増えても配送できたメッセージの数なので、1メッセージであれば1が返ってきます。

WebUIでメールの確認

無事メールが配信されているかを確認します。 http://localhost:1080 にアクセスします。

メールが配送できていることを確認できました。

まとめ

Djangoのローカル開発環境でメール送信とその確認を行う方法を記載しました。

コンテナのおかげでローカル開発環境は簡単に拡張できて本当にありがたいです。

次回はメール本文の記述方法について記載したいと思います。

参考図書

Djangoで特定のドメインで発生したエラーについて

特定のドメインでの起動でエラー発生

ローカルでの開発もある程度終わったので、特定のドメインで稼働するようDjangoアプリケーションをデプロイしました。

デプロイ後、そのドメインでアクセスを試みるといくつかエラーが発生したのでまとめておきます。

Disallowed Hostエラー

ブラウザにドメインのURLを入力してアクセスすると、Disallowed Host Errorが発生しました。

エラーメッセージをよくみると、このドメインをALLOWED_HOSTSに追加してくれと書かれています。

よって、settings.pyに以下の記述を行いました。

1
ALLOWED_HOSTS = ['特定のドメイン', 'localhost', '127.0.0.1']

再度デプロイを行うと、無事エラーが解消されていることが確認できました。

CSRF検証エラー

次に、admin管理画面にログインするために、管理画面ログインページにアクセスしました。

IDとパスワードを入力し、ログインボタンを押下すると、CSRF検証エラーと表示され、ドメイン名 does not match any trusted origins.となってしまいます。

原因調査

先程のエラーメッセージで検索すると、それっぽいものがひっかかりました。

https://stackoverflow.com/questions/70285834/forbidden-403-csrf-verification-failed-request-aborted-reason-given-for-fail

この記事にあるように、Django4.0からこのエラーは起こるようになったようです。

対応

対応方法としては、settings.pyにCSRF_TRUSTED_ORIGINSの設定をすれば良さそうです。早速行います。

1
CSRF_TRUSTED_ORIGINS = ['https://特定のドメイン']

再度デプロイしアクセスすると、無事admin管理画面でログインすることができました。

まとめ

Djangoのローカル開発環境以外で動作させるときのエラーについて記載しました。

まだまだ経験が少ないため、プロダクション環境やステージング環境での動作時に予期せぬことが起こるかもしれません。

環境ごとでの設定ファイル(settings.py)の指定も行う必要が出てきます。その辺りも対応していければと思います。

参考図書