前提・実現したいこと
実現したいこととしましては、modelformset
を使用して、Update
画面でレコードの編集や追加、削除を行えるようになりたいです。
Django
でwebアプリを作成しているのですが、Update
の画面において、更新を上手く行えません。
発生している問題・エラーメッセージ
Other
とSpot
というモデルがありまして、
- 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
の中を見てみたのですが、その時は、入力情報を受け取ることはできていました。
Spot
もOther
もCreate
画面において、レコードの保存はできます。
Python v3.7.7
Visual Studio Code v1.53.2
MacOS Darwin x64 20.3.0
Django 3.0.4
あなたの回答
tips
プレビュー