質問編集履歴
1
参考までにOtherモデルも追加しました。
test
CHANGED
File without changes
|
test
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
### 前提・実現したいこと
|
2
2
|
|
3
|
+
実現したいこととしましては、`modelformset`を使用して、`Update`画面でレコードの編集や追加、削除を行えるようになりたいです。
|
4
|
+
|
5
|
+
|
6
|
+
|
3
7
|
`Django`でwebアプリを作成しているのですが、`Update`の画面において、更新を上手く行えません。
|
4
8
|
|
5
9
|
|
6
10
|
|
7
|
-
また、`can_delete`を`True`にしており、`template`内に、`{{spot.DELETE}}`を書いたのですが、チェックボックスが出るのみで削除ができませんでした。
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
実現したいこととしましては、
|
12
|
-
|
13
|
-
`Update`画面でレコードの編集や追加、削除を行えるようになりたいです。
|
14
|
-
|
15
|
-
よろしくお願い致します。
|
16
|
-
|
17
|
-
|
18
|
-
|
19
11
|
### 発生している問題・エラーメッセージ
|
20
12
|
|
13
|
+
`Other`と`Spot`というモデルがありまして、
|
14
|
+
|
15
|
+
* Otherは`edit`画面において、新しいレコードの追加はできますが、既存のレコードを編集することができません。
|
16
|
+
|
17
|
+
* Spotは`edit`画面において、新しいレコードの追加をしたら、エラーになり、既存のレコードは編集できない状態です。
|
18
|
+
|
19
|
+
|
20
|
+
|
21
21
|
|
22
22
|
|
23
23
|
`jQuery`を使って動的にフォームを増減させているのですが、`Update`画面でフォームを追加して保存することも、既存のレコードを編集することもできません。
|
@@ -30,8 +30,6 @@
|
|
30
30
|
|
31
31
|
```
|
32
32
|
|
33
|
-
このモデルとほぼ同じで、`DateTimeField`が無いモデルでは上手くいったのですが、今回のモデルでは上手くいきません。
|
34
|
-
|
35
33
|
|
36
34
|
|
37
35
|
### 該当のソースコード
|
@@ -42,6 +40,16 @@
|
|
42
40
|
|
43
41
|
```django
|
44
42
|
|
43
|
+
class Other(models.Model):
|
44
|
+
|
45
|
+
trip = models.ForeignKey(Trip, on_delete=models.CASCADE, related_name='extra')
|
46
|
+
|
47
|
+
extra_name = models.CharField(max_length=50)
|
48
|
+
|
49
|
+
extra_cost = models.IntegerField(validators=[MinValueValidator(0, '0以上で入力してください')])
|
50
|
+
|
51
|
+
|
52
|
+
|
45
53
|
class Spot(models.Model):
|
46
54
|
|
47
55
|
trip = models.ForeignKey(Trip, on_delete=models.CASCADE, related_name='spot')
|
@@ -52,12 +60,6 @@
|
|
52
60
|
|
53
61
|
spot_cost = models.IntegerField(null=False, blank=False, validators=[MinValueValidator(0, '0以上で入力してください')])
|
54
62
|
|
55
|
-
|
56
|
-
|
57
|
-
def __str__(self):
|
58
|
-
|
59
|
-
return str(self.spot_name) + ' (' + str(self.spot_time) + '+' + str(self.spot_cost) + ')'
|
60
|
-
|
61
63
|
```
|
62
64
|
|
63
65
|
|
@@ -68,12 +70,46 @@
|
|
68
70
|
|
69
71
|
```django
|
70
72
|
|
73
|
+
class OtherForm(forms.ModelForm):
|
74
|
+
|
75
|
+
def __init__(self, *args, **kwargs):
|
76
|
+
|
77
|
+
super().__init__(*args, **kwargs)
|
78
|
+
|
79
|
+
self.fields['extra_name'].required = False
|
80
|
+
|
81
|
+
self.fields['extra_cost'].required = False
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
class Meta:
|
86
|
+
|
87
|
+
model = Other
|
88
|
+
|
89
|
+
fields = ('extra_name', 'extra_cost')
|
90
|
+
|
91
|
+
widgets = {
|
92
|
+
|
93
|
+
'extra_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '追加の費用の項目', 'name': 'extra_menu'}),
|
94
|
+
|
95
|
+
'extra_cost': forms.NumberInput(attrs={'class': 'form-control costs', 'placeholder': '追加の費用', 'name': 'extra_cost'})
|
96
|
+
|
97
|
+
}
|
98
|
+
|
99
|
+
|
100
|
+
|
71
101
|
class SpotForm(forms.ModelForm):
|
72
102
|
|
73
103
|
def __init__(self, *args, **kwargs):
|
74
104
|
|
75
105
|
super().__init__(*args, **kwargs)
|
76
106
|
|
107
|
+
self.fields['transport_name'].required = False
|
108
|
+
|
109
|
+
self.fields['transport_fee'].required = False
|
110
|
+
|
111
|
+
self.fields['transport_time'].required = False
|
112
|
+
|
77
113
|
|
78
114
|
|
79
115
|
class Meta:
|
@@ -106,27 +142,45 @@
|
|
106
142
|
|
107
143
|
def test_edit(request, num):
|
108
144
|
|
145
|
+
OtherFormSet = forms.modelformset_factory(
|
146
|
+
|
147
|
+
Other, form=OtherForm, extra=0, can_delete=True
|
148
|
+
|
149
|
+
)
|
150
|
+
|
151
|
+
SpotFormSet = forms.modelformset_factory(
|
152
|
+
|
153
|
+
Spot, form=SpotForm, extra=0, can_delete=True
|
154
|
+
|
155
|
+
)
|
156
|
+
|
157
|
+
trip_model = Trip.objects.get(id=num)
|
158
|
+
|
159
|
+
|
160
|
+
|
161
|
+
other_model = Other.objects.filter(trip=trip_model)
|
162
|
+
|
109
163
|
spot_model = Spot.objects.filter(trip=trip_model)
|
110
164
|
|
111
165
|
|
112
166
|
|
113
|
-
SpotFormSet = forms.modelformset_factory(
|
114
|
-
|
115
|
-
Spot, form=SpotForm, extra=0, can_delete=True
|
116
|
-
|
117
|
-
)
|
118
|
-
|
119
|
-
# idがnumのtripを取得する
|
120
|
-
|
121
167
|
if request.method == 'POST':
|
122
168
|
|
123
|
-
|
169
|
+
others = OtherFormSet(request.POST, queryset=other_model)
|
124
170
|
|
125
171
|
spots = SpotFormSet(request.POST, queryset=spot_model)
|
126
172
|
|
173
|
+
|
174
|
+
|
175
|
+
for other in others:
|
176
|
+
|
177
|
+
if other.is_valid():
|
178
|
+
|
179
|
+
other_db = other.save(commit=False)
|
180
|
+
|
127
|
-
pri
|
181
|
+
other_db.trip = trip_model
|
128
|
-
|
182
|
+
|
129
|
-
|
183
|
+
other_db.save()
|
130
184
|
|
131
185
|
|
132
186
|
|
@@ -146,16 +200,20 @@
|
|
146
200
|
|
147
201
|
|
148
202
|
|
149
|
-
# Getアクセス時の処理
|
150
|
-
|
151
203
|
else:
|
152
204
|
|
153
205
|
spots = SpotFormSet(queryset=spot_model)
|
154
206
|
|
207
|
+
others = OtherFormSet(queryset=other_model)
|
208
|
+
|
209
|
+
|
210
|
+
|
155
211
|
|
156
212
|
|
157
213
|
params = {
|
158
214
|
|
215
|
+
'others': others,
|
216
|
+
|
159
217
|
'spots': spots,
|
160
218
|
|
161
219
|
'id': num
|
@@ -214,9 +272,45 @@
|
|
214
272
|
|
215
273
|
<button type="button" id="delSpot">観光地を削除</button>
|
216
274
|
|
275
|
+
<div class="row mt-4" id="other_total">
|
276
|
+
|
277
|
+
<div class="col-12">
|
278
|
+
|
279
|
+
{{ others.management_form }}
|
280
|
+
|
281
|
+
<div id="other_formset">
|
282
|
+
|
283
|
+
{% for other in others %}
|
284
|
+
|
285
|
+
<div class="row mb-3" id="input-form-{{ forloop.counter0 }}">
|
286
|
+
|
287
|
+
<div class="col-6">{{ other.extra_name }}</div>
|
288
|
+
|
289
|
+
<div class="col-6">
|
290
|
+
|
291
|
+
{{ other.extra_cost }}
|
292
|
+
|
293
|
+
</div>
|
294
|
+
|
295
|
+
</div>
|
296
|
+
|
297
|
+
{% endfor %}
|
298
|
+
|
299
|
+
</div>
|
300
|
+
|
301
|
+
<div id="extra"></div>
|
302
|
+
|
303
|
+
</div>
|
304
|
+
|
305
|
+
</div>
|
306
|
+
|
307
|
+
<button class="btn btn-outline-dark mr-3" type="button" id="addExtra">費用を追加</button>
|
308
|
+
|
309
|
+
<button class="btn btn-outline-dark" type="button" id="delExtra">費用を削除</button>
|
310
|
+
|
217
311
|
<button type="submit">完成!</button>
|
218
312
|
|
219
|
-
|
313
|
+
</form>
|
220
314
|
|
221
315
|
{% endblock %}
|
222
316
|
|
@@ -236,8 +330,6 @@
|
|
236
330
|
|
237
331
|
format: 'Y-m-d',
|
238
332
|
|
239
|
-
// numberOfMonths: 2
|
240
|
-
|
241
333
|
});
|
242
334
|
|
243
335
|
$('.smallDate').datetimepicker({
|
@@ -248,176 +340,132 @@
|
|
248
340
|
|
249
341
|
});
|
250
342
|
|
343
|
+
|
344
|
+
|
345
|
+
var TotalManageElement = $('input#id_form-TOTAL_FORMS');
|
346
|
+
|
347
|
+
var currentCount = parseInt(TotalManageElement.val());
|
348
|
+
|
349
|
+
// spot用の処理
|
350
|
+
|
251
|
-
$('
|
351
|
+
$('#addSpot').on('click', function(){
|
352
|
+
|
252
|
-
|
353
|
+
// var new_tranSpot = $('#tranSpot_hidden').html();
|
354
|
+
|
355
|
+
var new_tranSpot = `<div class="row mb-5" id="input-spot-form-0">
|
356
|
+
|
253
|
-
|
357
|
+
<div class="col-4">
|
358
|
+
|
254
|
-
|
359
|
+
<input type="text" name="form-0-spot_name" class="form-control" placeholder="観光地" maxlength="50" id="id_form-0-spot_name">
|
360
|
+
|
361
|
+
</div>
|
362
|
+
|
363
|
+
<div class="col-4">
|
364
|
+
|
365
|
+
<input type="text" name="form-0-spot_time" class="form-control smallDate" placeholder="到着時間" autocomplete="off" id="id_form-0-spot_time">
|
366
|
+
|
367
|
+
</div>
|
368
|
+
|
369
|
+
<div class="col-4">
|
370
|
+
|
371
|
+
<input type="number" name="form-0-spot_cost" class="form-control costs" placeholder="滞在料金" id="id_form-0-spot_cost">
|
372
|
+
|
373
|
+
</div>
|
374
|
+
|
375
|
+
</div>`
|
376
|
+
|
255
|
-
|
377
|
+
currentCount += 1
|
378
|
+
|
256
|
-
|
379
|
+
new_tranSpot = new_tranSpot.replace(/form-0/g, 'form-'+currentCount);
|
380
|
+
|
381
|
+
$('#tranSpot').append(new_tranSpot);
|
382
|
+
|
383
|
+
$('input#id_form-'+ currentCount +'-spot_cost').removeClass('costs').addClass('tranSpot_costs');
|
384
|
+
|
257
|
-
|
385
|
+
TotalManageElement.attr('value', currentCount);
|
258
|
-
|
386
|
+
|
387
|
+
|
388
|
+
|
259
|
-
|
389
|
+
$('.smallDate').datetimepicker({
|
390
|
+
|
391
|
+
step: 15,
|
392
|
+
|
393
|
+
format: 'Y-m-d H:i',
|
394
|
+
|
395
|
+
});
|
396
|
+
|
397
|
+
});
|
398
|
+
|
399
|
+
// フォームを削除する
|
400
|
+
|
401
|
+
$('#delSpot').on('click', function(){
|
402
|
+
|
403
|
+
if ($('#tranSpot').children().length){
|
404
|
+
|
405
|
+
console.log('world');
|
406
|
+
|
407
|
+
// 削除されるフォームの金額を消す
|
408
|
+
|
409
|
+
var money = Number($('#tranSpot').children('div:last').find('.tranSpot_costs').val());
|
410
|
+
|
411
|
+
$('#tranSpot').children('div:last').remove();
|
260
412
|
|
261
413
|
}
|
262
414
|
|
263
415
|
});
|
264
416
|
|
417
|
+
// other用の処理
|
418
|
+
|
265
|
-
$('
|
419
|
+
$('#addExtra').on('click', function(){
|
420
|
+
|
266
|
-
|
421
|
+
var new_code = `<div class="row mb-3" id="input-form-0">
|
422
|
+
|
267
|
-
|
423
|
+
<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>
|
424
|
+
|
268
|
-
|
425
|
+
<div class="col-6"><input type="number" name="form-0-extra_cost" class="form-control costs" placeholder="追加の費用" id="id_form-0-extra_cost"></div>
|
426
|
+
|
427
|
+
</div>`
|
428
|
+
|
269
|
-
|
429
|
+
new_code = new_code.replace(/form-0/g, 'form-'+currentCount);
|
270
|
-
|
430
|
+
|
271
|
-
$('#a
|
431
|
+
$('#extra').append(new_code);
|
432
|
+
|
433
|
+
$('input#id_form-'+ currentCount +'-extra_cost').removeClass('costs').addClass('extra_costs');
|
434
|
+
|
435
|
+
currentCount += 1
|
436
|
+
|
437
|
+
TotalManageElement.attr('value', currentCount);
|
272
438
|
|
273
439
|
});
|
274
440
|
|
275
|
-
|
276
|
-
|
277
|
-
var TotalManageElement = $('input#id_form-TOTAL_FORMS');
|
278
|
-
|
279
|
-
var currentCount = parseInt(TotalManageElement.val());
|
280
|
-
|
281
|
-
// spot用の処理
|
282
|
-
|
283
|
-
$('#
|
441
|
+
$('#delExtra').on('click', function(){
|
442
|
+
|
284
|
-
|
443
|
+
// other_formsetかextraかをチェックする
|
444
|
+
|
445
|
+
if ($('#extra').children().length){
|
446
|
+
|
285
|
-
|
447
|
+
$('#extra').children('div:last').remove();
|
286
|
-
|
287
|
-
|
448
|
+
|
288
|
-
|
289
|
-
<div class="col-4">
|
290
|
-
|
291
|
-
<input type="text" name="form-0-spot_name" class="form-control" placeholder="観光地" maxlength="50" id="id_form-0-spot_name">
|
292
|
-
|
293
|
-
</div>
|
294
|
-
|
295
|
-
<div class="col-4">
|
296
|
-
|
297
|
-
<input type="text" name="form-0-spot_time" class="form-control smallDate" placeholder="到着時間" autocomplete="off" id="id_form-0-spot_time">
|
298
|
-
|
299
|
-
</div>
|
300
|
-
|
301
|
-
<div class="col-4">
|
302
|
-
|
303
|
-
<input type="number" name="form-0-spot_cost" class="form-control costs" placeholder="滞在料金" id="id_form-0-spot_cost">
|
304
|
-
|
305
|
-
</div>
|
306
|
-
|
307
|
-
</div>`
|
308
|
-
|
309
|
-
currentCount
|
449
|
+
currentCount -= 1
|
310
|
-
|
311
|
-
|
450
|
+
|
312
|
-
|
313
|
-
$('#tranSpot').append(new_tranSpot);
|
314
|
-
|
315
|
-
$('input#id_form-'+ currentCount +'-spot_cost').removeClass('costs').addClass('tranSpot_costs');
|
316
|
-
|
317
|
-
TotalManageElement.attr('value', currentCount);
|
451
|
+
TotalManageElement.attr('value', currentCount);
|
318
|
-
|
319
|
-
|
320
|
-
|
452
|
+
|
321
|
-
$('
|
453
|
+
}else if($('#other_formset').children().length == 1){
|
322
|
-
|
454
|
+
|
323
|
-
|
455
|
+
return ;
|
456
|
+
|
324
|
-
|
457
|
+
}else{
|
458
|
+
|
459
|
+
$('#other_formset').children('div:last').remove();
|
460
|
+
|
461
|
+
currentCount -= 1
|
462
|
+
|
325
|
-
|
463
|
+
TotalManageElement.attr('value', currentCount);
|
326
|
-
|
464
|
+
|
327
|
-
}
|
465
|
+
};
|
328
|
-
|
329
|
-
$('.tranSpot_costs').on('focus', function(){
|
330
|
-
|
331
|
-
var cost = Number($(this).val());
|
332
|
-
|
333
|
-
if(cost != NaN){
|
334
|
-
|
335
|
-
var costs = Number($('#all_money').text()) - cost
|
336
|
-
|
337
|
-
$('#all_money').text(costs);
|
338
|
-
|
339
|
-
}
|
340
|
-
|
341
|
-
});
|
342
|
-
|
343
|
-
$('.tranSpot_costs').on('blur', function(){
|
344
|
-
|
345
|
-
var cost = Number($('#all_money').text())+ Number($(this).val());
|
346
|
-
|
347
|
-
$('#all_money').text(cost);
|
348
|
-
|
349
|
-
});
|
350
466
|
|
351
467
|
});
|
352
468
|
|
353
|
-
// フォームを削除する
|
354
|
-
|
355
|
-
$('#delSpot').on('click', function(){
|
356
|
-
|
357
|
-
if ($('#tranSpot').children().length){
|
358
|
-
|
359
|
-
console.log('world');
|
360
|
-
|
361
|
-
// 削除されるフォームの金額を消す
|
362
|
-
|
363
|
-
var money = Number($('#tranSpot').children('div:last').find('.tranSpot_costs').val());
|
364
|
-
|
365
|
-
$('#tranSpot').children('div:last').remove();
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
// 削除されるフォームの金額分を差し引く
|
370
|
-
|
371
|
-
if(money != NaN) {
|
372
|
-
|
373
|
-
var costs = Number($('#all_money').text()) - money
|
374
|
-
|
375
|
-
}else{
|
376
|
-
|
377
|
-
var costs = Number($('#all_money').text())
|
378
|
-
|
379
|
-
}
|
380
|
-
|
381
|
-
// 合計金額が0を下回らないように一応書いておく
|
382
|
-
|
383
|
-
if (costs <= 0) { $('#all_money').text('0'); }
|
384
|
-
|
385
|
-
$('#all_money').text(costs);
|
386
|
-
|
387
|
-
}else if($('#tranSpot_formset').children().length == 1){
|
388
|
-
|
389
|
-
return ;
|
390
|
-
|
391
|
-
}else{
|
392
|
-
|
393
|
-
console.log('Hello');
|
394
|
-
|
395
|
-
// 削除されるフォームの金額を消す
|
396
|
-
|
397
|
-
var money = Number($('#tranSpot_formset').children('div:last').find('.costs').val());
|
398
|
-
|
399
|
-
$('#tranSpot_formset').children('div:last').remove();
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
// 削除されるフォームの金額分を差し引く
|
404
|
-
|
405
|
-
console.log('money:', money);
|
406
|
-
|
407
|
-
var costs = Number($('#all_money').text()) - money
|
408
|
-
|
409
|
-
// 合計金額が0を下回らないように一応書いておく
|
410
|
-
|
411
|
-
if (costs <= 0) { $('#all_money').text('0'); }
|
412
|
-
|
413
|
-
$('#all_money').text(costs);
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
}
|
418
|
-
|
419
|
-
});
|
420
|
-
|
421
469
|
});
|
422
470
|
|
423
471
|
</script>
|
@@ -426,6 +474,10 @@
|
|
426
474
|
|
427
475
|
```
|
428
476
|
|
477
|
+
|
478
|
+
|
479
|
+
|
480
|
+
|
429
481
|
template内で`formset`を回しております。
|
430
482
|
|
431
483
|
また、`jQuery`を使用して、フォームの増減を行っております。
|
@@ -438,16 +490,30 @@
|
|
438
490
|
|
439
491
|
### 試したこと
|
440
492
|
|
493
|
+
|
494
|
+
|
441
|
-
|
495
|
+
`DateTimeField`が問題かと思い、削除して試してみたのですが、上手くいきませんでした。
|
442
|
-
|
443
|
-
|
444
|
-
|
496
|
+
|
497
|
+
|
498
|
+
|
445
|
-
また、`
|
499
|
+
また、`Create`の時のように`queryset=Spot.objects.none()`に変更してみたのですが、保存できませんでした。
|
500
|
+
|
501
|
+
|
502
|
+
|
503
|
+
管理画面にて直接レコードを保存することはできましたが、editの時は上手くいきませんでした。
|
446
504
|
|
447
505
|
|
448
506
|
|
449
507
|
### 補足情報(FW/ツールのバージョンなど)
|
450
508
|
|
509
|
+
`request.POST`の中を見てみたのですが、その時は、入力情報を受け取ることはできていました。
|
510
|
+
|
511
|
+
|
512
|
+
|
513
|
+
`Spot`も`Other`も`Create`画面において、レコードの保存はできます。
|
514
|
+
|
515
|
+
|
516
|
+
|
451
517
|
Python v3.7.7
|
452
518
|
|
453
519
|
Visual Studio Code v1.53.2
|