質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
Django

DjangoはPythonで書かれた、オープンソースウェブアプリケーションのフレームワークです。複雑なデータベースを扱うウェブサイトを開発する際に必要な労力を減らす為にデザインされました。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

1回答

1412閲覧

Django checkboxがNoneの場合のみis_valid()がFalseになってしまいます.

Tatsuya1192

総合スコア9

Django

DjangoはPythonで書かれた、オープンソースウェブアプリケーションのフレームワークです。複雑なデータベースを扱うウェブサイトを開発する際に必要な労力を減らす為にデザインされました。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2023/03/09 14:47

実現したいこと

・checkboxの値がNoneの状態でforms.pyのis_valid()を通したいです.

前提

学習目的でCustomUserモデルを管理するフォームをAdmin無しで作成しております.
一つ詰まってしまった点で, タイトルの通り, checkboxがNoneの場合のみis_valid()がFalseになってしまいます.

・usernameとemailは変更してもis_valid()を通ります.
・問題のis_activeとis_staffも共にTrueであればis_valid()を通ります.
・is_activeとis_staffのいずれかがNoneの場合, is_valid()がFalseとなります.

発生している問題・エラーメッセージ

エラーメッセージは何も出ません.

該当のソースコード

python

1### models.py ### 2 3class User(AbstractBaseUser, PermissionsMixin): 4 username = models.CharField(max_length=50) 5 email = models.EmailField(max_length=150, unique=True) 6 # password = # AbstractBaseUserがフィールドを持つので追記不要. 7 # is_superuser = # PermissionMixinがフィールドを持つので追記不要. 8 is_staff = models.BooleanField(default=False) 9 is_active = models.BooleanField(default=False) 10 create_at = models.DateTimeField(default=datetime.datetime.now()) 11 modified_at = models.DateTimeField(null=True, blank=True) 12 13 USERNAME_FIELD = 'email' 14 REQUIRED_FIELDS = ['username'] 15 16 objects = UserManager() 17 18 def __str__(self): 19 return self.username 20

python

1### forms.py ### 2 3class UpdateuserForm(forms.ModelForm): 4 username = forms.CharField(label='ユーザー名', max_length=50) 5 email = forms.EmailField(label='メールアドレス', max_length=150) 6 is_active = forms.BooleanField() 7 is_staff = forms.BooleanField() 8 9 class Meta(): 10 model = User 11 fields = ('username', 'email', 'is_active', 'is_staff') 12

python

1### views.py ### 2@login_required 3def manage_user(request): 4 obj = User.objects.all() 5 update_user_form = UpdateuserForm(request.POST or None) 6 ctx = { 7 'user_objects': obj, 8 'update_form': update_user_form, 9 } 10 11 if 'user_edit_submit' in request.POST: 12 logger.info({ # logging 13 'action': 'user_edit_submit in request.POST', 14 'status': 'run', 15 'value': { 16 'request.POST: ': request.POST, 17 } 18 }) 19 terget_obj = get_object_or_404(User, id=request.POST['id']) 20 update_user_form = UpdateuserForm(request.POST, instance=terget_obj) 21 22 if update_user_form.is_valid(): 23 logger.info({ # logging 24 'action': 'update_user_form.is_valid()', 25 'status': 'run', 26 'value': { 27 'cleaned_data': update_user_form.cleaned_data 28 } 29 }) 30 update_user_form.save() 31 return redirect('account:manage_user') 32 else: 33 logger.warning({ # logging 34 'action': 'update_user_form.is_valid()', 35 'status': 'failed', 36 'request.POST': request.POST, 37 'message': 'updateformにinstanceを渡していますか?', 38 }) 39 40 return render(request, 'admin/manage_user.html', ctx) 41

html

1{% for user_object in user_objects %} 2 <ul> 3 <li>{{ user_object.username }}</li> 4 <li>...</li> <- リスト表示しているだけなので省略します. -> 5 </ul> 6 <form method="post"> 7 {% csrf_token %} 8 <input type="text" name="username" value="{{ user_object.username }}"> 9 <input type="email" name="email" value="{{ user_object.email }}"> 10 <- 問題のチェックボックスです -> 11 <input type="checkbox" name="is_active" {% if user_object.is_active == True %}checked{% endif %}> 12 <input type="checkbox" name="is_staff" {% if user_object.is_staff == True %}checked{% endif %}> 13 <- ここまで -> 14 <input type="hidden" name="id" value="{{ user_object.id }}"> 15 <input type="submit" name="user_edit_submit" value="変更"> 16 </form> 17{% endfor %}

試したこと

・文字列がいけないのかと思いBoolean型をrequest.POST['is_active']...にrequest.POST._mutableで無理矢理渡しましたが同じ動作(前提に箇条書きした条件の通りの動作です.).
・inputのvalue値で hiddenでFalse, チェックボックスon時でTrueをpostするようにしたところ同じ動作.
・checkboxのcheckedが悪さをしているかと思い外して上記を試したところ同じ動作.

補足情報(FW/ツールのバージョンなど)

docker v4.15 (python:3 のイメージでビルドしております.)
Python 3.11.1
Django 4.1.2

試したことの後3時間ほど調べたのですが, 自力での解決が難しいと考えました.
お力添えくださると幸いです.

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

django.forms.BooleanFieldrequired=Falseが必須のようです。

widget=django.forms.CheckboxInputとして自動でレンダリングさせるとわかりますが、このフラグがHTMLのinput要素のrequired属性と連動しています。つまり設計意図としては、BooleanFieldがデフォルトのrequired=Trueな場合、HTML的にはチェックボックスがチェックされない限り、ブラウザが送信そのものを行わない指定になるということです。そのため、Falseなのに送られてきたらis_validは当然Falseになるという理屈になります。

なので、チェックしていない結果を送信したい場合、BooleanFieldにrequired=Falseが必要になります。

検証用環境構築コード

venvで作った環境に簡単な検証コードを書いてみました。ご参考までに。

python

1import os 2import venv 3import subprocess 4ENV = 'env' 5if os.name == 'posix': 6 BIN = 'bin' 7else: 8 BIN = 'Scripts' 9builder = venv.EnvBuilder(with_pip=True) 10builder.create(ENV) 11cwd = os.getcwd() 12python = os.path.join(cwd, ENV, BIN, 'python') 13os.environ['VIRTUAL_ENV'] = os.path.join(cwd, ENV) 14os.environ['PATH'] = os.path.join(cwd, ENV, BIN) + os.pathsep + os.environ['PATH'] 15p = subprocess.run([python, '-m', 'pip', 'install', '--upgrade', 'pip', 'setuptools']) 16p = subprocess.run([python, '-m', 'pip', 'install', 'django==4.1.7', 'whatthepatch==1.0.4']) 17with open('generated.py', 'wt', encoding='utf8') as f: 18 f.write("""\ 19import subprocess 20import os 21p = subprocess.run('django-admin startproject mysite', shell=True) 22os.chdir('mysite') 23p = subprocess.run('python manage.py startapp myapp', shell=True) 24p = subprocess.run('python ../patch.py ../patch.diff', shell=True) 25""") 26with open('patch.diff', 'wt', encoding='utf8') as f: 27 f.write("""\ 28diff --git a/myapp/forms.py b/myapp/forms.py 29new file mode 100644 30index 0000000..e2e4514 31--- /dev/null 32+++ b/myapp/forms.py 33@@ -0,0 +1,4 @@ 34+from django import forms 35+class HogeForm(forms.Form): 36+ checkbox = forms.BooleanField(widget=forms.CheckboxInput, initial=False) 37+ textbox = forms.CharField(widget=forms.TextInput, initial='ほげ') 38diff --git a/myapp/templates/myapp/form.html b/myapp/templates/myapp/form.html 39new file mode 100644 40index 0000000..4028860 41--- /dev/null 42+++ b/myapp/templates/myapp/form.html 43@@ -0,0 +1,7 @@ 44+<form action="{% url 'form' %}" method="post"> 45+{% csrf_token %} 46+{{ form.checkbox }} チェックボックス<br> 47+{{ form.textbox }} 文字列<br> 48+<input type="submit" name="submit" value="送信"> 49+</form> 50+ 51diff --git a/myapp/urls.py b/myapp/urls.py 52new file mode 100644 53index 0000000..d12653a 54--- /dev/null 55+++ b/myapp/urls.py 56@@ -0,0 +1,6 @@ 57+from django.urls import path 58+from . import views 59+ 60+urlpatterns = [ 61+ path('', views.index, name='form'), 62+] 63diff --git a/myapp/views.py b/myapp/views.py 64index 91ea44a..32d73c5 100644 65--- a/myapp/views.py 66+++ b/myapp/views.py 67@@ -1,3 +1,11 @@ 68 from django.shortcuts import render 69+from django.http import HttpResponse 70+from .forms import HogeForm 71+ 72+def index(request): 73+ form = HogeForm(request.POST or None) 74+ if request.method == 'POST': 75+ if form.is_valid(): 76+ print('成功') 77+ return render(request, 'myapp/form.html', {'form': form}) 78 79-# Create your views here. 80diff --git a/mysite/settings.py b/mysite/settings.py 81index a73b6e4..4851a48 100644 82--- a/mysite/settings.py 83+++ b/mysite/settings.py 84@@ -31,6 +31,7 @@ ALLOWED_HOSTS = [] 85 # Application definition 86 87 INSTALLED_APPS = [ 88+ 'myapp.apps.MyappConfig', 89 'django.contrib.admin', 90 'django.contrib.auth', 91 'django.contrib.contenttypes', 92@@ -103,9 +104,9 @@ AUTH_PASSWORD_VALIDATORS = [ 93 # Internationalization 94 # https://docs.djangoproject.com/en/4.1/topics/i18n/ 95 96-LANGUAGE_CODE = 'en-us' 97+LANGUAGE_CODE = 'ja' 98 99-TIME_ZONE = 'UTC' 100+TIME_ZONE = 'Asia/Tokyo' 101 102 USE_I18N = True 103 104diff --git a/mysite/urls.py b/mysite/urls.py 105index 8bc0cd4..d2fd81c 100644 106--- a/mysite/urls.py 107+++ b/mysite/urls.py 108@@ -14,8 +14,9 @@ Including another URLconf 109 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 110 \"\"\" 111 from django.contrib import admin 112-from django.urls import path 113+from django.urls import include, path 114 115 urlpatterns = [ 116+ path('myapp/', include('myapp.urls')), 117 path('admin/', admin.site.urls), 118 ] 119""") 120with open('patch.py', 'wt', encoding='utf8') as f: 121 f.write("""\ 122import whatthepatch, os, sys 123with open(sys.argv[1], 'rt', encoding='utf8') as fp: 124 for diff in whatthepatch.parse_patch(fp): 125 header = diff.header 126 if header.new_path == '/dev/null': 127 os.remove(header.old_path) 128 else: 129 if header.old_path == '/dev/null': 130 old = [] 131 else: 132 with open(header.old_path, 'rt', encoding='utf8') as fo: 133 old = [line.rstrip(os.linesep) for line in fo] 134 new = whatthepatch.apply_diff(diff, old) 135 try: 136 os.makedirs(os.path.dirname(header.new_path)) 137 except FileExistsError: 138 pass 139 with open(header.new_path, 'wt', encoding='utf8') as fn: 140 for line in new: 141 print(line, file=fn) 142""") 143p = subprocess.run([python, 'generated.py'])

フォームとビューのコード抜粋

mysite/myapp/forms.py

python

1from django import forms 2class HogeForm(forms.Form): 3 checkbox = forms.BooleanField(widget=forms.CheckboxInput, initial=False) 4 textbox = forms.CharField(widget=forms.TextInput, initial='ほげ')

mysite/myapp/views.py

python

1from django.shortcuts import render 2from django.http import HttpResponse 3from .forms import HogeForm 4 5def index(request): 6 form = HogeForm(request.POST or None) 7 if request.method == 'POST': 8 if form.is_valid(): 9 print('成功') 10 return render(request, 'myapp/form.html', {'form': form})

mysite/myapp/templates/myapp/form.html

html

1<form action="{% url 'form' %}" method="post"> 2{% csrf_token %} 3{{ form.checkbox }} チェックボックス<br> 4{{ form.textbox }} 文字列<br> 5<input type="submit" name="submit" value="送信"> 6</form>

動作のために必要な修正

mysite/myapp/forms.py

python

1from django import forms 2class HogeForm(forms.Form): 3 # checkbox = forms.BooleanField(widget=forms.CheckboxInput, initial=False) 4 checkbox = forms.BooleanField(widget=forms.CheckboxInput, initial=False, required=False) 5 textbox = forms.CharField(widget=forms.TextInput, initial='ほげ')

投稿2023/03/09 19:37

dameo

総合スコア943

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Tatsuya1192

2023/03/10 08:28

>> BooleanFieldがデフォルトのrequired=Trueな場合、HTML的にはチェックボックスがチェックされない限り、ブラウザが送信そのものを行わない指定になるということです。 こういう挙動になるのですね! チェックが外れればNoneが返されるのでよく考えれば確かにそうでした... 勉強になりました. とても助かります!
dameo

2023/03/10 08:54

他の人のための補足です。質問者さんがおっしゃっているのはこの部分の話ですね。 https://developer.mozilla.org/ja/docs/Web/HTML/Element/input/checkbox > メモ: フォームが送信されたときにチェックボックスがチェックされていなかった場合、チェックされていない状態を表す値 (value=unchecked など) が送信されることはなく、値はサーバーに全く送信されません。 なので、django側にフォームとして送信されるデータの中で、チェックボックスのinput要素のname属性に指定される値は、チェックされていない場合には付加されず、djangoはそのname属性をキーとした値を聞かれるとNoneを返すということです。 私が言ってたのはrequired=Trueのとき、input要素のrequired属性もTrueになり(HTMLで属性名のみは真偽値の真を意味します)、チェックされない限りsubmitされない(ポップアップが出て送信できない)ということです。私のコードではチェックボックスは自動でレンダリングされてますが、 {{ form.checkbox }} 質問者さんのコードでは、自動でレンダリングしていないため <input type="checkbox" name="is_active" {% if user_object.is_active == True %}checked{% endif %}> BooleanFieldのrequired属性に連動させずに、input要素のrequired属性を外すことができているため、submitは出来ています。私のコード(required=True)で無理矢理submitさせるためには、ブラウザの開発者ツールで、直接input要素のrequired属性を外してあげる必要があります。 なお、付記しようか迷ったのですが、フォームフィールドにMultipleChoiceFieldを使い、ウィジェットにCheckboxSelectMultipleを使い、各要素にvalue属性も設定することで、複数選択可能なラジオボタンのような使い方も可能です。この場合はデフォルトだと最低1個選択で送信可能になります。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問