目标是要有一个简单的工作流程,其中需要相关的预算持有人批准订单和相关的订单行(在之前的步骤中创建)。 批准表单显示所有订单行,但禁用当前用户没有关联的行(他们应该能够查看整个订单,但只能编辑他们被允许的行)。 如果必要,他们应该能够添加新的行。 用户需要决定是否批准或不批准(批准单选框不能为空)。
覆盖 BaseInlineFormSet 的 clean() 方法,但是我无法在 init 中禁用字段或任何验证规则(它会在验证失败时悄无声息地失败,并呈现一个空的 inlineformset - 它永远不会到达 clean() 方法)。
如果所有值都正确输入,则初始表单将正确显示并能够正确保存输入 - 但是,如果未通过验证,则不正确的字段将被突出显示并清除其值。
models.py
class Order(models.Model):
department = models.ForeignKey(user_models.Department, on_delete=models.CASCADE)
location = models.ForeignKey(location_models.Location, on_delete=models.CASCADE, null=True)
description = models.CharField(max_length=30)
project = models.ForeignKey(project_models.Project, on_delete=models.CASCADE)
product = models.ManyToManyField(catalogue_models.Product, through='OrderLine', related_name='orderlines')
total = models.DecimalField(max_digits=20, decimal_places=2, null=True, blank=True)
def __str__(self):
return self.description
class OrderLine(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
project_line = models.ForeignKey(project_models.ProjectLine, on_delete=models.SET_NULL, null=True, blank=False)
product = models.ForeignKey(catalogue_models.Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField()
price = models.DecimalField(max_digits=20, decimal_places=4)
total = models.DecimalField(max_digits=20, decimal_places=2)
budgetholder_approved = models.BooleanField(null=True)
def get_line_total(self):
total = self.quantity * self.price
return total
def save(self, *args, **kwargs):
self.total = self.get_line_total()
super(OrderLine, self).save(*args, **kwargs)
def __str__(self):
return self.product.name
views.py
class BudgetApprovalView(FlowMixin, generic.UpdateView):
form_class = forms.BudgetHolderApproval
def get_object(self):
return self.activation.process.order
def get_context_data(self, **kwargs):
data = super(BudgetApprovalView, self).get_context_data(**kwargs)
if self.request.POST:
data['formset'] = forms.OrderLineFormet(self.request.POST, instance=self.object)
else:
data['formset'] = forms.OrderLineFormet(instance=self.activation.process.order, form_kwargs={'user': self.request.user})
return data
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
form = forms.BudgetHolderApproval(self.request.POST, instance=self.activation.process.order)
formset = forms.OrderLineFormet(self.request.POST, instance=self.activation.process.order)
if form.is_valid() and formset.is_valid():
return self.is_valid(form, formset)
else:
return self.is_invalid(form, formset)
def is_valid(self, form, formset):
self.object = form.save(commit=False)
self.object.created_by = self.request.user
self.activation.process.order = self.object
with transaction.atomic():
self.object.save()
self.activation.done()
formset.save()
return HttpResponseRedirect(self.get_success_url())
def is_invalid(self, form, formset):
return self.render_to_response(self.get_context_data(form=form, formset=formset))
我尝试了几种方法来解决这个问题 - 但都没有成功:
- 重写ModelForm的clean()方法 - 然而,我无法确定提交的表单是否被禁用。
forms.py
class OrderForm(forms.ModelForm):
class Meta:
model = models.Order
fields = ['description', 'project', 'location']
def __init__(self, *args, **kwargs):
super(OrderForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
class OrderLine(forms.ModelForm):
class Meta:
model = models.OrderLine
exclude = ['viewflow']
def __init__(self, *args, **kwargs):
YES_OR_NO = (
(True, 'Yes'),
(False, 'No')
)
self.user = kwargs.pop('user', None)
super(OrderLine, self).__init__(*args, **kwargs)
self.fields['project_line'].queryset = project_models.ProjectLine.objects.none()
self.fields['budgetholder_approved'].widget = forms.RadioSelect(choices=YES_OR_NO)
if self.instance.pk:
self.fields['budgetholder_approved'].required = True
self.fields['order'].disabled = True
self.fields['project_line'].disabled = True
self.fields['product'].disabled = True
self.fields['quantity'].disabled = True
self.fields['price'].disabled = True
self.fields['total'].disabled = True
self.fields['budgetholder_approved'].disabled = True
if 'project' in self.data:
try:
project_id = int(self.data.get('project'))
self.fields['project_line'].queryset = project_models.ProjectLine.objects.filter(project_id=project_id)
except (ValueError, TypeError):
pass
elif self.instance.pk:
self.fields['project_line'].queryset = self.instance.order.project.projectline_set
project_line_id = int(self.instance.project_line.budget_holder.id)
user_id = int(self.user.id)
if project_line_id == user_id:
self.fields['budgetholder_approved'].disabled = False
self.helper = FormHelper()
self.helper.template = 'crispy_forms/templates/bootstrap4/table_inline_formset.html'
self.helper.form_tag = False
def clean(self):
super(OrderLine, self).clean()
pprint(vars(self.instance))
//This just returns a list of fields without any attributes to apply the validation logic
OrderLineFormet = forms.inlineformset_factory(
parent_model=models.Order,
model=models.OrderLine,
form=OrderLine,
extra=2,
min_num=1
)
覆盖 BaseInlineFormSet 的 clean() 方法,但是我无法在 init 中禁用字段或任何验证规则(它会在验证失败时悄无声息地失败,并呈现一个空的 inlineformset - 它永远不会到达 clean() 方法)。
forms.py
class OrderForm(forms.ModelForm):
class Meta:
model = models.Order
fields = ['description', 'project', 'location']
def __init__(self, *args, **kwargs):
super(TestOrderForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
class BaseTestOrderLine(forms.BaseInlineFormSet):
def __init__(self, user, *args, **kwargs):
self.user = user
super(BaseTestOrderLine, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.template = 'crispy_forms/templates/bootstrap4/table_inline_formset.html'
self.helper.form_tag = False
// Never gets to the clean method as is_valid fails silently
def clean(self):
super(BaseTestOrderLine, self).clean()
if any(self.errors):
pprint(vars(self.errors))
return
OrderLineFormet = forms.inlineformset_factory(
parent_model=models.Order,
model=models.OrderLine,
formset=BaseTestOrderLine,
exclude=['order'],
extra=2,
min_num=1
)
编辑 - 根据Dao的建议反映进展(表单重新加载时,验证错误正确显示)
唯一剩下的问题是,当表单重新加载时,应该仍然启用的字段(budgetholder_approved)被禁用了。两个批准复选框行中的一个应该是可编辑的。
for form in formset:#do something
。 - Milo Persicblank=True
和/或null=True
。这就是为什么表单显示“必填项”,而没有提交的原因。然后在模型表单类中处理所需或不需要的内容。 - Milo Persic