今回は、フォームまわりの扱い方をみていきます。
前提
・「my_app」というアプリを作成済
・「manage.py」と同じ階層に「templates」ディレクトリを作成し、読込先のテンプレートディレクトリのパスをこちらに変更済
.
.
.
from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
TEMPLATE_DIR = os.path.join(BASE_DIR, 'templates')
.
.
.
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [TEMPLATE_DIR,],
・プロジェクトディレクトリの「urls.py」に以下のようにパスを登録済
path('my_app/', include('my_app.urls')),
フォームの作成
まず「/templates」に以下を作成
・「form」ディレクトリ
・「form/index.html」
・「form/form.html」
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>HOME</title>
</head>
<body>
<h1>HOME</h1>
<a href="{% url 'my_app:form' %}">フォーム画面へ</a>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Form</title>
</head>
<body>
<form method="POST">
{% csrf_token %}
<table class="table">
{{ form.as_table }}
</table>
<input type="submit" value="送信">
</form>
</body>
</html>
「my_app」に「forms.py」を作成
from django import forms
class UserInfo(forms.Form):
name = forms.CharField()
age = forms.IntegerField()
mail = forms.EmailField()
「views.py」を下記のように変更
from django.shortcuts import render
from . import forms
# Create your views here.
def index(request):
return render(request, 'form/index.html')
def form(request):
form = forms.UserInfo()
return render(
request, 'form/form.html', context={
'form': form
}
)
「my_app」に「urls.py」を作成
from django.urls import path
from . import views
app_name = 'my_app'
urlpatterns = [
path('', views.index, name='index'),
path('form', views.form, name='form')
]
「/my_app」にアクセスすると、以下のように表示されます。

送信内容の受け取り
「views.py」を以下のように変更
from django.shortcuts import render
from . import forms
# Create your views here.
def index(request):
return render(request, 'form/index.html')
def form(request):
form = forms.UserInfo()
if request.method == 'POST':
form = forms.UserInfo(request.POST)
if form.is_valid():
print(f"name: {form.cleaned_data['name']}, age: {form.cleaned_data['age']}, mail: {form.cleaned_data['mail']}")
return render(
request, 'form/form.html', context={
'form': form
}
)
ブラウザのフォーム画面で値を入力して送信を押すと、コマンドラインに出力されます。

↓

フィールドの種類
下記URLを参照
https://docs.djangoproject.com/ja/3.1/ref/forms/fields/
「forms.py」を修正
from django import forms
class UserInfo(forms.Form):
name = forms.CharField()
age = forms.IntegerField()
mail = forms.EmailField()
is_married = forms.BooleanField()
birthday = forms.DateField()
salary = forms.DecimalField()
job = forms.ChoiceField(choices=(
(1, '正社員'),
(2, '自営業'),
(3, '学生'),
(4, '無色')
))
hobby = forms.MultipleChoiceField(choices=(
(1, 'スポーツ'),
(2, '読書'),
(3, '映画鑑賞'),
(4, '楽器'),
(5, 'その他')
))
homepage = forms.URLField()
ブラウザの表示↓

フォームのカスタマイズ
ラベルのカスタマイズ
name = forms.CharField(label='名前')

入力必須を無効
homepage = forms.URLField(label='ホームページURL', required=False)
ウィジェットを変更
mail = forms.EmailField(
label='メールアドレス',
widget=forms.TextInput(attrs={'placeholder': 'taro@sample.com'})
)
is_married = forms.BooleanField(label='既婚')
birthday = forms.DateField(label='生年月日', initial='1900-01-01')
salary = forms.DecimalField(label='給与')
job = forms.ChoiceField(label='仕事', choices=(
(1, '正社員'),
(2, '自営業'),
(3, '学生'),
(4, '無色')
), widget=forms.RadioSelect)
hobby = forms.MultipleChoiceField(label='趣味', choices=(
(1, 'スポーツ'),
(2, '読書'),
(3, '映画鑑賞'),
(4, '楽器'),
(5, 'その他')
), widget=forms.CheckboxSelectMultiple)
homepage = forms.URLField(label='ホームページURL', required=False)
note = forms.CharField(label='備考', widget=forms.Textarea)

初期値を設定
birthday = forms.DateField(label='生年月日', initial='1900-01-01')

文字数の最小・最大値の設定
name = forms.CharField(label='名前', min_length=1, max_length=20)
id, class の設定
今回は、挙動を確認するために Bootstrap4 を読み込みます。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Form</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
<div class="container">
<form method="POST">
{% csrf_token %}
<table class="table">
{{ form.as_table }}
</table>
<input type="submit" value="送信">
</form>
</div>
</body>
</html>
id や class を設定するには2通りのやり方があります。
widget の属性として設定
name = forms.CharField(
label='名前',
min_length=1,
max_length=20,
widget=forms.TextInput(attrs={'class': 'form-control'})
)

クラスの初期化処理時に設定
以下の関数を追加します。
def __init__(self, *args, **kwargs):
super(UserInfo, self).__init__(*args, **kwargs)
self.fields['age'].widget.attrs['class'] = 'form-control'

フォームのバリデーション
フォームを送信する際に、フォームが正しく入力されているかチェックする機能をバリデーションと言います。
バリデーションの実装方法をいくつかみていきます。
「clean_フィールド名」メソッドの使用
「clean_フィールド名」というメソッドを使用します。
def clean_homepage(self):
homepage = self.cleaned_data['homepage']
if not homepage.startswith('https'):
raise forms.ValidationError('httpsから始めてください')
ホームページURL入力内容が「https」から始まっていない場合、以下のエラーが表示されます。

validators の使用
.
.
.
from django.core import validators
.
.
.
age = forms.IntegerField(
label='年齢',
validators=[validators.MinValueValidator(20, message='20歳以上が対象です')]
)
年齢の入力内容が20未満だとエラーが表示されます。

validators の種類は以下を参照
https://docs.djangoproject.com/ja/3.1/ref/validators/#built-in-validators
バリデートメソッドの自作
自作したバリデート用のメソッドをフィールドの validators に設定することで実装できます。
.
.
.
from django.core import validators
.
.
.
def check_name(value):
if value == 'あいうえお':
raise validators.ValidationError('その名前は登録できません')
name = forms.CharField(
label='名前',
min_length=1,
max_length=20,
widget=forms.TextInput(attrs={'class': 'form-control'}),
validators=[check_name]
)

cleanメソッドの使用
cleanメソッドと、メールアドレス再入力フィールドを追加します。
def clean(self):
cleaned_data = super().clean()
mail = cleaned_data['mail']
verify_mail = cleaned_data['verify_mail']
if mail != verify_mail:
raise forms.ValidationError('メールアドレスが一致しません')
・
・
・
verify_mail = forms.EmailField(
label='メールアドレス再入力',
widget=forms.TextInput(attrs={'placeholder': 'taro@sample.com'})
)

モデルフォーム
データの挿入
モデルを作成
from django.db import models
# Create your models here.
class Post(models.Model):
name = models.CharField(max_length=50)
title = models.CharField(max_length=255)
memo = models.CharField(max_length=255)
「forms.py」を修正
.
.
.
from .models import Post
・
・
・
class PostModelForm(forms.ModelForm):
memo = forms.CharField(
widget=forms.Textarea(attrs={'rows': 5})
)
class Meta:
model = Post
fields = '__all__'
# 必要な項目のみ指定
# fields = ['name', 'title']
# 除外する項目を指定
# exclude = ['memo']
その他ファイルを修正
.
.
.
def form_post(request):
form = forms.PostModelForm()
if request.method == 'POST':
form = forms.PostModelForm(request.POST)
if form.is_valid():
form.save()
return render(
request, 'form/form_post.html', context={'form': form}
)
path('form_post', views.form_post, name='form_post')
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Form</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
<div class="container">
<form method="POST">
{% csrf_token %}
<table class="table">
{{ form.as_table }}
</table>
<input type="submit" value="送信">
</form>
</div>
</body>
</html>
マイグレーションを実行
manage.py makemigrations my_app
python manage.py migrate
「/my_app/form_post」にアクセスすると、以下のように表示されます。

任意の値を入力して送信すると、データベースにも反映されます。

saveメソッドのカスタマイズ
saveメソッドを上書きすることでカスタマイズできます。
class PostModelForm(forms.ModelForm):
memo = forms.CharField(
widget=forms.Textarea(attrs={'rows': 5})
)
class Meta:
model = Post
fields = '__all__'
def save(self, *args, **kwargs):
obj = super(PostModelForm, self).save(commit=False, *args, **kwargs)
obj.save()
return obj
モデルフォームクラスの継承
class BaseForm(forms.ModelForm):
def save(self, *args, **kwargs):
print(f'form: {self.__class__.__name__}')
return super(BaseForm, self).save(*args, **kwargs)
class PostModelForm(BaseForm):
.
.
.
各フィールドのカスタマイズ
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Form</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
<div class="container py-4">
<form method="POST">
{% csrf_token %}
{{ form.name.label }}: {{ form.name }}
{% if form.name.errors %}
{{ form.name.errors.as_text }}
{% endif %}
{{ form.title.label }}: {{ form.title }}
{% if form.title.errors %}
{{ form.title.errors.as_text }}
{% endif %}
{{ form.memo.label }}: {{ form.memo }}
{% if form.memo.errors %}
{{ form.memo.errors.as_text }}
{% endif %}
<input type="submit" value="送信">
</form>
</div>
</body>
</html>
class PostModelForm(BaseForm):
def clean_name(self):
name = self.cleaned_data.get('name')
if name == 'あいうえお':
raise validators.ValidationError('その名前は登録できません')
return name
def clean_title(self):
title = self.cleaned_data.get('title')
if title == 'あいうえお':
raise validators.ValidationError('そのタイトルは登録できません')
return title
name = forms.CharField(label='名前')
title = forms.CharField(label='タイトル')
memo = forms.CharField(
label='メモ',
widget=forms.Textarea(attrs={'rows': 5})
)
.
.
.
ブラウザには以下のように表示されます。

また、バリデーションも正常に実行できています。

複数のエラーを一ヵ所に表示させたい場合は以下のとおりです。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Form</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
<div class="container py-4">
<form method="POST">
{% csrf_token %}
{% if form.errors %}
{% for key, value in form.errors.items %}
<p>{{key}}: {{value.as_text}}</p>
{% endfor %}
{% endif %}
{{ form.name.label }}: {{ form.name }}
{{ form.title.label }}: {{ form.title }}
{{ form.memo.label }}: {{ form.memo }}
<input type="submit" value="送信">
</form>
</div>
</body>
</html>
エラーを発生させると、以下のように一ヵ所にまとめて表示されます。

フォームを外部ファイルに定義
フォームのテンプレートファイルをつくります。
<form method="POST">
{% csrf_token %}
{% if form.errors %}
{% for key, value in form.errors.items %}
<p>{{key}}: {{value.as_text}}</p>
{% endfor %}
{% endif %}
{{ form.name.label }}: {{ form.name }}
{% if form.name.errors %}
{{ form.name.errors.as_text }}
{% endif %}
{{ form.title.label }}: {{ form.title }}
{% if form.title.errors %}
{{ form.title.errors.as_text }}
{% endif %}
{{ form.memo.label }}: {{ form.memo }}
{% if form.memo.errors %}
{{ form.memo.errors.as_text }}
{% endif %}
<input type="submit" value="送信">
</form>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Form</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
<div class="container py-4">
{% include "form/form_template.html" %}
</div>
</body>
</html>
ブラウザにはこれまで通り表示されます。

もうひとつテンプレートファイルをつくります。
<form method="POST">
{% csrf_token %}
{% if as_table %}
<table>
{{ form.as_table }}
</table>
{% elif as_ul %}
{{ form.as_ul }}
{% else %}
{{ form.as_p }}
{% endif %}
<input type="submit" value="送信">
</form>
with でプロパティを渡します。
{% include "form/form_template2.html" with as_ul=True %}
するとフォームがulタグで表示されます。

ファイルのアップロード
基本的な方法
「settings.py」を修正
from pathlib import Path
import os
BASE_DIR = Path(__file__).resolve().parent.parent
.
.
.
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
「manage.py」と同階層に「media」ディレクトリを作成
(「/media」)
「views.py」を修正
.
.
.
from django.core.files.storage import FileSystemStorage
import os
.
.
.
def upload_sample(request):
if request.method == 'POST' and request.FILES['upload_file']:
upload_file = request.FILES['upload_file']
fs = FileSystemStorage()
file_path = os.path.join('upload', upload_file.name)
file = fs.save(file_path, upload_file)
uploaded_file_url = fs.url(file)
return render(request, 'form/upload_file.html', context={
'uploaded_file_url': uploaded_file_url
})
return render(request, 'form/upload_file.html')
HTMLファイルを作成
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Form Upload</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
<div class="container py-4">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="upload_file"><br>
<input type="submit" value="保存">
</form>
{% if uploaded_file_url %}
<p>
<a href="{{ uploaded_file_url }}">{{ uploaded_file_url }}</a>
</p>
{% endif %}
</div>
</body>
</html>
パスの追加
path('upload_sample', views.upload_sample, name='upload_sample')
「/my_app/upload_sample」にアクセスし、適当な画像を選択して保存
すると、「/media/upload」に画像が保存されます。
モデルを使った方法
プロジェクトディレクトリの「urls.py」を修正
.
.
.
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
.
.
.
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
モデルの作成
class User(models.Model):
name = models.CharField(max_length=50)
age = IntegerField()
picture = models.FileField(upload_to='picture/')
マイグレーションの実行
python manage.py makemigrations my_app
python manage.py migrate my_app
「forms.py」の修正
.
.
.
from .models import Post, User
.
.
.
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = '__all__'
「views.py」の修正
.
.
.
def upload_model_form(request):
user = None
if request.method == 'POST':
form = forms.UserForm(request.POST, request.FILES)
if form.is_valid():
user = form.save()
else:
form = forms.UserForm()
return render(request, 'form/upload_model_form.html', context={
'form': form,
'user': user
})
HTMLファイルの作成
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Form Upload</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
<div class="container py-4">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="upload_file"><br>
<input type="submit" value="保存">
</form>
{% if uploaded_file_url %}
<p>
<a href="{{ uploaded_file_url }}">{{ uploaded_file_url }}</a>
</p>
{% endif %}
</div>
</body>
</html>
パスの追加
path('upload_model_form', views.upload_model_form, name='upload_model_form')
「/my_app/upload_model_form」にアクセスして、値の入力と画像の選択後に保存すると下に表示されます。

画像は「/media/picture」は以下に保存されます。
また、「upload_to=」を以下のように指定すると、
画像を保存したときの日付のディレクトリが自動的に作られます。
class User(models.Model):
name = models.CharField(max_length=50)
age = IntegerField()
picture = models.FileField(upload_to='picture/%Y-%m-%d')

今回は以上になります。
ご覧いただきありがとうございました!
続きはこちら↓
コメント