Django: первая запись данных не сохраняется, когда поле = ноль
Я новичок в Django и сейчас пытаюсь найти причину, по которой первая запись данных из formset
не сохраняется (включается) в базу данных, когда числовое поле poQty
модели ProductOrder
равно нулю.
QueryDict
, кажется, работает с 4 записями в этом примере, но первая запись не включается в базу данных, когда productorder_set-0-poQty
равен нулю:
<QueryDict: {'csrfmiddlewaretoken': ['6b85qEA4LMz7f3lcUvlpAi74BATLiouApAE1AKLMQhz0I7Gohvmw8jhAHnMf82LT'], 'orderOpen': ['on'], 'orderTable': ['02'], 'menuQuery': ['1'],
'productorder_set-TOTAL_FORMS': ['4'], 'productorder_set-INITIAL_FORMS': ['0'], 'productorder_set-MIN_NUM_FORMS': ['0'], 'productorder_set-MAX_NUM_FORMS': ['1000'],
'productorder_set-0-prodQuery': ['1'], 'productorder_set-0-poQty': ['0'], 'productorder_set-0-poOrder': [''], 'productorder_set-0-id': [''],
'productorder_set-1-prodQuery': ['2'], 'productorder_set-1-poQty': ['0'], 'productorder_set-1-poOrder': [''], 'productorder_set-1-id': [''],
'productorder_set-2-prodQuery': ['4'], 'productorder_set-2-poQty': ['0'], 'productorder_set-2-poOrder': [''], 'productorder_set-2-id': [''],
'productorder_set-3-prodQuery': ['3'], 'productorder_set-3-poQty': ['0'], 'productorder_set-3-poOrder': [''], 'productorder_set-3-id': ['']}>
Я был бы признателен за помощь в этом... спасибо!
Вот более подробная информация:
models.py
class ProductClass(models.Model):
classDescription = models.CharField(max_length=255, verbose_name='Type', unique=True, null=True)
classPrint = models.BooleanField(default=True, verbose_name='Print?')
class Meta:
verbose_name_plural = 'Product Classes'
def __str__(self):
return self.classDescription
class Product(models.Model):
prodclassQuery = models.ForeignKey(ProductClass, on_delete=models.PROTECT, verbose_name='Product Class', default=1)
prodDescription = models.CharField(max_length=255, verbose_name='Product')
prodPrice = models.DecimalField(max_digits=6, decimal_places=2, verbose_name='Price')
class Meta:
ordering = ['prodclassQuery', 'prodDescription']
def __str__(self):
return self.prodDescription
class Menu(models.Model):
menuActive = models.BooleanField(verbose_name='Active?', default=False)
menuDescription = models.CharField(max_length=255, verbose_name='Menu')
prodQuery = models.ManyToManyField(Product, verbose_name='Product')
class Meta:
ordering = ['menuDescription', ]
def __str__(self):
return self.menuDescription
class Order(models.Model):
orderDtOpen = models.DateTimeField(auto_now_add=True)
orderDtClose = models.DateTimeField(auto_now=True)
orderOpen = models.BooleanField(default=True, verbose_name='Open?')
orderTable = models.CharField(max_length=25, verbose_name='Table')
menuQuery = models.ForeignKey(Menu, on_delete=models.PROTECT, verbose_name='Menu', default=1)
prodQuery = models.ManyToManyField(Product, through='ProductOrder')
class ProductOrder(models.Model):
poOrder = models.ForeignKey(Order, on_delete=models.CASCADE, verbose_name='Order', default=1)
prodQuery = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name='Product', default=1)
poQty = models.PositiveIntegerField(default=0, verbose_name='Quantity')
class Meta:
constraints = [
models.UniqueConstraint(fields=('poOrder', 'prodQuery'), name='once_per_product_order')
]
forms.py
class OrderForm(ModelForm):
class Meta:
model = Order
fields = ['orderOpen', 'orderTable', 'menuQuery',]
def __init__(self, *args, **kwargs):
super(OrderForm, self).__init__(*args, **kwargs)
self.fields['menuQuery'].queryset = Menu.objects.filter(menuActive=True)
class ProductOrderForm(ModelForm):
filtered_products = None
class Meta:
model = ProductOrder
fields = ['prodQuery', 'poQty',]
widgets = {'prodQuery': TextInput}
def __init__(self, *args, **kwargs):
super(ProductOrderForm, self).__init__(*args, **kwargs)
self.filtered_products = Product.objects.filter(menu__prodQuery = self.instance.prodQuery)
self.fields['prodQuery'].queryset = self.filtered_products
views.py
def create_order(request):
form = OrderForm(request.POST or None, request.FILES or None)
filtered_products = ProductOrderForm()
ProductOrderFormset = inlineformset_factory(Order, Order.prodQuery.through, form = ProductOrderForm,
extra = len(filtered_products.filtered_products),
can_delete = False)
if request.POST and form.is_valid():
order = form.save(commit=False)
formset = ProductOrderFormset(request.POST, instance=order)
if formset.is_valid():
try:
order.save()
formset.save()
return redirect('/orders')
except:
pass
else:
formset = ProductOrderFormset(request.POST or None, instance=Order(),
initial=[{'prodQuery': prod.id} for prod in filtered_products.filtered_products],)
context = {
'form': form,
'formset': formset,
}
return render(request,'create_order.html', context)
шаблон
<form method="post">
{% csrf_token %}
{% load custom_tags %}
{{ form.orderOpen.label }} {{ form.orderOpen }}
{{ form.orderTable.label }} {{ form.orderTable }}
{{ form.menuQuery.label }} {{ form.menuQuery }}
{{ formset.management_form }}
{% for form in formset %}
<table>
<tr>
<th><label for="{{ form.prodQuery.auto_id }}">
{% with idx=forloop.counter0 %}
{% with prod=form.fields.prodQuery.queryset|index:idx %}
{{ prod.prodclassQuery }} {{ prod }} {{ prod.prodPrice }}
{% endwith %}
{% endwith %}
</label></th>
<td>
<input type="hidden" name="{{ form.prodQuery.html_name }}" value="{{ form.prodQuery.value }}" cols="80" rows="20" id="{{ form.prodQuery.auto_id }}">
</td>
</tr>
<tr>
<!--<th><label for="{{ form.poQty.auto_id }}">Quantity:</label></th>-->
<td>
<button type="button" class="btn btn-outline-secondary" onclick="incrementValue('{{ form.poQty.auto_id }}')">+</button>
<input type="number" name="{{ form.poQty.html_name }}" value="0" min="0" id="{{ form.poQty.auto_id }}">
<button type="button" class="btn btn-outline-secondary" onclick="decreaseValue('{{ form.poQty.auto_id }}')">-</button>
<input type="hidden" name="{{ form.poOrder.html_name }}" id="{{ form.poOrder.auto_id }}">
<input type="hidden" name="{{ form.id.html_name }}" id="{{ form.id.auto_id }}">
</td>
</tr>
</table>
{% endfor %}
<div>
<a type="button" class="btn btn-danger" href="{%url 'urlOrders' %}">Cancel</a>
<button type="submit" class="btn btn-success">Create</button>
</div>
</form>
PS.: Я решил "подражать" шаблону inlineformset_factory
, чтобы добавить несколько кнопок для увеличения/уменьшения количества товара.
Чтобы гарантировать, что первая запись всегда будет включена, независимо от значения poQty
, вы можете установить extra
в 1. Это гарантирует, что по крайней мере одна дополнительная форма будет включена в набор форм, даже если poQty
будет нулевой для всех записей.
Используйте следующий вид:
def create_order(request):
form = OrderForm(request.POST or None, request.FILES or None)
filtered_products = ProductOrderForm()
ProductOrderFormset = inlineformset_factory(Order, Order.prodQuery.through, form=ProductOrderForm,
extra=1,
can_delete=False)
if request.POST and form.is_valid():
order = form.save(commit=False)
formset = ProductOrderFormset(request.POST, instance=order)
if formset.is_valid():
try:
order.save()
formset.save()
return redirect('/orders')
except:
pass
else:
formset = ProductOrderFormset(request.POST or None, instance=Order(),
initial=[{'prodQuery': prod.id} for prod in filtered_products.filtered_products])
context = {
'form': form,
'formset': formset,
}
return render(request, 'create_order.html', context)
Таким образом, установив значение extra
в 1, набор форм всегда будет включать по крайней мере одну дополнительную форму, даже если поле poQty
для первой записи равно нулю.
Редактирование
Как вы указали в комментарии, если установка extra=1
вызывает проблемы с загрузкой всех вариантов продукта в форме, вы можете попробовать другой подход, чтобы гарантировать, что первая запись всегда будет включена, независимо от значения poQty
.
Один из способов добиться этого - проверить, есть ли в наборе форм отправленные данные. Если данных нет, можно вручную добавить форму для первого товара.
Попробуйте так:
def create_order(request):
form = OrderForm(request.POST or None, request.FILES or None)
filtered_products = ProductOrderForm()
ProductOrderFormset = inlineformset_factory(Order, Order.prodQuery.through, form=ProductOrderForm,
extra=0, # Set extra to 0 initially
can_delete=False)
if request.POST and form.is_valid():
order = form.save(commit=False)
formset = ProductOrderFormset(request.POST, instance=order)
if formset.is_valid():
try:
order.save()
formset.save()
return redirect('/orders')
except:
pass
else:
formset = ProductOrderFormset(request.POST or None, instance=Order())
if not request.POST:
initial_data = [{'prodQuery': prod.id} for prod in filtered_products.filtered_products]
formset.forms.append(ProductOrderForm(initial=initial_data))
context = {
'form': form,
'formset': formset,
}
return render(request, 'create_order.html', context)
Теперь, даже если данные не будут отправлены, набор форм будет включать форму для первого продукта.
Редактирование 2
Похоже, что в качестве начального аргумента ожидается словарь с ключами, соответствующими полям формы, но вместо него получается список словарей.
Попробуйте это:
def create_order(request):
form = OrderForm(request.POST or None, request.FILES or None)
filtered_products = ProductOrderForm()
ProductOrderFormset = inlineformset_factory(Order, Order.prodQuery.through, form=ProductOrderForm,
extra=0, # Set extra to 0 initially
can_delete=False)
if request.POST and form.is_valid():
order = form.save(commit=False)
formset = ProductOrderFormset(request.POST, instance=order)
if formset.is_valid():
try:
order.save()
formset.save()
return redirect('/orders')
except:
pass
else:
formset = ProductOrderFormset(request.POST or None, instance=Order())
if not request.POST:
initial_data = [{'prodQuery': prod.id, 'poQty': None} for prod in filtered_products.filtered_products]
formset.forms.append(ProductOrderForm(initial=data) for data in initial_data)
context = {
'form': form,
'formset': formset,
}
return render(request, 'create_order.html', context)
Другой подход:
Вы также можете попробовать установить начальное количество для форм в None
вместо 0
. Таким образом, Django распознает их как измененные и попытается сохранить их, как показано ниже:
else:
formset = ProductOrderFormset(
request.POST or None,
instance=Order(),
initial=[{'prodQuery': prod.id, 'poQty': None} for prod in filtered_products.filtered_products],
)