Удаление 'disabled' при сохранении изменений в наборе форм
Я создаю таблицу с набором форм из кверисета существующих объектов. Несколько полей в таблице должны изменяться, а другие поля должны отображаться, но не изменяться. Для отображения нередактируемых полей я использую виджет с 'disabled' в форме. GET-запрос делает все правильно. Но POST-запрос не сохраняет изменения в разрешенных полях, потому что неразрешенные поля являются обязательными и отключены, что блокирует отправку существующих значений. Я пытался использовать jQuery из example, но это не работает. Более того, я не понимаю, как я могу ссылаться на разные идентификаторы в наборе форм.
Модели:
class Order(models.Model):
car = models.ForeignKey(
Car,
blank=True,
null=True,
on_delete=models.PROTECT,
related_name='car_orders',
)
department = models.ForeignKey(
Department,
blank=False,
null=False,
on_delete=models.PROTECT,
related_name='dep_orders',
)
...
View
def orders_list(request, year, month, day):
orders = Order.objects.filter(
order_date__year=year, order_date__month=month, order_date__day=day
)
if request.method == 'POST':
formset = OrderCloseFormSet(
request.POST or None, queryset=orders, prefix='order'
)
if formset.is_valid():
formset.save(commit=False)
for form in formset:
form.save()
formset = OrderCloseFormSet(queryset=orders, prefix='order')
context = {'orders': orders, 'formset': formset}
return render(request, 'orders/orders_list.html', context)
Форма
class OrderCloseForm(forms.ModelForm):
class Meta:
model = Order
fields = (
'type_car',
'department',
...
)
widgets = {
'car': forms.Select(attrs={'style': 'width: 100%'}),
'department': forms.Select(attrs={'disabled': 'True', 'style': 'width: 100%'}),
...
}
Шаблон
<form method="post" action="{% url 'orders:orders_list' year month day %}">
{% csrf_token %}
{{ formset.management_form }}
{{ formset.non_form_errors.as_ul }}
<table class="table table-order" id="order-formset">
{% for form in formset %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tbody>
<tr>
{% for field in form %}
<td>
{% if field == car %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{% if field == form.route_movement %}
<a href="{% url 'orders:order_detail' form.id.value %}">
{{ field|addclass:'input-box input-select' }}
</a>
{% else %}
{{ field|addclass:'input-box input-select' }}
{% endif %}
</td>
{% endfor %}
</tr>
</tbody>
{% endfor %}
</table>
...
</form>
<script>
$('id_order-0-department').submit(function(e) {
$(':disabled').each(function(e) {
$(this).removeAttr('disabled');
})
});
</script>
В ответ получите id формы в наборе форм со следующим id:
<select name="order-0-department" disabled="True" id="id_order-0-department"></select>
Как сделать таблицу с редактируемыми полями, но сохраняющую данные в неизменных полях.
Установив disabled
, вы только сделаете поле нередактируемым, так что люди все равно смогут подделать POST-запрос, который изменит поле.
Вы должны установить поле на .disabled = True
, это не только пропустит поля, но и не позволит людям подделать POST-запрос, который каким-то образом изменит эти поля, например:
class OrderCloseForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['department'].disabled = True
class Meta:
model = Order
fields = (
'type_car',
'department',
# …
)
widgets = {
'car': forms.Select(attrs={'style': 'width: 100%'}),
'department': forms.Select(attrs={'style': 'width: 100%'}),
# …
}
На самом деле вид намного проще, чем сейчас: вы можете сохранить FormSet
напрямую:
def orders_list(request, year, month, day):
orders = Order.objects.filter(
order_date__year=year, order_date__month=month, order_date__day=day
)
if request.method == 'POST':
formset = OrderCloseFormSet(
request.POST, request.FILES, queryset=orders, prefix='order'
)
if formset.is_valid():
formset.save() # commit=True
return redirect('name-of-some-view')
else:
formset = OrderCloseFormSet(queryset=orders, prefix='order')
context = {'orders': orders, 'formset': formset}
return render(request, 'orders/orders_list.html', context)
Note: In case of a successful POST request, you should make a
redirect
[Django-doc] to implement the Post/Redirect/Get pattern [wiki]. This avoids that you make the same POST request when the user refreshes the browser.