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

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

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

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

MacOS(OSX)

MacOSとは、Appleの開発していたGUI(グラフィカルユーザーインターフェース)を採用したオペレーションシステム(OS)です。Macintoshと共に、市場に出てGUIの普及に大きく貢献しました。

Python

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

Q&A

0回答

1199閲覧

[Django]NOT NULL constraint failed: の解決方法がわからない

kawataku

総合スコア3

Django

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

MacOS(OSX)

MacOSとは、Appleの開発していたGUI(グラフィカルユーザーインターフェース)を採用したオペレーションシステム(OS)です。Macintoshと共に、市場に出てGUIの普及に大きく貢献しました。

Python

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

0グッド

0クリップ

投稿2021/07/14 05:30

編集2021/07/16 02:21

前提・実現したいこと

実現したいこととしましては、modelformsetを使用して、Update画面でレコードの編集や追加、削除を行えるようになりたいです。

Djangoでwebアプリを作成しているのですが、Updateの画面において、更新を上手く行えません。

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

OtherSpotというモデルがありまして、

  • Otherはedit画面において、新しいレコードの追加はできますが、既存のレコードを編集することができません。
  • Spotはedit画面において、新しいレコードの追加をしたら、エラーになり、既存のレコードは編集できない状態です。

jQueryを使って動的にフォームを増減させているのですが、Update画面でフォームを追加して保存することも、既存のレコードを編集することもできません。
フォームを追加して保存すると、下のようなエラーメッセージが出てきます。

NOT NULL constraint failed: make_trip_spot.spot_cost

該当のソースコード

models.py

django

1class Other(models.Model): 2 trip = models.ForeignKey(Trip, on_delete=models.CASCADE, related_name='extra') 3 extra_name = models.CharField(max_length=50) 4 extra_cost = models.IntegerField(validators=[MinValueValidator(0, '0以上で入力してください')]) 5 6class Spot(models.Model): 7 trip = models.ForeignKey(Trip, on_delete=models.CASCADE, related_name='spot') 8 spot_name = models.CharField(null=False, blank=False, max_length=50) 9 spot_time = models.DateTimeField(blank=False, null=False) 10 spot_cost = models.IntegerField(null=False, blank=False, validators=[MinValueValidator(0, '0以上で入力してください')])

forms.py

django

1class OtherForm(forms.ModelForm): 2 def __init__(self, *args, **kwargs): 3 super().__init__(*args, **kwargs) 4 self.fields['extra_name'].required = False 5 self.fields['extra_cost'].required = False 6 7 class Meta: 8 model = Other 9 fields = ('extra_name', 'extra_cost') 10 widgets = { 11 'extra_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '追加の費用の項目', 'name': 'extra_menu'}), 12 'extra_cost': forms.NumberInput(attrs={'class': 'form-control costs', 'placeholder': '追加の費用', 'name': 'extra_cost'}) 13 } 14 15class SpotForm(forms.ModelForm): 16 def __init__(self, *args, **kwargs): 17 super().__init__(*args, **kwargs) 18 self.fields['transport_name'].required = False 19 self.fields['transport_fee'].required = False 20 self.fields['transport_time'].required = False 21 22 class Meta: 23 model = Spot 24 fields = ('spot_name', 'spot_time', 'spot_cost') 25 widgets = { 26 'spot_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '観光地', 'name': 'spot_name'}), 27 'spot_time': forms.DateTimeInput(attrs={'class': 'form-control smallDate', 'placeholder': '到着時間', 'autocomplete': 'off', 'name': 'spot_time'}), 28 'spot_cost': forms.NumberInput(attrs={'class': 'form-control costs', 'placeholder': '滞在料金', 'name': 'spot_cost'}) 29 }

モデルとフォームはそこまで特別なことはしてないと思いますので、そのまま載せておきます。

views.py

django

1def test_edit(request, num): 2 OtherFormSet = forms.modelformset_factory( 3 Other, form=OtherForm, extra=0, can_delete=True 4 ) 5 SpotFormSet = forms.modelformset_factory( 6 Spot, form=SpotForm, extra=0, can_delete=True 7 ) 8 trip_model = Trip.objects.get(id=num) 9 10 other_model = Other.objects.filter(trip=trip_model) 11 spot_model = Spot.objects.filter(trip=trip_model) 12 13 if request.method == 'POST': 14 others = OtherFormSet(request.POST, queryset=other_model) 15 spots = SpotFormSet(request.POST, queryset=spot_model) 16 17 for other in others: 18 if other.is_valid(): 19 other_db = other.save(commit=False) 20 other_db.trip = trip_model 21 other_db.save() 22 23 for spot in spots: 24 if spot.is_valid(): 25 spot_db = spot.save(commit=False) 26 spot_db.trip = trip_model 27 spot_db.save() 28 29 return redirect(to='/make_trip/myPage') 30 31 else: 32 spots = SpotFormSet(queryset=spot_model) 33 others = OtherFormSet(queryset=other_model) 34 35 36 params = { 37 'others': others, 38 'spots': spots, 39 'id': num 40 } 41 return render(request, 'make_trip/test.html', params)

views.pyではformsetを使用しており、Create画面で入力した情報をUpdate画面の初期値として表示するようにしています。

template

django

1{% block container %} 2{% load boost %} 3<form action="{% url 'test_edit' id %}" method="post"> 4 {% csrf_token %} 5 <div id="tran_spot_total"> 6 {{ spots.management_form }} 7 <div id="tranSpot_formset"> 8 {% for spot in spots %} 9 <div id="input-spot-form-{{ forloop.counter0 }}"> 10 {{ spot.spot_name }} 11 {{ spot.spot_time }} 12 {{ spot.spot_cost }} 13 </div> 14 {% endfor %} 15 </div> 16 <div id="tranSpot"></div> 17 </div> 18 <button type="button" id="addSpot">観光地を追加</button> 19 <button type="button" id="delSpot">観光地を削除</button> 20 <div class="row mt-4" id="other_total"> 21 <div class="col-12"> 22 {{ others.management_form }} 23 <div id="other_formset"> 24 {% for other in others %} 25 <div class="row mb-3" id="input-form-{{ forloop.counter0 }}"> 26 <div class="col-6">{{ other.extra_name }}</div> 27 <div class="col-6"> 28 {{ other.extra_cost }} 29 </div> 30 </div> 31 {% endfor %} 32 </div> 33 <div id="extra"></div> 34 </div> 35 </div> 36 <button class="btn btn-outline-dark mr-3" type="button" id="addExtra">費用を追加</button> 37 <button class="btn btn-outline-dark" type="button" id="delExtra">費用を削除</button> 38 <button type="submit">完成!</button> 39</form> 40{% endblock %} 41 42{% block js %} 43<script> 44 $(function(){ 45 $.datetimepicker.setLocale('ja'); 46 $('.bigDate').datetimepicker({ 47 timepicker: false, 48 format: 'Y-m-d', 49 }); 50 $('.smallDate').datetimepicker({ 51 step: 15, 52 format: 'Y-m-d H:i', 53 }); 54 55 var TotalManageElement = $('input#id_form-TOTAL_FORMS'); 56 var currentCount = parseInt(TotalManageElement.val()); 57 // spot用の処理 58 $('#addSpot').on('click', function(){ 59 // var new_tranSpot = $('#tranSpot_hidden').html(); 60 var new_tranSpot = `<div class="row mb-5" id="input-spot-form-0"> 61 <div class="col-4"> 62 <input type="text" name="form-0-spot_name" class="form-control" placeholder="観光地" maxlength="50" id="id_form-0-spot_name"> 63 </div> 64 <div class="col-4"> 65 <input type="text" name="form-0-spot_time" class="form-control smallDate" placeholder="到着時間" autocomplete="off" id="id_form-0-spot_time"> 66 </div> 67 <div class="col-4"> 68 <input type="number" name="form-0-spot_cost" class="form-control costs" placeholder="滞在料金" id="id_form-0-spot_cost"> 69 </div> 70 </div>` 71 currentCount += 1 72 new_tranSpot = new_tranSpot.replace(/form-0/g, 'form-'+currentCount); 73 $('#tranSpot').append(new_tranSpot); 74 $('input#id_form-'+ currentCount +'-spot_cost').removeClass('costs').addClass('tranSpot_costs'); 75 TotalManageElement.attr('value', currentCount); 76 77 $('.smallDate').datetimepicker({ 78 step: 15, 79 format: 'Y-m-d H:i', 80 }); 81 }); 82 // フォームを削除する 83 $('#delSpot').on('click', function(){ 84 if ($('#tranSpot').children().length){ 85 console.log('world'); 86 // 削除されるフォームの金額を消す 87 var money = Number($('#tranSpot').children('div:last').find('.tranSpot_costs').val()); 88 $('#tranSpot').children('div:last').remove(); 89 } 90 }); 91// other用の処理 92 $('#addExtra').on('click', function(){ 93 var new_code = `<div class="row mb-3" id="input-form-0"> 94 <div class="col-6"><input type="text" name="form-0-extra_name" class="form-control" placeholder="追加の費用の項目" maxlength="50" id="id_form-0-extra_name"></div> 95 <div class="col-6"><input type="number" name="form-0-extra_cost" class="form-control costs" placeholder="追加の費用" id="id_form-0-extra_cost"></div> 96 </div>` 97 new_code = new_code.replace(/form-0/g, 'form-'+currentCount); 98 $('#extra').append(new_code); 99 $('input#id_form-'+ currentCount +'-extra_cost').removeClass('costs').addClass('extra_costs'); 100 currentCount += 1 101 TotalManageElement.attr('value', currentCount); 102 }); 103 $('#delExtra').on('click', function(){ 104 // other_formsetかextraかをチェックする 105 if ($('#extra').children().length){ 106 $('#extra').children('div:last').remove(); 107 currentCount -= 1 108 TotalManageElement.attr('value', currentCount); 109 }else if($('#other_formset').children().length == 1){ 110 return ; 111 }else{ 112 $('#other_formset').children('div:last').remove(); 113 currentCount -= 1 114 TotalManageElement.attr('value', currentCount); 115 }; 116 }); 117 }); 118</script> 119{% endblock %}

template内でformsetを回しております。
また、jQueryを使用して、フォームの増減を行っております。

どなたか手助けして頂ければありがたいです。

試したこと

DateTimeFieldが問題かと思い、削除して試してみたのですが、上手くいきませんでした。

また、Createの時のようにqueryset=Spot.objects.none()に変更してみたのですが、保存できませんでした。

管理画面にて直接レコードを保存することはできましたが、editの時は上手くいきませんでした。

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

request.POSTの中を見てみたのですが、その時は、入力情報を受け取ることはできていました。

SpotOtherCreate画面において、レコードの保存はできます。

Python v3.7.7
Visual Studio Code v1.53.2
MacOS Darwin x64 20.3.0
Django 3.0.4

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

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

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

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

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

guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだ回答がついていません

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

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

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問