mizzsugar’s blog

Pythonで学んだことや読書録を書きます。

【Django】すべてをviews.pyのみに書いていたのをdomain.pyを作ってリファクタリングした

DB関連の処理も、計算関連の処理のための関数も、全てviewsに書いていましたが、

viewsに全てを書くのをやめました!

という話です。

動くからいいやん!と思っていましたが、viewsにはviewsの役割があり、上記の処理はviewsでやることではないとか。

半年ほど前に下書きのまま放置していたのをようやく投稿(^^;

viewsやmodeslなど、それぞれどんな役割か?

ざっくり、forms・models・viewsの役割はこんな感じ。


  • forms

フォームの入力項目やバリデーションを管理します。


  • models

データベースに格納するデータを管理します。

テーブルとかカラムとか、データベース設計はmodels.pyを見れば把握できます。


  • views

リクエストやリスポンスにまつわる処理を管理します。

送られてきたリクエストをもとに、どのような内容を表示させるかを決定をしています。

CodeBeforeAfter

ユーザー登録機能を例にあげます。

過去記事のです(過去の未熟さを積極的にさらすスタイルでいきます笑)

mizzsugar.hatenablog.com


まず、forms、models、viewsだけのコードがこちら↓

forms.py

from django import forms

from django import forms

class RegistrationForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField()
    email = forms.EmailField()


models.pyは、ユーザー登録に関してはUserクラスをインポートすればmodelsに書かなくても良いので割愛。


views.py

import django.http
import myApp.forms
from django.shortcuts import render
import uuid
from django.contrib.auth.models import User
import re

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 registation_user(request):
    if request.method == 'POST':
        registration_form = myApp.forms.RegistrationForm(request.POST)
        password = request.POST['password']
        if len(password) < 8:
            registration_form.add_error('password', "文字数が8文字未満です。")
        if not has_digit(password):
            registration_form.add_error('password',"数字が含まれていません")
        if not has_alphabet(password):
            registration_form.add_error('password',"アルファベットが含まれていません")
        if registration_form.has_error('password'):
            return render(request, 'registration.html', {'registration_form': registration_form})
        user = User.objects.create_user(username=request.POST['username'], password=password, email=request.POST['email'])
        return django.http.HttpResponseRedirect('/login')
    else:
        registration_form = myApp.forms.RegistrationForm()
    return render(request, 'registration.html', {'registration_form': registration_form})


そのままでも動きますが、

viewsに登録や判定など

表示に関係ない部分もまとめて書いています。

コードが長くなると、どこでどの処理を行っているのかを把握するのが困難になります。

また、viewsが長すぎて悪い部分を修正するのが大変になります。


そこで、domains.pyを作成し、

viewにはリクエストをもとに表示内容を決める処理を書き、

domainsは計算などのそのシステムの中核となる処理を書くようにします。


役割を分けて書いた結果(viewsとdomains以外は変わっていないのでforms.pyは割愛)↓

domains.py

def post_new_post(author: User, post: Dict[str, Any], file: MultiValueDict) -> None:
    if not author.is_authenticated:
        raise minsta.exceptions.LoginRequiredError()

    form = minsta.forms.NewPostForm(post, file)
    if not form.is_valid():
        raise minsta.exceptions.ValidationError(form)
    
    file_path = handle_uploaded_file(form.cleaned_data['file'])
    print(type(file))
    minsta.models.Post.create(author, file_path, form.cleaned_data['comment'])

def handle_uploaded_file(f):
    file_path = "uploads/{}.jpeg".format(uuid.uuid4().hex)
    with open('minsta/static/{}'.format(file_path), 'wb') as destination:
        #  'minsta/static/uploads/{}.jpeg'.format(uuid.uuid4().hex) .format()で{}の中を()の中に置き換えてくれる
        #  uuidとは、ランダムでユニークなIDの規格である。uuid.uuid4().hexの.hexで文字列にする。
        for chunk in f.chunks():
            destination.write(chunk)
    return file_path


views.py

import django.http
import minsta.models
import minsta.forms
from django.shortcuts import render
from django.contrib.auth.models import User
import minsta.domain


def get_post_new_post(request):
    try:
        minsta.domain.check_authenticated(request.user)
    except minsta.exceptions.LoginRequiredError:
        return django.http.HttpResponseRedirect('/login')
    form = minsta.forms.NewPostForm()
    return render(request, 'new_post.html', {'form': form, 'user':request.user})

def post_post_new_post(request):
    try:
        minsta.domain.post_new_post(request.user, request.POST, request.FILES)
    except minsta.exceptions.LoginRequiredError:
        return django.http.HttpResponseRedirect('/login')
    except minsta.exceptions.ValidationError as e:
        return render(request, 'new_post.html', {'form': e.form, 'user':request.user})
    return django.http.HttpResponseRedirect('/list')

def post_new_post(request):
    if request.method == 'POST':
        return post_post_new_post(request)
    return get_post_new_post(request)


viewsとdomainsを分けると、

修正が必要になった際に

viewsとdomainsのどちらが悪いのかが分かり

悪い方だけ修正すれば良くなるので

修正範囲が小さくなり、修正がより楽になるのでおすすめです。