【发布时间】:2023-02-04 16:40:05
【问题描述】:
目标是拥有一个简单的工作流,其中订单和相关订单行(在上一步中创建)需要得到相关预算持有人的批准。审批表显示所有订单行,但禁用当前用户未关联的那些行(他们应该能够看到整个订单,但只能编辑他们被允许的行)。如有必要,他们应该能够添加新行。用户需要决定是否批准(批准单选不能为空)
初始表单正确显示并且能够在正确输入所有值时正确保存输入 - 但是,如果验证失败,则不正确的字段会突出显示并清除它们的值。
模型.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
视图.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() 方法 - 但是,我不知道如何确定提交的表单是否被禁用。
表单.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() 方法 - 但是,我无法禁用在里面或任何验证规则(它默默地验证失败并在失败时显示一个空白的内联表单集 - 它永远不会进入 clean() 方法。
表单.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 - 感谢您的回复。就行数而言 - 它是动态的(它取决于原始顺序中输入的行数。所以它可能是 10 或 500。至于你关于在表单集中循环表单的建议 - 你会把这段代码放在哪里?
-
视图需要一个函数来生成表单列表,每个表单都有自己的对象实例。该模板将解压它。根据您处理的对象数量,我不确定我会走那条路。表单集和自定义验证可能是一个很好的解决方案。作为第一步,我会考虑将
blank=True和/或null=True添加到您的模型字段,如“数量”、“产品”等。这就是表单显示“必填”而不是提交的原因。然后处理模型表单类上需要或不需要的内容。 -
顺便问一下,您是否有任何 ajax 可以刷新表单并显示来自验证器的错误?如果是这样的话,我认为这可能是您的主要缺失部分。
-
嗨 Milo - 在模型允许空值的“budgetholder_approved”字段上验证失败。因此模型定义的验证不应失败(现有行的所有其他字段已经存在,因此这些也不应失败)。无论如何 - 我试图覆盖中的表单集在里面这应该定义验证规则。我在这一点上避免使用 ajax,因为我试图在改进用户体验之前在后端进行验证
标签: python django validation django-forms inline-formset