Django模型的clean方法和FormView中的外键验证

时间:2019-12-01 16:59:16

标签: python django django-models django-forms

在我的django应用中,我有简单的Category和Offer模型:

class Category(BaseModel):
    title = models.CharField(_('Category title'), max_length=256)
    available = models.BooleanField(_('Is available'), default=True)
    slug = models.SlugField(max_length=256, null=True, blank=True, unique=True, verbose_name=_(Slug'))

    requires_item_price = models.BooleanField(default=False, verbose_name=_('Requires item price to be provided'))


class Offer(BaseModel):
    category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True, verbose_name=_('Category'))

    item_price = models.DecimalField(max_digits=8, decimal_places=2, null=True, verbose_name=_('Item price'),
                                     blank=True)

    def clean(self):
        if self.category.requires_item_price and not self.item_price:
            raise ValidationError({'item_price': _('If category requires item price - you have to provide it')})

我的表单类:

class NewPaginatedOfferForm(forms.ModelForm):
    class Meta:
        model = Offer
        fields = ('item_price',)

    def __init__(self, category, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.category = category
        self.helper = FormHelper()
        self.helper.form_id = self.__class__.__name__.lower()
        self.initial['category'] = category

        self.helper.layout = Layout(
            Field('item_price'),
            Div(
                Submit('submit', _(Save →'),
                       css_class="btn btn-lg bold btn-block btn-success", ),
            )
        )

和我的视图类,基于通用的CreateView类:

class NewOfferForCategoryView(CreateView):
    model = Offer
    category = None
    template_name = 'web/new_offer_for_category.html'

    def get_form_class(self):
        print('get_form_class')
        if self.category.requires_item_price:
            return NewPaginatedOfferForm

    def get_form_kwargs(self):
        print('get_form_kwargs')
        kwargs = super().get_form_kwargs()
        kwargs['category'] = self.category
        print('kwargs:', kwargs)
        return kwargs

    def dispatch(self, request, *args, **kwargs):
        print('dispatch, ', request.method)
        try:
            self.category = Category.objects.get(slug=self.kwargs.get('cat_slug'), available=True)
        except:
            print('wrong category')
            return redirect(reverse('web:new_offer'))
        print('self category is', self.category)
        return super().dispatch(request, *args, **kwargs)

    def form_valid(self, form):
        print('form_valid')
        form.instance.category = self.category
        return super().form_valid(form)

    def form_invalid(self, form):
        print('form_invalid')
        form.instance.category = self.category
        return super().form_invalid(form)

    def get_context_data(self, **kwargs):
        print('get_context_data')
        ctx = super().get_context_data(**kwargs)
        ctx['categories'] = Category.objects.filter(available=True)
        ctx['category'] = self.category
        return ctx

GET请求运行正常。问题是当我尝试提交此类表格时。 我总是遇到错误:

AttributeError at /new-offer/my-category-slug
'NoneType' object has no attribute 'requires_item_price'

Request Method: POST
Request URL:    http://127.0.0.1:8000/new-offer/my-category-slug
Django Version: 2.2.6
Exception Type: AttributeError
Exception Value:    
'NoneType' object has no attribute 'requires_item_price'
Exception Location: /Users/User/project/core/models.py in clean, line 272

在Offer模型的清洁方法中,看起来类别属性为None-但我不确定为什么以及如何将其传递到那里。我想使用模型清理方法,因为当在其中创建新对象时,它也涵盖了django的管理面板验证。 有什么想法吗?

1 个答案:

答案 0 :(得分:0)

您的模型允许空的categorynull=True),因此您的clean()方法应该假定存在类别。添加if self.category and self.category.requires_item_price...

在您看来,您是在表单验证之后(即在调用模型的clean()方法之后)分配类别。相反,应该通过执行__init__()在表单的初始化程序中分配它(因为您已经将其传递给self.instance.category = category)。

您可以删除表单中的self.initial['category'] = category,因为表单没有字段category,所以该操作无济于事。您也可以删除self.category = category,因为您没有在任何地方使用它。然后,您可以在视图中删除form.instance.category = category

在您的dispatch()方法中,您还具有一个包含全部内容的except子句。永远不要那样做,只捕获要捕获的异常,而不仅仅是任何异常。根据您的情况,将except:替换为except Category.DoesNotExist: