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

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

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

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

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

3回答

760閲覧

Djangoで1つのフォームからデータを送信した際に、2つのモデルに反映されるようにしたい

nokonoko_1203

総合スコア17

Django

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

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2018/07/12 03:57

編集2018/07/17 23:30

いつも大変お世話になっております。

表記の件につきまして、現在下記のような二つのモデルが存在しており、「shopping_list_form.html」のフォームからPOSTした際に自動的に「Purchase_history」モデルに反映されるようにしたいのですが、やり方が分からず困っております。

python

1#models.py 2 3class Shopping_list(models.Model): 4 name = models.CharField(max_length=64) 5 price = models.IntegerField(null=True, blank=True) 6 7 def __str__(self): 8 return '%s' % (self.name) 9 10 11class Purchase_history(models.Model): 12 name = models.CharField(max_length=64) 13 price = models.IntegerField(null=True, blank=True) 14 15 def __str__(self): 16 return '%s' % (self.name)

html

1<body> 2 3 {% if error_list %} 4 <ul> 5 {% for v in error_list %} 6 <li>{{ v }}</li> 7 {% endfor %} 8 </ul> 9 {% endif %} 10 11 <h3>買い物リストを編集する</h3> 12 13 <form method="post"> 14 <!--ミドルウェアに「django.middleware.csrf.CsrfViewMiddleware」を指定しているとcsrf_tokenを渡していないPOSTメソッドは403エラーになる--> 15 {{ form.management_form }} 16 {% csrf_token %} 17 <div class="shopping_list"> 18 {% for fm in form %} 19 {{ fm.id }} 20 <div class="shopping_list_one"> 21 <p>商品名<br/>{{ fm.name }}</p> 22 <br/> 23 <p>金額<br/>{{ fm.price }}</p> 24 <p class="delete_check_box">削除:{{ fm.DELETE }}</p> 25 </div> 26 {% endfor %} 27 <button type="submit">リストを更新</button> 28 </form> 29 30 <a href="{% url "stockpile:index" %}">トップに戻る</a> 31 32 <h4 id="week_menu_list">今週のメニュー</h4> 33 <table> 34 <tr> 35 <th></th> 36 <th></th> 37 <th></th> 38 <th></th> 39 <th></th> 40 <th></th> 41 <th></th> 42 </tr> 43 {% for v in Week_menu %} 44 <tr> 45 <td>{{ v.mon }}</td> 46 <td>{{ v.tue }}</td> 47 <td>{{ v.wed }}</td> 48 <td>{{ v.thu }}</td> 49 <td>{{ v.fri }}</td> 50 <td>{{ v.sat }}</td> 51 <td>{{ v.sun }}</td> 52 </tr> 53 {% endfor %} 54 55</body>

「Shopping_list」のフォームに関しては、汎用ビューとmodelformset_factoryを利用してフォームを3つ発生させるようにしております。
「Purchase_historyForm」のビューに関してはどうすればいいか分からず、まだ記述しておりません。

python

1#forms.py 2 3class Shopping_listForm(ModelForm): 4 5 class Meta: 6 model = Shopping_list 7 fields = ("name", "price") 8 9 10Shopping_listFormSet = modelformset_factory( 11 Shopping_list, 12 form=Shopping_listForm, 13 fields=("name", "price"), 14 extra=3, 15 can_delete=True 16 ) 17 18 19class Purchase_historyForm(ModelForm): 20 21 class Meta: 22 model = Purchase_history 23 fields = ("name", "price")

python

1# Views.py 2 3class Shopping_listCreateView(FormView): 4 # model = Shopping_list 5 # fields = ["name", "price"] 6 form_class = Shopping_listFormSet 7 template_name = "stockpile/shopping_list_form.html" 8 success_url = "/stockpile/shopping_list/" 9 10 def get_context_data(self, **kwargs): 11 context = super().get_context_data(**kwargs) 12 context['Week_menu'] = Week_menu.objects.all() 13 return context 14 15 def form_valid(self, form): 16 form.save() # ここで保存 17 return super().form_valid(form) # リダイレクト処理です。redirect関数等でも良いです。 18

考えていたのは

・Shopping_listCreateViewでShopping_listモデルに保存

・Purchase_historyに保存するためアップデートビューに飛ばす(テンプレートは表示させない)

・アップデートビューから変遷先のテンプレートにリダイレクト

で、実装できるのかな。と考えておりましたが、そもそもそのやりかたも分からず、ご教授頂きたいです。

追記:修正しましたが解決に至っておりません。コードは下記の通りです。

python

1# views.py 2 3# Shopping_listView 4 5class Shopping_listView(ListView): 6 model = Shopping_list 7 paginate_by = 30 8 template_name = "stockpile/shopping_list.html" 9 queryset = Shopping_list.objects.all() 10 11 def get_context_data(self, **kwargs): 12 context = super().get_context_data(**kwargs) 13 ''' 14 辞書のリストが帰ってくる 15 [{"id": 1, "price":500......}] 16 ''' 17 all_list_price = Shopping_list.objects.all().values("id", "price") 18 sum_price = 0 19 for v in all_list_price: 20 price = v["price"] 21 sum_price += price 22 context['sum_price'] = sum_price 23 return context 24 25 26class Shopping_listCreateView(FormView): 27 # model = Shopping_list 28 # fields = ["name", "price"] 29 form_class = Shopping_listFormSet 30 template_name = "stockpile/shopping_list_form.html" 31 success_url = "/stockpile/shopping_list/" 32 33 def get_context_data(self, **kwargs): 34 context = super().get_context_data(**kwargs) 35 context['Week_menu'] = Week_menu.objects.all() 36 return context 37 38 def form_valid(self, form): 39 form.save() # ここで保存 40 41 for f in form: 42 Purchase_history.objects.create(name=f.cleaned_data["name"], 43 price=f.cleaned_data["price"]) 44 45 return super().form_valid(form) 46 47 48def delete_shopping_list(request, shopping_list_id): 49 shopping_list = get_object_or_404(Shopping_list, id=shopping_list_id) 50 shopping_list.delete() 51 shopping_list = Shopping_list.objects.all() 52 context = {'object_list': shopping_list} 53 return render(request, 'stockpile/shopping_list.html', context)

python

1forms.py 2 3class Shopping_listForm(ModelForm): 4 5 class Meta: 6 model = Shopping_list 7 fields = ("name", "price") 8 9 10Shopping_listFormSet = modelformset_factory( 11 Shopping_list, 12 form=Shopping_listForm, 13 fields=("name", "price"), 14 extra=3, 15 can_delete=True 16 ) 17 18class Purchase_historyForm(ModelForm): 19 20 class Meta: 21 model = Purchase_history 22 fields = ("name", "price")

Local

1__class__ 2<class 'stockpile.views.Shopping_listCreateView'> 3 4f 5<Shopping_listForm bound=True, valid=True, fields=(name;price;id;DELETE)> 6 7form 8<django.forms.formsets.Shopping_listFormFormSet object at 0x7f1e95ec36a0> 9 10self 11<stockpile.views.Shopping_listCreateView object at 0x7f1e95df7518>

fオブジェクトにはnameが存在しているように見えるのですが、エラーとなります

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

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

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

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

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

guest

回答3

0

ご質問のソースでは、汎用ビューを利用されていましたね。
汎用ビューはチュートリアル以来利用していなかったので、存在を忘れていました・・・笑

先の回答の3.4を、form_valid関数の中で実施すれば良いと思います。

for cleaned_data in form.cleaned_data:
で各レコードのcleaned_dataを取り出せます。

cleaned_dataは、フォームのフィールド名をキーとした辞書になっています。

(上記のfor文がエラーになる場合は、引数formにformsetのイテレータが入っているかもしれません。その場合は、for modelForm in form:のような感じで、それぞれのmodelFormにアクセスし、cleaned_dataを取り出してください。)

あとは、各cleaned_data毎にPurchase_historyのレコードを作成すれば良いと思います。

python

1 def form_valid(self, form): 2 form.save() # ここで保存 3 4 for cleaned_data in form.cleaned_data: 5 Purchase_history.objects.create(name=cleaned_data["name"], price=cleaned_data["price"]) 6 7 return super().form_valid(form)

動作未確認です。悪しからず。

追記

この記事によると、FormsetにはQuerySet(元のモデル)へのアクセスが必要になりますが、汎用ビューはQuerySetを取り扱わないので、自前でセットしなければならないようです。

python

1class Shopping_listCreateView(FormView): 2 # model = Shopping_list 3 # fields = ["name", "price"] 4 form_class = Shopping_listFormSet 5 template_name = "stockpile/shopping_list_form.html" 6 success_url = "/stockpile/shopping_list/" 7 8 def get_form_kwargs(self): 9 kwargs = super(Shopping_listCreateView, self).get_form_kwargs() 10 kwargs["queryset"] = Shopping_list.objects.none() 11 return kwargs

投稿2018/07/13 01:21

編集2018/07/27 00:12
Meganezaru

総合スコア715

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

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

nokonoko_1203

2018/07/17 21:01

詳細なご回答ありがとうございます。 しかし、御察しの通りエラーが出てしまい、苦戦しております。 本文に修正したコードを追記しておりますが、ご指摘頂いた通りにform_valid関数の中でfor文を使ってcleaned_dataを取り出そうと試みましたが、「KeyError at /stockpile/add_shopping_list/ 'name'」と、KeyErrorが発生してしまいました。
nokonoko_1203

2018/07/17 21:10

デバッグモードでエラー箇所を確認してみるとcleaned_dataは「{}」となっており、中には何も入っていないように見えます。 記載して頂いている通り、modelFormからcleaned_dataを取り出そうとしたのですが、そもそもformがどういうオブジェクトなのか分からず、なんの値を取り出せばいいのか分からず手詰まりになってしまいました。なんども恐縮ですが、対処法をご教示いただけると幸いです。
Meganezaru

2018/07/18 00:49

すいませんでした・・・ やっぱり、確認せずは、ダメですね・・・ 時間をみて、ちょっと試してみますので、お待ちください。 djangoのformは、大きく分けて、二つの意味合いがあり、一つは、フォームをHTMLレンダリングするための材料、もう一つは入力データのバリデーションを体系的に行う仕組み、です。(まだあるのかな?w) 今回の件で言えば、バリデーションが通った、データベースに保存しても問題がないデータ、が、 cleaned_dataとして取り出せるはずなのですが・・・w saveのタイミングでクリアされてしまうんでしょうか??とにかく調べてみます。
nokonoko_1203

2018/07/18 20:58

何度もごめんなさい… よろしくお願いいたします。
nokonoko_1203

2018/07/18 22:15

今、管理サイトを確認してみると2点問題がありました。 ・問題なくPurchase_historyにレコードが作成されていたのにも関わらずKeyErrorが発生していた。 ・Shopping_listを更新、削除した際に全件Purchase_historyに追加されてしまう。 2番目の問題に関しては後回しにするにしても、最初の問題がなぜこうなるのか理解できず困っておりました。 教えて頂いたようなやり方でfオブジェクトからデータを取り出し、Purchase_historyに追加できているようなのですが、エラーが出てきます… うーん…
Meganezaru

2018/07/18 23:50

なるほど、なんとなくわかった気が・・・ 今回の回答としては、新規登録しかイメージしていませんので、 毎回、新規レコードが作成されるので、更新・削除の流れでエラーになっている可能性がありますね。 モデル定義では、nameフィールドにUnique制限をかけてなさそうですが、 DB上の定義では、Uniqueがかかってる、とかですかね? ソースからは詳細不明なのでなんとも言えませんが。 更新や、削除は、別の処理が必要になりますが、アプリケーションの領域だと思うので、 何がやりたいかによって、Purchase_historyの扱いが変わってきますね。 汎用ビューは、使ったことがないので、ちょっと調べてみましたが、 更新には、UpdateViewから派生させたViewが使えるようです。 UpdateViewのform_validには、Purchase_historyをupdate(必要であれば)する処理を入れることになると思います。 削除は、いろいろ手段があるように思いますので・・・
Meganezaru

2018/07/26 23:59

実績のないことへの回答は、中途半端になってしまうことを反省してます(>_<) どうやら、汎用ビューは、FormSetに対応していないようです。 そのままでは、うまく動かないようで、少し修正が必要なようです。 またしても、探したレベルの回答で申し訳ないですが、のちほど、回答に追記しておきます。 何かの手掛かりになれば。
guest

0

自己解決

自己解決しました!
Meganezaru様、ご協力いただきありがとうございました!

投稿2018/08/19 20:42

nokonoko_1203

総合スコア17

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

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

Meganezaru

2018/08/20 02:16

おつかれさまでした。 解決してよかったです(^ ^) よろしければ、後続の方のために、解決方法の簡単なまとめを記述してもらえると、良いと思います。 私も勉強させてもらいたいですしね。
nokonoko_1203

2018/08/20 02:29

ごめんなさい! 記載するのを忘れていました! 後程記載させていただきますので、ご覧ください!
guest

0

djangoのformってややこしいですよね・・・
getで表示するformとpostで生成するformは全く別のインスタンスだってことを理解するのに苦労しました笑

ざっくりな流れで書くと、

postに対するViewの呼び出しで、
1.form(postオブジェクトでinitailize)を生成し、
2.そのformでバリデーションした後(saveもこのタイミング)、
3.formからcleaned_dateを取り出し、
4.その値を利用してPurchase_historyモデルに新規レコードを作成する。

という感じになると思います。

historyに書いても良いかどうかの、特殊なバリデーションが必要じゃなければ、Purchase_historyのformは不要だと思います。必要であれば、4のタイミングで、そのformを生成してバリデーションして、save、という流れで良いのではないでしょうか。

投稿2018/07/12 04:50

Meganezaru

総合スコア715

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

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

nokonoko_1203

2018/07/12 14:00

ご回答ありがとうございます。 非常にややこしくて困っておりました… cleaned_dateについていろいろ調べてみたのですが、そもそも前提知識が足りないのかよく理解することが出来なかったので、お手数でなければ具体的な解決方法をご教示いただけるとありがたいです。 調べた中では、forms.pyの中でforms.ModelFormをオーバーライドしているのですが、自分のコードではmodelformset_factoryを利用しているので、記述の仕方が異なっており理解することができませんでした。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問