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

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

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

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

Q&A

1回答

651閲覧

Djangoでrequest.POST.getlistができず、TypeError が出る。

oono

総合スコア38

Django

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

0グッド

0クリップ

投稿2024/02/16 15:16

実現したいこと

Django forms.MultipleChoiceField を使って、複数の選択結果を request.POST.getlist によってリスト表示したい。

発生している問題・分からないこと

TypeError at /xxxxx
can only concatenate str (not "list") to str

が出て、実現したいことができない。

該当のソースコード

Django

1#forms.py 2 3from django import forms 4 5class PyForm(forms.Form): 6 sc = [ 7 ('肉', 'にく'), 8 ('魚', 'さかな'), 9 ('米', 'ごはん'), 10 ('野菜', 'やさい'), 11 ('汁物', 'しるもの'), 12 ('漬物', 'つけもの'), 13 ] 14 multi_choice=forms.MultipleChoiceField(label='食べたもの複数選択可', choices=sc, \ 15 widget=forms.SelectMultiple(attrs={ 16 'size':6,'class':'form-select' 17 })) 18 19 20#views.py 21 22from django.shortcuts import render 23from . forms import PyForm 24 25def choices(request): 26 template = 'choices.html' 27 context = { 28 'title': 'mul_choice', #パス 29 'goto': 'mul_choices', #name 30 'message': None, 31 'form': PyForm(), 32 } 33 34 if 'multi_choice' in request.POST: 35 ch = request.POST.getlist('multi_choice') 36 context['message'] = '食べたものは: '+ch 37 context['form'] = PyForm(request.POST) 38 39 return render(request, template, context)

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

GitHab等で似たような質問、回答があったが、問題のポイントが違うようだ。

補足

選択テーブルはブラウザに表示されるが、
そもそも、選択テーブルで一つの項目しか選べない。
そこで、request.POST.get('multi_choice') とやると、エラーは出ず、選んだもの一つを表示する。
forms.pyにある複数選択のコマンドのどこかに問題があり、複数選べていない模様。

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

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

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

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

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

guest

回答1

0

原因

そもそもフォームデータの取得方法がおかしい
https://docs.djangoproject.com/ja/5.0/ref/forms/api/

対策

views.py

python

1from django.shortcuts import render 2from . forms import PyForm 3 4def choices(request): 5 template = 'choices.html' 6 context = { 7 'title': 'mul_choice', #パス 8 'goto': 'mul_choices', #name 9 'message': None, 10 'form': PyForm(), 11 } 12 13 if request.method == "POST": 14 form = PyForm(request.POST) 15 if form.is_valid(): 16 selected = form.cleaned_data['multi_choice'] 17 context['message'] = '食べたものは: '+','.join(selected) 18 context['form'] = form 19 20 return render(request, template, context)

環境構築

私が試した環境を再現するためのものです。
django系のコードは再現コードがないと同じ話が出来ない場合が多いので。

venv下に実行環境を構築するpythonスクリプトです。ubuntuの場合事前にpython3-venvパッケージが必要です。
スクリプトを空のディレクトリに例えばcreate_env.pyという名前で保存し、python3 create_env.py(windowsの場合はpython)すると構築されます。

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==5.0.2', 'whatthepatch==1.0.5']) 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..2784dd8 31--- /dev/null 32+++ b/myapp/forms.py 33@@ -0,0 +1,15 @@ 34+from django import forms 35+ 36+class PyForm(forms.Form): 37+ sc = [ 38+ ('肉', 'にく'), 39+ ('魚', 'さかな'), 40+ ('米', 'ごはん'), 41+ ('野菜', 'やさい'), 42+ ('汁物', 'しるもの'), 43+ ('漬物', 'つけもの'), 44+ ] 45+ multi_choice=forms.MultipleChoiceField(label='食べたもの複数選択可', choices=sc, \\ 46+ widget=forms.SelectMultiple(attrs={ 47+ 'size':6,'class':'form-select' 48+ })) 49diff --git a/myapp/templates/choices.html b/myapp/templates/choices.html 50new file mode 100644 51index 0000000..2c8026c 52--- /dev/null 53+++ b/myapp/templates/choices.html 54@@ -0,0 +1,6 @@ 55+<form method="post"> 56+ {% csrf_token %} 57+ {{ form }} 58+ <div>{{ message }}</div> 59+ <input type="submit" value="Submit"> 60+</form> 61diff --git a/myapp/urls.py b/myapp/urls.py 62new file mode 100644 63index 0000000..def718a 64--- /dev/null 65+++ b/myapp/urls.py 66@@ -0,0 +1,7 @@ 67+from django.urls import path 68+from . import views 69+ 70+urlpatterns = [ 71+ path('', views.choices, name='choices'), 72+] 73+ 74diff --git a/myapp/views.py b/myapp/views.py 75index 91ea44a..f61d001 100644 76--- a/myapp/views.py 77+++ b/myapp/views.py 78@@ -1,3 +1,20 @@ 79 from django.shortcuts import render 80+from . forms import PyForm 81 82-# Create your views here. 83+def choices(request): 84+ template = 'choices.html' 85+ context = { 86+ 'title': 'mul_choice', #パス 87+ 'goto': 'mul_choices', #name 88+ 'message': None, 89+ 'form': PyForm(), 90+ } 91+ 92+ if request.method == "POST": 93+ form = PyForm(request.POST) 94+ if form.is_valid(): 95+ selected = form.cleaned_data['multi_choice'] 96+ context['message'] = '食べたものは: '+','.join(selected) 97+ context['form'] = form 98+ 99+ return render(request, template, context) 100diff --git a/mysite/settings.py b/mysite/settings.py 101index 2f5eb34..addc95d 100644 102--- a/mysite/settings.py 103+++ b/mysite/settings.py 104@@ -31,6 +31,7 @@ ALLOWED_HOSTS = [] 105 # Application definition 106 107 INSTALLED_APPS = [ 108+ 'myapp.apps.MyappConfig', 109 'django.contrib.admin', 110 'django.contrib.auth', 111 'django.contrib.contenttypes', 112diff --git a/mysite/urls.py b/mysite/urls.py 113index dedacdb..687084f 100644 114--- a/mysite/urls.py 115+++ b/mysite/urls.py 116@@ -15,8 +15,9 @@ Including another URLconf 117 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 118 \"\"\" 119 from django.contrib import admin 120-from django.urls import path 121+from django.urls import include, path 122 123 urlpatterns = [ 124+ path('myapp/', include('myapp.urls')), 125 path('admin/', admin.site.urls), 126 ] 127""") 128with open('patch.py', 'wt', encoding='utf8') as f: 129 f.write("""\ 130import whatthepatch, os, sys 131with open(sys.argv[1], 'rt', encoding='utf8') as fp: 132 for diff in whatthepatch.parse_patch(fp): 133 header = diff.header 134 if header.new_path == '/dev/null': 135 os.remove(header.old_path) 136 else: 137 if header.old_path == '/dev/null': 138 old = [] 139 else: 140 with open(header.old_path, 'rt', encoding='utf8') as fo: 141 old = [line.rstrip(os.linesep) for line in fo] 142 new = whatthepatch.apply_diff(diff, old) 143 try: 144 dirpath = os.path.dirname(header.new_path) 145 if dirpath != "": 146 os.makedirs(dirpath) 147 except FileExistsError: 148 pass 149 with open(header.new_path, 'wt', encoding='utf8') as fn: 150 for line in new: 151 print(line, file=fn) 152""") 153p = subprocess.run([python, 'generated.py'])

使い方

bash

1$ . env/bin/activate # windowsの場合は.\env\Scripts\activate 2$ cd mysite 3$ python manage.py runserver 4... 5^C 6$ deactivate

投稿2024/02/16 21:02

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

oono

2024/02/17 13:28

ご教示ありがとうございます。 解決いたしました。 ただ、小生の場合、もっと基本的な単純な誤りだったようです。 views.pyの以下のコマンドの中で context['message'] = '食べたものは: '+ch の部分 str(ch) としたら、解決しました。 if 'multi_choice' in request.POST: ch = request.POST.getlist('multi_choice') context['message'] = '食べたものは: '+ch → context['message'] = '食べたものは: '+str(ch) context['form'] = PyForm(request.POST)
退会済みユーザー

退会済みユーザー

2024/02/17 13:41

エラーになっている直接の原因はlist型であることなわけですが、リクエストパラメータを直に見てしまっているので、validationが効いていない値になっており、見方そのものが正しくないのです。私の書いた方法だとリクエストパラメータを元にフォームオブジェクトを構築し、それをvalidateした上でリクエストパラメータではなく、フォームに対してきちんとした値を聞いているので、正しい方法になります。 正しい方法の詳細は回答にあるリンク先を読んでください。
退会済みユーザー

退会済みユーザー

2024/02/17 13:43

あとこの表示方法で複数選ぶにはCtrlキーを押しながら選択する必要があります。
退会済みユーザー

退会済みユーザー

2024/02/17 14:27

例えばchoices関数に、@csrf_exemptデコレータを付けてCSRFを無効化し、ブラウザではなくコマンドラインから $ wget -O - --post-data 'multi_choice=beaf' 'http://localhost:8000/myapp/' などとすると、リストにない選択肢の'beaf'を送る事ができます。私の方法だとvalidationが機能するので、is_valid()に失敗して受け取りません(messageはNoneのまま)が、あなたの方法ではそのまま受け取ってしまって、beafと表示されてしまいます。
oono

2024/02/18 03:11

回答ありがとうございます。 dameo様のやり方も試し、実行できました。非常に参考になりました。ご丁寧な説明感謝。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.31%

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

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

質問する

関連した質問