try/except(try/catch)の処理順序を理解する
try/exceptの処理の順序がよくわかっていないことがわかったので、自分の理解のために書きます。
今回は、Djangoでユーザー登録する機能を例にします。
パスワードが以下の条件を満たしていなかったらエラーとなり、
8文字以上
半角英字と半角数字の両方から成り立っている
を満たしていたら、データベースにユーザーの情報が登録されて、ログインした状態でブログの投稿画面に移動します。
urls.pyで URLを指定します。 今回は、必要な分だけ設定します。
urls.py
from django.contrib import admin from django.urls import path import myApp.views urlpatterns = [ path("post_new_post", myApp.views.post_new_post), path('admin/', admin.site.urls), path("registration", myApp.views.registation_user) ] # post_new_postは、ブログ投稿用のURL # adminは、アプリ管理者しか入れない、管理用のURL # registrationは、ユーザー登録用の URL
forms.py
from django import forms class RegistrationForm(forms.Form): # 登録画面で入力するフォームのフィールドを定義します。 username = forms.CharField() password = forms.CharField() email = forms.EmailField()
exceptions.pyでは、例外の設定をします。
exceptions.py
class MyAppError(Exception): # Exceptionクラスを継承するエラークラスを定義します。 pass class RegistrationError(MyAppError): # Exceptionクラスを継承するエラークラスを引数とします。 def __init__(self, form): # Registration classのインスタンスを生成するために、__init__メソッドを定義します。例外クラスのインスタンスに、フォームを引数に持たせます。 self.form = form # エラーになったフォームを例外に持たせて、エラーの詳細としています。
domain.pyでは、アプリのコアとなる処理を設定します。
domain.py
import myApp.exceptions import uuid import re import myApp.forms from django.contrib.auth.models import User def has_digit(text): # 文字列に数字を含むかどうかを判定する関数です。 if re.search("\d", text): return True return False def has_alphabet(text): # 文字列に英字(小文字または大文字)を含むかどうかを判定する関数です。 if re.search("[a-zA-Z]", text): return True return False def register(user_profile): # ユーザー情報を登録する関数です。 registration_form = myApp.forms.RegistrationForm(user_profile) password = registration_form.password if len(password) < 8: registration_form.add_error('password', "文字数が8文字未満です。") # まず、エラーがあるか判定します。文字列が8文字未満なら、「文字数が8文字未満です。」というエラーをフォームに追加します。 if not has_digit(password): registration_form.add_error('password',"数字が含まれていません") # has_digitがfalseなら、「数字が含まれていません」というエラーをフォームに追加します。 if not has_alphabet(password): # has_alphabetがfalseなら、「アルファベットが含まれていません」というエラーをフォームに追加します。 registration_form.add_error('password',"アルファベットが含まれていません") if registration_form.has_error('password'): # パスワードがエラーの条件に合致するか判定し、エラーがある場合フォームに追加した後、フォームがエラーを持っているか判定します。 raise myApp.exceptions.RegistrationError(registration_form) # エラーを持っている場合、RegistrationErrorを発生させます。RegistrationErrorのコンストラクタの引数は、registration_formとします。 user = User.objects.create_user( username=registration_form.username, password=password, email=registration_form.email) # エラーを持っていないと判定された場合、まずデータベースにユーザー情報を登録します。 return user # 登録した後、登録した内容を代入された変数userを返します。
views.pyでは、http通信する際の処理を設定します。
views.py
import django.http from django.shortcuts import render import re from django.contrib.auth import authenticate, login as django_login import myApp.exceptions import myApp.domain def get_registration_user(request):# URL'registration'でgetメソッドが発生した時に利用する関数 registration_form = minsta.forms.RegistrationForm() return render(request, 'registration.html', {'registration_form': registration_form}) def post_registration_user(request): # URL'registration'でpostメソッドが発生した時に利用する関数 try: user = myApp.domain.register(request.POST) #まず、tryでdomain.pyで設定したregister関数を実行します。引数user_profileには、HTMLフォームで送信された内容(request.POST)を入れます。 except myApp.exceptions.RegistrationError as e: # try文でRegistrationErrorという例外が発生した場合 return render(request, 'registration.html', {'registration_form': e.registration_form, 'user':request.user}) # registration.htmlに移動します。RegistrationErrorのインスタンスに含まれているフォームを利用します。 django_login(request, user) # 例外が発生せずユーザー情報が登録されたら、userにはユーザー情報が含まれます。それを利用してlogin関数でログインします。 return django.http.HttpResponseRedirect('/post_new_post') # 例外が発生しなかった場合、ログインした後に、'post_new_post'に移動します。 def registration_user(request): if request.method == 'POST': #リクエストメソッドにPOSTが利用されたら、post_registration_userの結果を返します。 return post_registration_user(request) return get_registration_user(request) #リクエストメソッドにGEETが利用されたら、get_registration_userの結果を返します。 def post_new_post(request): # post_new_postが呼び出された時の関数です。 return render(request, 'new_post.html', {'form': form, 'user':request.user}) # 今回は、投稿画面に移動するだけとします。
try/exceptを利用しているメソッドを見てみます。
# views.pyの一部抜粋 def post_registration_user(request): try: user = myApp.domain.register(request.POST) # この処理が1番目に実行する処理。 except myApp.exceptions.RegistrationError as e: # 「except 例外」に、tryの実行結果で「except 例外」で指定した例外が発生されている場合の処理を書きます。例外が発生されているかどうかを判定するのが2番目の処理。 return render(request, 'registration.html', {'registration_form': e.registration_form, 'user':request.user}) # tryの実行結果にRegistrationErrorが発生していたら、これが3番目の処理。 django_login(request, user) # 例外が発生していなかったら、これが3番目の処理。domain.pyですでに例外が発生していなかったらユーザー情報をデータベースに登録する処理をするよう設定しているので、views.pyで設定する必要はありません。 return django.http.HttpResponseRedirect('/post_new_post') # 例外が発生しなかった場合、これが4番目の処理
ーーー勘違いしていたことーーー
最初、try節の文はif文のように条件を示すだけで実行していないかと思っていました。
if文とは違って、try文は実行されているのですが・・・
try文の中にある、
user = ....
は、ただ単にuserという変数を代入しているだけで
評価していないかと思っていました。
しかし、実際は
except以下で、まずエラーをキャッチするか判断し
エラーがなければ
user = ...
でユーザーがデータベースに登録され、かつ代入もされる動きでした。
これからは、
tryは実行されるということと、
exceptと例外が発生しなかった時の処理に関して
上から判定・処理されることを意識してプログラムを書きます。