保持 Django 模型清洁方法验证外键对象并使用 ModelForm 保存
keeping django models clean method validating foreign key object and using ModelForm save
在我的模型的 clean
方法中,我验证是否在外键参展商 is_premium 中给出,并且还验证他没有超过 MAX_DISCOUNTS_PER_EXHIBITOR 个活动对象。
它在 django admin 中工作得很好。添加时以及编辑时。
我想让它在我的自定义(非 django-admin)视图中工作。我正在使用模型表单。
第一种方法
我将视图中的参展商分配给最初 commit=False
保存的对象。
它崩溃并显示 DoesNotExist
:折扣没有参展商,因为 clean
已执行,但尚未分配参展商。正确的实施方式应该是什么?
models.py
class Discount(models.Model):
exhibitor = models.ForeignKey(
'core_backend.Exhibitor', related_name='discounts')
is_active = models.BooleanField(default=False, verbose_name=u"Aktywny")
title = models.TextField(verbose_name=u"Tytuł")
def clean(self):
if not self.exhibitor.is_premium:
raise ValidationError(
u'Discounts only for premium')
count = Discount.objects.filter(
is_active=True,
exhibitor=self.exhibitor
).count()
if not self.pk:
# newly added
count = count + (1 if self.is_active else 0)
else:
# edited
discount = Discount.objects.get(pk=self.pk)
if discount.is_active and not self.is_active:
count = count - 1
elif not discount.is_active and self.is_active:
count = count + 1
if count > settings.MAX_DISCOUNTS_PER_EXHIBITOR:
raise ValidationError(
u'Max %s active discounts' % (
settings.MAX_DISCOUNTS_PER_EXHIBITOR
)
)
forms.py
class DiscountForm(forms.ModelForm):
class Meta:
model = Discount
fields = (
'title',
'description',
'is_activa',
)
views.py
def add_discount(request, fair_pk, exhibitor_pk):
fair = get_object_or_404(Fair, pk=fair_pk)
exhibitor = get_object_or_404(
Exhibitor, fair=fair, pk=exhibitor_pk, user=request.user)
if request.method == 'GET':
form = DiscountForm()
return render(request, 'new_panel/add_discount.html', {
'exhibitor': exhibitor,
'discount_form': form,
})
if request.method == 'POST':
form = DiscountForm(data=request.POST)
if form.is_valid():
discount = form.save(commit=False)
discount.exhibitor = exhibitor
discount.save()
return redirect(reverse(
'discounts_list',
kwargs={"fair_pk": fair.pk, 'exhibitor_pk': exhibitor.pk}
))
return render(request, 'new_panel/add_discount.html', {
'exhibitor': exhibitor,
'discount_form': form,
})
第二种方法
我也尝试了不同的方法,使用单独的形式用于 POST,但它因同样的错误而崩溃:
forms.py
class DiscountFormAdd(forms.ModelForm):
class Meta:
model = Discount
fields = (
'title',
'exhibitor',
'is_active',
)
def __init__(self, exhibitor, *args, **kwargs):
self.exhibitor = exhibitor
super(DiscountFormAdd, self).__init__(*args, **kwargs)
def save(self, commit=False):
discount = super(DiscountFormAdd, self).save(commit=False)
discount.exhibitor = self.exhibitor
if commit:
discount.save()
return discount
views.py
def add_discount(request, fair_pk, exhibitor_pk):
fair = get_object_or_404(Fair, pk=fair_pk)
exhibitor = get_object_or_404(
Exhibitor, fair=fair, pk=exhibitor_pk, user=request.user)
if request.method == 'GET':
form = DiscountForm()
return render(request, 'new_panel/add_discount.html', {
'exhibitor': exhibitor,
'discount_form': form,
})
if request.method == 'POST':
form = DiscountFormAdd(data=request.POST, exhibitor=exhibitor)
if form.is_valid():
discount = form.save(commit=True)
return redirect(reverse(
'discounts_list',
kwargs={"fair_pk": fair.pk, 'exhibitor_pk': exhibitor.pk}
))
return render(request, 'new_panel/add_discount.html', {
'exhibitor': exhibitor,
'discount_form': form,
})
我想坚持模型清理验证,而不是跳入表单清理。如果 django admin 能够做到,那么肯定可以在自定义模型中实现。
当使用 self.exhibitor = exhibitor
分配参展商时,这会在您的表单上设置一个与表单内容无关的 property
。
要实际设置参展商,请改用以下代码:
import copy
.
.
.
if request.method == 'POST':
# request.POST is an immutable QueryDict so it needs to be copied
form_data = copy.copy(request.POST)
form_data['exhibitor'] = exhibitor.id
form = DiscountFormAdd(data=form_data)
并完全放弃在表单的 __init__
中设置 exhibitor
这样,您的表单数据(用于创建您的 Discount
)对象将是正确的并且可以被清理
[编辑]
如何在 ModelForm 中创建隐藏输入字段:
class DiscountFormAdd(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DiscountFormAdd, self).__init__(*args, **kwargs)
self.fields['exhibitor'].widget = forms.HiddenInput()
...
或者您也可以使用 Meta class
的 widgets
属性
class DiscountFormAdd(forms.ModelForm):
class Meta:
model = Discount
widgets = {'exhibitor': forms.HiddenInput()}
...
在我的模型的 clean
方法中,我验证是否在外键参展商 is_premium 中给出,并且还验证他没有超过 MAX_DISCOUNTS_PER_EXHIBITOR 个活动对象。
它在 django admin 中工作得很好。添加时以及编辑时。
我想让它在我的自定义(非 django-admin)视图中工作。我正在使用模型表单。
第一种方法
我将视图中的参展商分配给最初 commit=False
保存的对象。
它崩溃并显示 DoesNotExist
:折扣没有参展商,因为 clean
已执行,但尚未分配参展商。正确的实施方式应该是什么?
models.py
class Discount(models.Model):
exhibitor = models.ForeignKey(
'core_backend.Exhibitor', related_name='discounts')
is_active = models.BooleanField(default=False, verbose_name=u"Aktywny")
title = models.TextField(verbose_name=u"Tytuł")
def clean(self):
if not self.exhibitor.is_premium:
raise ValidationError(
u'Discounts only for premium')
count = Discount.objects.filter(
is_active=True,
exhibitor=self.exhibitor
).count()
if not self.pk:
# newly added
count = count + (1 if self.is_active else 0)
else:
# edited
discount = Discount.objects.get(pk=self.pk)
if discount.is_active and not self.is_active:
count = count - 1
elif not discount.is_active and self.is_active:
count = count + 1
if count > settings.MAX_DISCOUNTS_PER_EXHIBITOR:
raise ValidationError(
u'Max %s active discounts' % (
settings.MAX_DISCOUNTS_PER_EXHIBITOR
)
)
forms.py
class DiscountForm(forms.ModelForm):
class Meta:
model = Discount
fields = (
'title',
'description',
'is_activa',
)
views.py
def add_discount(request, fair_pk, exhibitor_pk):
fair = get_object_or_404(Fair, pk=fair_pk)
exhibitor = get_object_or_404(
Exhibitor, fair=fair, pk=exhibitor_pk, user=request.user)
if request.method == 'GET':
form = DiscountForm()
return render(request, 'new_panel/add_discount.html', {
'exhibitor': exhibitor,
'discount_form': form,
})
if request.method == 'POST':
form = DiscountForm(data=request.POST)
if form.is_valid():
discount = form.save(commit=False)
discount.exhibitor = exhibitor
discount.save()
return redirect(reverse(
'discounts_list',
kwargs={"fair_pk": fair.pk, 'exhibitor_pk': exhibitor.pk}
))
return render(request, 'new_panel/add_discount.html', {
'exhibitor': exhibitor,
'discount_form': form,
})
第二种方法
我也尝试了不同的方法,使用单独的形式用于 POST,但它因同样的错误而崩溃:
forms.py
class DiscountFormAdd(forms.ModelForm):
class Meta:
model = Discount
fields = (
'title',
'exhibitor',
'is_active',
)
def __init__(self, exhibitor, *args, **kwargs):
self.exhibitor = exhibitor
super(DiscountFormAdd, self).__init__(*args, **kwargs)
def save(self, commit=False):
discount = super(DiscountFormAdd, self).save(commit=False)
discount.exhibitor = self.exhibitor
if commit:
discount.save()
return discount
views.py
def add_discount(request, fair_pk, exhibitor_pk):
fair = get_object_or_404(Fair, pk=fair_pk)
exhibitor = get_object_or_404(
Exhibitor, fair=fair, pk=exhibitor_pk, user=request.user)
if request.method == 'GET':
form = DiscountForm()
return render(request, 'new_panel/add_discount.html', {
'exhibitor': exhibitor,
'discount_form': form,
})
if request.method == 'POST':
form = DiscountFormAdd(data=request.POST, exhibitor=exhibitor)
if form.is_valid():
discount = form.save(commit=True)
return redirect(reverse(
'discounts_list',
kwargs={"fair_pk": fair.pk, 'exhibitor_pk': exhibitor.pk}
))
return render(request, 'new_panel/add_discount.html', {
'exhibitor': exhibitor,
'discount_form': form,
})
我想坚持模型清理验证,而不是跳入表单清理。如果 django admin 能够做到,那么肯定可以在自定义模型中实现。
当使用 self.exhibitor = exhibitor
分配参展商时,这会在您的表单上设置一个与表单内容无关的 property
。
要实际设置参展商,请改用以下代码:
import copy
.
.
.
if request.method == 'POST':
# request.POST is an immutable QueryDict so it needs to be copied
form_data = copy.copy(request.POST)
form_data['exhibitor'] = exhibitor.id
form = DiscountFormAdd(data=form_data)
并完全放弃在表单的 __init__
exhibitor
这样,您的表单数据(用于创建您的 Discount
)对象将是正确的并且可以被清理
[编辑] 如何在 ModelForm 中创建隐藏输入字段:
class DiscountFormAdd(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DiscountFormAdd, self).__init__(*args, **kwargs)
self.fields['exhibitor'].widget = forms.HiddenInput()
...
或者您也可以使用 Meta class
的widgets
属性
class DiscountFormAdd(forms.ModelForm):
class Meta:
model = Discount
widgets = {'exhibitor': forms.HiddenInput()}
...