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

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

ただいまの
回答率

90.34%

  • Python

    9289questions

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

  • Django

    1188questions

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

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

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 309

nokonoko_1203

score 11

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

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

#models.py

class Shopping_list(models.Model):
    name = models.CharField(max_length=64)
    price = models.IntegerField(null=True, blank=True)

    def __str__(self):
        return '%s' % (self.name)


class Purchase_history(models.Model):
    name = models.CharField(max_length=64)
    price = models.IntegerField(null=True, blank=True)

    def __str__(self):
        return '%s' % (self.name)
<body>

    {% if error_list %}
    <ul>
    {% for v in error_list %}
        <li>{{ v }}</li>
    {% endfor %}
    </ul>
    {% endif %}

    <h3>買い物リストを編集する</h3>

    <form method="post">
    <!--ミドルウェアに「django.middleware.csrf.CsrfViewMiddleware」を指定しているとcsrf_tokenを渡していないPOSTメソッドは403エラーになる-->
    {{ form.management_form }} 
    {% csrf_token %}
    <div class="shopping_list">
    {% for fm in form %}
    {{ fm.id }}
        <div class="shopping_list_one">
            <p>商品名<br/>{{ fm.name }}</p>
            <br/>
            <p>金額<br/>{{ fm.price }}</p>
        <p class="delete_check_box">削除:{{ fm.DELETE }}</p>
    </div>
    {% endfor %}    
        <button type="submit">リストを更新</button>
    </form>

    <a href="{% url "stockpile:index" %}">トップに戻る</a>

        <h4 id="week_menu_list">今週のメニュー</h4>
        <table>
        <tr>
            <th></th>
            <th></th>
            <th></th>
            <th></th>
            <th></th>
            <th></th>
            <th></th>
        </tr>
        {% for v in Week_menu %}
        <tr>
            <td>{{ v.mon }}</td>
            <td>{{ v.tue }}</td>
            <td>{{ v.wed }}</td>
            <td>{{ v.thu }}</td>
            <td>{{ v.fri }}</td>
            <td>{{ v.sat }}</td>
            <td>{{ v.sun }}</td>
        </tr>
        {% endfor %}

</body>


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

#forms.py

class Shopping_listForm(ModelForm):

    class Meta:
        model = Shopping_list
        fields = ("name", "price")


Shopping_listFormSet = modelformset_factory(
    Shopping_list,
    form=Shopping_listForm,
    fields=("name", "price"),
    extra=3,
    can_delete=True
    )


class Purchase_historyForm(ModelForm):

    class Meta:
        model = Purchase_history
        fields = ("name", "price")
# Views.py

class Shopping_listCreateView(FormView):
    # model = Shopping_list
    # fields = ["name", "price"]
    form_class = Shopping_listFormSet
    template_name = "stockpile/shopping_list_form.html"
    success_url = "/stockpile/shopping_list/"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['Week_menu'] = Week_menu.objects.all()
        return context

    def form_valid(self, form):
        form.save()  # ここで保存
        return super().form_valid(form)  # リダイレクト処理です。redirect関数等でも良いです。


考えていたのは

・Shopping_listCreateViewでShopping_listモデルに保存

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

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

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

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

# views.py

# Shopping_listView

class Shopping_listView(ListView):
    model = Shopping_list
    paginate_by = 30
    template_name = "stockpile/shopping_list.html"
    queryset = Shopping_list.objects.all()

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        '''
        辞書のリストが帰ってくる
        [{"id": 1, "price":500......}]
        '''
        all_list_price = Shopping_list.objects.all().values("id", "price")
        sum_price = 0
        for v in all_list_price:
            price = v["price"]
            sum_price += price
        context['sum_price'] = sum_price
        return context


class Shopping_listCreateView(FormView):
    # model = Shopping_list
    # fields = ["name", "price"]
    form_class = Shopping_listFormSet
    template_name = "stockpile/shopping_list_form.html"
    success_url = "/stockpile/shopping_list/"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['Week_menu'] = Week_menu.objects.all()
        return context

    def form_valid(self, form):
        form.save()  # ここで保存

        for f in form:
            Purchase_history.objects.create(name=f.cleaned_data["name"],
            price=f.cleaned_data["price"])

        return super().form_valid(form)


def delete_shopping_list(request, shopping_list_id):
    shopping_list = get_object_or_404(Shopping_list, id=shopping_list_id)
    shopping_list.delete()
    shopping_list = Shopping_list.objects.all()
    context = {'object_list': shopping_list}
    return render(request, 'stockpile/shopping_list.html', context)
forms.py

class Shopping_listForm(ModelForm):

    class Meta:
        model = Shopping_list
        fields = ("name", "price")


Shopping_listFormSet = modelformset_factory(
    Shopping_list,
    form=Shopping_listForm,
    fields=("name", "price"),
    extra=3,
    can_delete=True
    )

class Purchase_historyForm(ModelForm):

    class Meta:
        model = Purchase_history
        fields = ("name", "price")
__class__    
<class 'stockpile.views.Shopping_listCreateView'>

f    
<Shopping_listForm bound=True, valid=True, fields=(name;price;id;DELETE)>

form    
<django.forms.formsets.Shopping_listFormFormSet object at 0x7f1e95ec36a0>

self    
<stockpile.views.Shopping_listCreateView object at 0x7f1e95df7518>


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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+1

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

先の回答の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のレコードを作成すれば良いと思います。

  def form_valid(self, form):
    form.save()  # ここで保存

    for cleaned_data in form.cleaned_data:
      Purchase_history.objects.create(name=cleaned_data["name"], price=cleaned_data["price"])

    return super().form_valid(form)

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

 追記

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

class Shopping_listCreateView(FormView):
    # model = Shopping_list
    # fields = ["name", "price"]
    form_class = Shopping_listFormSet
    template_name = "stockpile/shopping_list_form.html"
    success_url = "/stockpile/shopping_list/"

    def get_form_kwargs(self):
        kwargs = super(Shopping_listCreateView, self).get_form_kwargs()
        kwargs["queryset"] = Shopping_list.objects.none()
        return kwargs

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/18 06:01

    詳細なご回答ありがとうございます。
    しかし、御察しの通りエラーが出てしまい、苦戦しております。

    本文に修正したコードを追記しておりますが、ご指摘頂いた通りにform_valid関数の中でfor文を使ってcleaned_dataを取り出そうと試みましたが、「KeyError at /stockpile/add_shopping_list/
    'name'」と、KeyErrorが発生してしまいました。

    キャンセル

  • 2018/07/18 06:10

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

    キャンセル

  • 2018/07/18 09:49

    すいませんでした・・・
    やっぱり、確認せずは、ダメですね・・・

    時間をみて、ちょっと試してみますので、お待ちください。

    djangoのformは、大きく分けて、二つの意味合いがあり、一つは、フォームをHTMLレンダリングするための材料、もう一つは入力データのバリデーションを体系的に行う仕組み、です。(まだあるのかな?w)

    今回の件で言えば、バリデーションが通った、データベースに保存しても問題がないデータ、が、
    cleaned_dataとして取り出せるはずなのですが・・・w

    saveのタイミングでクリアされてしまうんでしょうか??とにかく調べてみます。

    キャンセル

  • 2018/07/19 05:58

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

    キャンセル

  • 2018/07/19 07:15

    今、管理サイトを確認してみると2点問題がありました。
    ・問題なくPurchase_historyにレコードが作成されていたのにも関わらずKeyErrorが発生していた。
    ・Shopping_listを更新、削除した際に全件Purchase_historyに追加されてしまう。

    2番目の問題に関しては後回しにするにしても、最初の問題がなぜこうなるのか理解できず困っておりました。

    教えて頂いたようなやり方でfオブジェクトからデータを取り出し、Purchase_historyに追加できているようなのですが、エラーが出てきます…
    うーん…

    キャンセル

  • 2018/07/19 08:50

    なるほど、なんとなくわかった気が・・・

    今回の回答としては、新規登録しかイメージしていませんので、
    毎回、新規レコードが作成されるので、更新・削除の流れでエラーになっている可能性がありますね。

    モデル定義では、nameフィールドにUnique制限をかけてなさそうですが、
    DB上の定義では、Uniqueがかかってる、とかですかね?
    ソースからは詳細不明なのでなんとも言えませんが。

    更新や、削除は、別の処理が必要になりますが、アプリケーションの領域だと思うので、
    何がやりたいかによって、Purchase_historyの扱いが変わってきますね。

    汎用ビューは、使ったことがないので、ちょっと調べてみましたが、
    更新には、UpdateViewから派生させたViewが使えるようです。

    UpdateViewのform_validには、Purchase_historyをupdate(必要であれば)する処理を入れることになると思います。

    削除は、いろいろ手段があるように思いますので・・・

    キャンセル

  • 2018/07/27 08:59

    実績のないことへの回答は、中途半端になってしまうことを反省してます(>_<)

    どうやら、汎用ビューは、FormSetに対応していないようです。
    そのままでは、うまく動かないようで、少し修正が必要なようです。

    またしても、探したレベルの回答で申し訳ないですが、のちほど、回答に追記しておきます。
    何かの手掛かりになれば。

    キャンセル

check解決した方法

0

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/20 11:16

    おつかれさまでした。
    解決してよかったです(^ ^)

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

    キャンセル

  • 2018/08/20 11:29

    ごめんなさい!
    記載するのを忘れていました!

    後程記載させていただきますので、ご覧ください!

    キャンセル

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 23:00

    ご回答ありがとうございます。
    非常にややこしくて困っておりました…

    cleaned_dateについていろいろ調べてみたのですが、そもそも前提知識が足りないのかよく理解することが出来なかったので、お手数でなければ具体的な解決方法をご教示いただけるとありがたいです。

    調べた中では、forms.pyの中でforms.ModelFormをオーバーライドしているのですが、自分のコードではmodelformset_factoryを利用しているので、記述の仕方が異なっており理解することができませんでした。

    キャンセル

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

  • ただいまの回答率 90.34%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • Python

    9289questions

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

  • Django

    1188questions

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