Python | Django | モデルフォームにおけるエラー発生方法

2021年7月13日

公開日:2021/7/12

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

前記事にて,「モデルフォームの使い方」を記した.前記事での設定をそのまま引き継いだ上で,本記事では,「モデルフォームにおけるエラー発生方法」を以下3つの構成にて記す.

  1. エラーメッセージを各項目の上部に発生
  2. エラーメッセージを項目の上にまとめて発生
  3. 重複内容を除外する実装

◆実施環境

Python 3.8.8
Django 3.2.3

■モデルフォーム(ModelForm)におけるエラー発生方法

  1. エラーメッセージを各項目の上部に発生

“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 %}

{% if form.birdname.errors %} # 以下追記箇所(13~31行目)
  <div style="color:red;">{{ form.birdname.errors.as_text }}<br></div>
{% endif %}
{{ form.birdname.label }}: {{ form.birdname }}<br>

{% if form.title.errors %}
  <div style="color:red;">{{ form.title.errors.as_text }}<br></div>
{% endif %}
{{ form.title.label }}: {{ form.title }}<br>

{% if form.review.errors %}
  <div style="color:red;">{{ form.review.errors.as_text }}<br></div>
{% endif %}
{{ form.review.label }}: {{ form.review }}<br>

{% if form.author.errors %}
  <div style="color:red;">{{ form.author.errors.as_text }}<br></div>
{% endif %}
{{ form.author.label }}: {{ form.author }}<br>

<input type="submit" value="submit">
</form>
</body> 
</html>

“form_app/forms.py"を以下のように変更する.ブラウザページの各項目に"this is error"と入力するとエラーが出力するように実装した.

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):
  def save(self,*args,**kwargs):
    print(f'{self.__class__.__name__} is successful')
    return super(BaseForm, self).save(*args,**kwargs)

class BirdModelForm(BaseForm):
  birdname = forms.CharField(label='Birdname')
  title = forms.CharField(label='Title')
  review = forms.CharField(
    label='review', widget=forms.Textarea(attrs={
      'rows':15,
      'cols':25,}))
  
  author = forms.CharField(label='Author')

  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

  def clean_birdname(self):
    birdname = self.cleaned_data.get('birdname')
    if birdname == 'this is error':
      raise validators.ValidationError('そのbirdnameはエラーです')
    return birdname

  def clean_title(self):
    title = self.cleaned_data.get('title')
    if title == 'this is error':
      raise validators.ValidationError('そのtitleはエラーです')
    return title

  def clean_review(self):
    review = self.cleaned_data.get('review')
    if review == 'this is error':
      raise validators.ValidationError('そのreviewはエラーです')
    return review

  def clean_author(self):
    author = self.cleaned_data.get('author')
    if author == 'this is error':
      raise validators.ValidationError('そのauthorはエラーです')
    return author

ターミナルを開き,”conda activate 仮想環境名”を実行し,仮想環境に移行する(移行方法の詳細はこちら)."cd form_project"を実行することによって,”form_project”のディレクトリに移動する.その後,”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 13, 2021 - 01:49:28
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"を入力すると,以下ページに遷移する.

各項目でエラーが発生するように"this is error"を記述し,"submit"をクリックする.クリックすると以下のようにエラーが発生した.

  1. エラーメッセージを項目の上にまとめて発生

“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 %}

{% if form.errors %}
  {% for item_label, item_content in form.errors.items %}
    <p><div style="color:red;">{{ item_label }}: {{ item_content.as_text }}</div></p>
  {% endfor %}
{% endif %}

{{ form.birdname.label }}: {{ form.birdname }}<br>

{{ form.title.label }}: {{ form.title }}<br>

{{ form.review.label }}: {{ form.review }}<br>

{{ form.author.label }}: {{ form.author }}<br>

<input type="submit" value="submit">
</form>
</body> 
</html>

上記を保存し,ブラウザのURLに"http://127.0.0.1:8000/form_application/form_bird"を入力する.その後,各項目でエラーが発生するように"this is error"を記述し,"submit"をクリックする.クリックすると以下のようにエラーが項目の上にまとめて発生した.

  1. 重複内容を除外する実装

“form_app/forms.py"に追記し,以下のように編集する.重複した"author"の情報が提供される場合,エラーが出力するように実装した.

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):
  def save(self,*args,**kwargs):
    print(f'{self.__class__.__name__} is successful')
    return super(BaseForm, self).save(*args,**kwargs)

class BirdModelForm(BaseForm):
  birdname = forms.CharField(label='Birdname')
  title = forms.CharField(label='Title')
  review = forms.CharField(
    label='review', widget=forms.Textarea(attrs={
      'rows':15,
      'cols':25,}))
  
  author = forms.CharField(label='Author')

  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

  def clean_birdname(self):
    birdname = self.cleaned_data.get('birdname')
    if birdname == 'this is error':
      raise validators.ValidationError('そのbirdnameはエラーです')
    return birdname

  def clean_title(self):
    title = self.cleaned_data.get('title')
    if title == 'this is error':
      raise validators.ValidationError('そのtitleはエラーです')
    return title

  def clean_review(self):
    review = self.cleaned_data.get('review')
    if review == 'this is error':
      raise validators.ValidationError('そのreviewはエラーです')
    return review

  def clean_author(self):
    author = self.cleaned_data.get('author')
    if author == 'this is error':
      raise validators.ValidationError('そのauthorはエラーです')
    return author
  
  def clean(self): # 以下追記箇所
    cleaned_data = super().clean()
    author = cleaned_data.get('author')
    checking_exists = Bird.objects.filter(author=author).first()
    if checking_exists:
      raise validators.ValidationError('そのauthorはすでに存在しています')

“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 %}

{{ form.non_field_errors }} # 以下変更箇所
{% if form.errors %}
  {% for item_label, item_content in form.errors.items %}
    {% if item_label != '__all__' %}
      <p><div style="color:red;">{{ item_label }}: {{ item_content.as_text }}</div></p>
    {% endif %}
  {% endfor %}
{% endif %}

{{ form.birdname.label }}: {{ form.birdname }}<br>

{{ form.title.label }}: {{ form.title }}<br>

{{ form.review.label }}: {{ form.review }}<br>

{{ form.author.label }}: {{ form.author }}<br>

<input type="submit" value="submit">
</form>
</body> 
</html>

上記を保存し,ブラウザのURLに"http://127.0.0.1:8000/form_application/form_bird"を入力する.その後,各項目でエラーが発生しないように記述し,"submit"をクリックする.エラーはない.

再び各項目に同じ内容を記述し,"submit"をクリックすると,"Author"に記入された者が同じのため,エラーが出現した.

以上