Python | Django | モデルフォームの使い方

2021年7月12日

公開日:2021/7/12

Pythonには,DjangoというWebアプリケーションフレームワークがある.フレームワークのため,Djangoを利用するとWebアプリを通常よりも短時間で開発することが可能になる.

前記事にて,「フォームにおけるバリデーションの使い方」を記した.前記事での設定をそのまま引き継いだ上で,本記事では,「モデルフォームの使い方」を以下4つの構成にて記す.

  1. モデルフォームの基本実装
  2. DB(データベース)へのデータ追加処理
  3. ページのフォーマットを変更
    (1) “fields"を利用
    (2) “exclude"を利用
    (3) テキストボックスからテキストエリアへの変更
  4. Saveメソッドの利用
    (1) 入力内容を変更(大文字から小文字に変更)
    (2) 様々なFormに共通利用できるログ出力の実装

◆実施環境

Python 3.8.8
Django 3.2.3

■モデルフォーム(ModelForm)の使い方

  1. モデルフォームの基本実装

“form_app/models.py"に追記し,以下のように編集する.

from django.db import models 

class Bird(models.Model): # 以下追記箇所 
  birdname = models.CharField(max_length=50) 
  title = models.CharField(max_length=100) 
  review = models.CharField(max_length=300) 
  author = models.CharField(max_length=50)

“form_app/forms.py"に追記し,以下のように編集する.

from django import forms
from django.core import validators

from .models import Bird # 追記箇所

class ChocolateInfo(forms.Form):
  chocolate_name = forms.CharField(label='チョコレート名', max_length=50)
  chocolate_maker = forms.CharField(label='チョコレートメーカー', max_length=50)
  mail = forms.EmailField(label='メールアドレス')
  confirmed_mail = forms.EmailField(label='メールアドレス再入力')
  is_available_for_sale = forms.BooleanField(label='販売中')
  birthday = forms.DateField(required=False, label='開発日')
  price = forms.DecimalField(initial=300, max_value=1000, label='価格',validators=[validators.MinValueValidator(1, message='1以上にしてください')])
  flavor = forms.ChoiceField(choices=(
    (1, 'Original Chocolat'),
    (2, 'Double Chocolat'),
    (3, 'Original White'),
    (4, 'Rich Matcha'),
  ), widget=forms.RadioSelect, label='味')
  maker_factory = forms.MultipleChoiceField(choices=(
    (1, 'Ibaraki Moriya-city'),
    (2, 'Saitama Sakado-city'),
    (3, 'Aichi Inazawa-city'),
    (4, 'Osaka Takatsuki-city'),
  ), widget=forms.CheckboxSelectMultiple, label='メーカー工場')
  homepage = forms.URLField(
    label='ホームページ',
    widget=forms.TextInput(attrs={'class':'url_class','placeholder':'https://www.shelokuma.com'})
  )

  def __init__(self,*args,**kwargs):
    super(ChocolateInfo, self).__init__(*args, **kwargs)
    self.fields['flavor'].widget.attrs['id'] = 'id_flavor'
    self.fields['maker_factory'].widget.attrs['class'] = 'maker_factory_class'

  def clean_chocolatename(self):
    chocolate_name = self.cleaned_data['chocolate_name']
    if not chocolate_name.startswith('F'):
      raise forms.ValidationError('名前は「F」から始めてください')

  def clean(self):
    cleaned_data = super().clean()
    mail = cleaned_data['mail']
    confirmed_mail = cleaned_data['confirmed_mail']
    if mail != confirmed_mail:
      raise forms.ValidationError('メールアドレスが一致しません')

class BirdModelForm(forms.ModelForm): # 以下追記箇所

  class Meta:
    model = Bird
    fields = '__all__'

“form_app/views.py"に追記し,以下のように編集する.

from django.shortcuts import render 

from . import forms 

def index(request): 
  return render(request, 'form_temp/index.html') 

def form_page(request): 
  form = forms.ChocolateInfo() 
  
  if request.method == 'POST': 
    form = forms.ChocolateInfo(request.POST) 
    if form.is_valid(): 
      print('submission is successful') 
      print(form.cleaned_data) 
    else: 
      print('submission is failed') 
      
    return render( request,'form_temp/form_page.html', 
                  context={ 'form':form } ) 
    
def form_bird(request): #以下追記箇所 
  form = forms.BirdModelForm() 
  return render( request, 'form_temp/form_bird.html', 
                context={'form':form} )

“form_app/urls.py"に追記し,以下のように編集する.

from django. urls import path
from . import views

app_name =
'form_application'

urlpatterns = [
  path('', views.index, name='index'),
  path('form_page/', views.form_page, name='form_page'),
  path('form_bird/', views.form_bird, name='form_bird'), # 追記箇所
]

“form_project/templates/form_temp"に以下のように"form_bird.html"を作成する.

“form_project/templates/form_temp/form_bird.html"を以下のように編集する.

<!DOCTYPE html>
<html> 
<head> 
<meta charset="utf-8">
<title>Form</title> 
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body> 
<form method="POST">
{% csrf_token %}
<table>
{{form.as_table}}
</table>
<input type="submit" value="submit">
</form>
</body> 
</html>

ターミナルを開き,”conda activate 仮想環境名”を実行し,仮想環境に移行する(移行方法の詳細はこちら)."cd form_project"を実行することによって,”form_project”のディレクトリに移動する.その後,”python manage.py makemigrations form_app”を実行する.以下が出力される.

(djangoenv) C:\Users\shiro\Desktop\210517_python development\myformtest\form_project
>python manage.py makemigrations form_app

Migrations for 'form_app': # 以下出力箇所
  form_app\migrations\0001_initial.py
    - Create model Bird

引き続き,"python manage.py migrate"を実行する.以下が出力される.

(djangoenv) C:\Users\shiro\Desktop\210517_python development\myformtest\form_project
>python manage.py migrate

Operations to perform: # 以下出力箇所
  Apply all migrations: admin, auth, contenttypes, form_app, sessions
Running migrations:
  Applying form_app.0001_initial... OK

ターミナルで"python manage.py runserver"を実行する.以下が出力される.

(djangoenv) C:\Users\shiro\Desktop\210517_python development\myformtest\form_project
>python manage.py runserver

System check identified no issues (0 silenced). # 以下出力箇所
July 11, 2021 - 15:39:59
Django version 3.2.3, using settings 'form_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

上記の"http://127.0.0.1:8000″をクリックする.以下ページに遷移する.

ブラウザのURLに"http://127.0.0.1:8000/form_application/form_bird"を入力すると,以下ページに遷移する.

ページの項目に内容を記述し,"submit"をクリックする.

エラーがなく,ターミナルに以下が出力されると成功となる.

[11/Jul/2021 17:18:39] 
"POST /form_application/form_bird/ HTTP/1.1" 200 925
  1. DB(データベース)へのデータ追加処理

“form_app/views.py"に追記し,以下のように編集する.

from django.shortcuts import render

from . import forms

def index(request):
  return render(request,'form_temp/index.html')

def form_page(request):
  form = forms.ChocolateInfo()
  
  if request.method =='POST':
    form = forms.ChocolateInfo(request.POST)
    if form.is_valid():
      print('submission is successful')
      print(form.cleaned_data)
    else:
      print('submission is failed')
      
  return render(
    request,'form_temp/form_page.html', 
    context={'form':form})

def form_bird(request):
  form = forms.BirdModelForm()
  if request.method == 'POST': # 追記箇所(27~30行目)
    form = forms.BirdModelForm(request.POST)
    if form.is_valid():
      form.save()
  return render(
    request, 'form_temp/form_bird.html', context={'form':form}
  )

上記を保存し,ブラウザのURLに"http://127.0.0.1:8000/form_application/form_bird"を入力し,以下ページの項目に内容を記述し,"submit"をクリックする.

エラーがなく,ターミナルに以下が出力される.

[11/Jul/2021 17:36:14] 
"POST /form_application/form_bird/ HTTP/1.1" 200 925

SQLiteをインストールし(詳細はこちら参照),"SQLITE EXPLORER"を開くと,"form_app_bird"を以下赤枠にて確認できる.右クリックをし,"Show Table"をクリックする.

以下のように入力データを確認できる.

  1. ページのフォーマットを変更

(1) “fields"を利用

“form_app/forms.py"を以下のように変更する.

from django import forms
from django.core import validators

from .models import Bird # 追記箇所

class ChocolateInfo(forms.Form):
  chocolate_name = forms.CharField(label='チョコレート名', max_length=50)
  chocolate_maker = forms.CharField(label='チョコレートメーカー', max_length=50)
  mail = forms.EmailField(label='メールアドレス')
  confirmed_mail = forms.EmailField(label='メールアドレス再入力')
  is_available_for_sale = forms.BooleanField(label='販売中')
  birthday = forms.DateField(required=False, label='開発日')
  price = forms.DecimalField(initial=300, max_value=1000, label='価格',validators=[validators.MinValueValidator(1, message='1以上にしてください')])
  flavor = forms.ChoiceField(choices=(
    (1, 'Original Chocolat'),
    (2, 'Double Chocolat'),
    (3, 'Original White'),
    (4, 'Rich Matcha'),
  ), widget=forms.RadioSelect, label='味')
  maker_factory = forms.MultipleChoiceField(choices=(
    (1, 'Ibaraki Moriya-city'),
    (2, 'Saitama Sakado-city'),
    (3, 'Aichi Inazawa-city'),
    (4, 'Osaka Takatsuki-city'),
  ), widget=forms.CheckboxSelectMultiple, label='メーカー工場')
  homepage = forms.URLField(
    label='ホームページ',
    widget=forms.TextInput(attrs={'class':'url_class','placeholder':'https://www.shelokuma.com'})
  )

  def __init__(self,*args,**kwargs):
    super(ChocolateInfo, self).__init__(*args, **kwargs)
    self.fields['flavor'].widget.attrs['id'] = 'id_flavor'
    self.fields['maker_factory'].widget.attrs['class'] = 'maker_factory_class'

  def clean_chocolatename(self):
    chocolate_name = self.cleaned_data['chocolate_name']
    if not chocolate_name.startswith('F'):
      raise forms.ValidationError('名前は「F」から始めてください')

  def clean(self):
    cleaned_data = super().clean()
    mail = cleaned_data['mail']
    confirmed_mail = cleaned_data['confirmed_mail']
    if mail != confirmed_mail:
      raise forms.ValidationError('メールアドレスが一致しません')

class BirdModelForm(forms.ModelForm):

  class Meta:
    model = Bird
    fields = ['birdname','title','review'] # 変更箇所

上記を保存し,ブラウザのURLに"http://127.0.0.1:8000/form_application/form_bird"を入力すると,以下ページに遷移する.項目は,4項目から3項目に変更した.

(2) “exclude"を利用

“form_app/forms.py"を以下のように変更する.

from django import forms
from django.core import validators

from .models import Bird # 追記箇所

class ChocolateInfo(forms.Form):
  chocolate_name = forms.CharField(label='チョコレート名', max_length=50)
  chocolate_maker = forms.CharField(label='チョコレートメーカー', max_length=50)
  mail = forms.EmailField(label='メールアドレス')
  confirmed_mail = forms.EmailField(label='メールアドレス再入力')
  is_available_for_sale = forms.BooleanField(label='販売中')
  birthday = forms.DateField(required=False, label='開発日')
  price = forms.DecimalField(initial=300, max_value=1000, label='価格',validators=[validators.MinValueValidator(1, message='1以上にしてください')])
  flavor = forms.ChoiceField(choices=(
    (1, 'Original Chocolat'),
    (2, 'Double Chocolat'),
    (3, 'Original White'),
    (4, 'Rich Matcha'),
  ), widget=forms.RadioSelect, label='味')
  maker_factory = forms.MultipleChoiceField(choices=(
    (1, 'Ibaraki Moriya-city'),
    (2, 'Saitama Sakado-city'),
    (3, 'Aichi Inazawa-city'),
    (4, 'Osaka Takatsuki-city'),
  ), widget=forms.CheckboxSelectMultiple, label='メーカー工場')
  homepage = forms.URLField(
    label='ホームページ',
    widget=forms.TextInput(attrs={'class':'url_class','placeholder':'https://www.shelokuma.com'})
  )

  def __init__(self,*args,**kwargs):
    super(ChocolateInfo, self).__init__(*args, **kwargs)
    self.fields['flavor'].widget.attrs['id'] = 'id_flavor'
    self.fields['maker_factory'].widget.attrs['class'] = 'maker_factory_class'

  def clean_chocolatename(self):
    chocolate_name = self.cleaned_data['chocolate_name']
    if not chocolate_name.startswith('F'):
      raise forms.ValidationError('名前は「F」から始めてください')

  def clean(self):
    cleaned_data = super().clean()
    mail = cleaned_data['mail']
    confirmed_mail = cleaned_data['confirmed_mail']
    if mail != confirmed_mail:
      raise forms.ValidationError('メールアドレスが一致しません')

class BirdModelForm(forms.ModelForm):

  class Meta:
    model = Bird
    exclude = ['birdname'] # 変更箇所

上記を保存し,ブラウザのURLに"http://127.0.0.1:8000/form_application/form_bird"を入力すると,以下ページに遷移する.項目は,4項目から3項目に変更した.

(3) テキストボックスからテキストエリアへの変更

“form_app/forms.py"を以下のように変更する.

from django import forms
from django.core import validators

from .models import Bird # 追記箇所

class ChocolateInfo(forms.Form):
  chocolate_name = forms.CharField(label='チョコレート名', max_length=50)
  chocolate_maker = forms.CharField(label='チョコレートメーカー', max_length=50)
  mail = forms.EmailField(label='メールアドレス')
  confirmed_mail = forms.EmailField(label='メールアドレス再入力')
  is_available_for_sale = forms.BooleanField(label='販売中')
  birthday = forms.DateField(required=False, label='開発日')
  price = forms.DecimalField(initial=300, max_value=1000, label='価格',validators=[validators.MinValueValidator(1, message='1以上にしてください')])
  flavor = forms.ChoiceField(choices=(
    (1, 'Original Chocolat'),
    (2, 'Double Chocolat'),
    (3, 'Original White'),
    (4, 'Rich Matcha'),
  ), widget=forms.RadioSelect, label='味')
  maker_factory = forms.MultipleChoiceField(choices=(
    (1, 'Ibaraki Moriya-city'),
    (2, 'Saitama Sakado-city'),
    (3, 'Aichi Inazawa-city'),
    (4, 'Osaka Takatsuki-city'),
  ), widget=forms.CheckboxSelectMultiple, label='メーカー工場')
  homepage = forms.URLField(
    label='ホームページ',
    widget=forms.TextInput(attrs={'class':'url_class','placeholder':'https://www.shelokuma.com'})
  )

  def __init__(self,*args,**kwargs):
    super(ChocolateInfo, self).__init__(*args, **kwargs)
    self.fields['flavor'].widget.attrs['id'] = 'id_flavor'
    self.fields['maker_factory'].widget.attrs['class'] = 'maker_factory_class'

  def clean_chocolatename(self):
    chocolate_name = self.cleaned_data['chocolate_name']
    if not chocolate_name.startswith('F'):
      raise forms.ValidationError('名前は「F」から始めてください')

  def clean(self):
    cleaned_data = super().clean()
    mail = cleaned_data['mail']
    confirmed_mail = cleaned_data['confirmed_mail']
    if mail != confirmed_mail:
      raise forms.ValidationError('メールアドレスが一致しません')

class BirdModelForm(forms.ModelForm):

  review = forms.CharField( # 追記箇所(50~55行目)
    widget=forms.Textarea(attrs={
      'rows':15,
      'cols':25,
    })
  )
  class Meta:
    model = Bird
    fields = '__all__'

上記を保存し,ブラウザのURLに"http://127.0.0.1:8000/form_application/form_bird"を入力すると,以下ページに遷移する.項目数は4項目に戻り,項目"Review"の入力エリアは拡大した.

  1. Saveメソッドの利用

(1) 入力内容を変更(大文字から小文字に変更)

“form_app/forms.py"を以下のように変更する.

from django import forms
from django.core import validators

from .models import Bird

class ChocolateInfo(forms.Form):
  chocolate_name = forms.CharField(label='チョコレート名', max_length=50)
  chocolate_maker = forms.CharField(label='チョコレートメーカー', max_length=50)
  mail = forms.EmailField(label='メールアドレス')
  confirmed_mail = forms.EmailField(label='メールアドレス再入力')
  is_available_for_sale = forms.BooleanField(label='販売中')
  birthday = forms.DateField(required=False, label='開発日')
  price = forms.DecimalField(initial=300, max_value=1000, label='価格',validators=[validators.MinValueValidator(1, message='1以上にしてください')])
  flavor = forms.ChoiceField(choices=(
    (1, 'Original Chocolat'),
    (2, 'Double Chocolat'),
    (3, 'Original White'),
    (4, 'Rich Matcha'),
  ), widget=forms.RadioSelect, label='味')
  maker_factory = forms.MultipleChoiceField(choices=(
    (1, 'Ibaraki Moriya-city'),
    (2, 'Saitama Sakado-city'),
    (3, 'Aichi Inazawa-city'),
    (4, 'Osaka Takatsuki-city'),
  ), widget=forms.CheckboxSelectMultiple, label='メーカー工場')
  homepage = forms.URLField(
    label='ホームページ',
    widget=forms.TextInput(attrs={'class':'url_class','placeholder':'https://www.shelokuma.com'})
  )

  def __init__(self,*args,**kwargs):
    super(ChocolateInfo, self).__init__(*args, **kwargs)
    self.fields['flavor'].widget.attrs['id'] = 'id_flavor'
    self.fields['maker_factory'].widget.attrs['class'] = 'maker_factory_class'

  def clean_chocolatename(self):
    chocolate_name = self.cleaned_data['chocolate_name']
    if not chocolate_name.startswith('F'):
      raise forms.ValidationError('名前は「F」から始めてください')

  def clean(self):
    cleaned_data = super().clean()
    mail = cleaned_data['mail']
    confirmed_mail = cleaned_data['confirmed_mail']
    if mail != confirmed_mail:
      raise forms.ValidationError('メールアドレスが一致しません')

class BirdModelForm(forms.ModelForm):

  review = forms.CharField(
    widget=forms.Textarea(attrs={
      'rows':15,
      'cols':25,
    })
  )
  class Meta:
    model = Bird
    fields = '__all__'

  def save(self, *args, **kwargs): # 以下追記箇所
    ipt = super(BirdModelForm, self).save(commit=False, *args, **kwargs)
    ipt.birdname = ipt.birdname.lower()
    print(type(ipt))
    print('save is successful')
    ipt.save()
    return ipt

上記を保存し,ブラウザのURLに"http://127.0.0.1:8000/form_application/form_bird"を入力すると,以下画面に遷移する.内容を記述し,"submit"をクリックする."Birdname"の項目に記載した内容は,大文字で"BUDGERIGAR"である

ターミナルには以下が出力される.

<class 'form_app.models.Bird'>
save is successful
[12/Jul/2021 01:03:37] 
"POST /form_application/form_bird/ HTTP/1.1" 200 1014

上記のように,SQLiteをインストールし,"SQLITE EXPLORER"を開き,"form_app_bird"を右クリックをし,"Show Table"をクリックする.以下赤枠の内容を確認すると,"Birdname"の項目に記載した大文字の内容は,小文字の"budgerigar"になった.

(2) 様々なFormに共通利用できるログ出力の実装

“form_app/forms.py"を以下のように変更する.クラス"BaseForm"を追記し,ログが出力できるようにした.また,"BirdModelForm"が"BaseForm"を継承できるようにした.新たなFormを作成する際,"BaseForm"経由にすることで共通の機能(50行目のprint)を持つことができる.

from django import forms
from django.core import validators

from .models import Bird

class ChocolateInfo(forms.Form):
  chocolate_name = forms.CharField(label='チョコレート名', max_length=50)
  chocolate_maker = forms.CharField(label='チョコレートメーカー', max_length=50)
  mail = forms.EmailField(label='メールアドレス')
  confirmed_mail = forms.EmailField(label='メールアドレス再入力')
  is_available_for_sale = forms.BooleanField(label='販売中')
  birthday = forms.DateField(required=False, label='開発日')
  price = forms.DecimalField(initial=300, max_value=1000, label='価格',validators=[validators.MinValueValidator(1, message='1以上にしてください')])
  flavor = forms.ChoiceField(choices=(
    (1, 'Original Chocolat'),
    (2, 'Double Chocolat'),
    (3, 'Original White'),
    (4, 'Rich Matcha'),
  ), widget=forms.RadioSelect, label='味')
  maker_factory = forms.MultipleChoiceField(choices=(
    (1, 'Ibaraki Moriya-city'),
    (2, 'Saitama Sakado-city'),
    (3, 'Aichi Inazawa-city'),
    (4, 'Osaka Takatsuki-city'),
  ), widget=forms.CheckboxSelectMultiple, label='メーカー工場')
  homepage = forms.URLField(
    label='ホームページ',
    widget=forms.TextInput(attrs={'class':'url_class','placeholder':'https://www.shelokuma.com'})
  )

  def __init__(self,*args,**kwargs):
    super(ChocolateInfo, self).__init__(*args, **kwargs)
    self.fields['flavor'].widget.attrs['id'] = 'id_flavor'
    self.fields['maker_factory'].widget.attrs['class'] = 'maker_factory_class'

  def clean_chocolatename(self):
    chocolate_name = self.cleaned_data['chocolate_name']
    if not chocolate_name.startswith('F'):
      raise forms.ValidationError('名前は「F」から始めてください')

  def clean(self):
    cleaned_data = super().clean()
    mail = cleaned_data['mail']
    confirmed_mail = cleaned_data['confirmed_mail']
    if mail != confirmed_mail:
      raise forms.ValidationError('メールアドレスが一致しません')

class BaseForm(forms.ModelForm): # 追記箇所(48~51行目)
  def save(self, *args, **kwargs):
    print(f'{self.__class__.__name__} is successful')
    return super(BaseForm, self).save(*args,**kwargs)

class BirdModelForm(BaseForm): # 変更箇所

  review = forms.CharField(
    widget=forms.Textarea(attrs={
      'rows':15,
      'cols':25,
    })
  )
  class Meta:
    model = Bird
    fields = '__all__'

  def save(self, *args, **kwargs):
    ipt = super(BirdModelForm, self).save(commit=False, *args, **kwargs)
    ipt.birdname = ipt.birdname.lower()
    print(type(ipt))
    print('save is successful')
    ipt.save()
    return ipt

上記を保存し,ブラウザのURLに"http://127.0.0.1:8000/form_application/form_bird"を入力すると,以下画面に遷移する.内容を記述し,"submit"をクリックする.

ターミナルには以下が出力される.

BirdModelForm is successful
<class 'form_app.models.Bird'>
save is successful
[12/Jul/2021 15:21:28] 
"POST /form_application/form_bird/ HTTP/1.1" 200 1011

以上