'Django model's clean method and foreign key validation in FormView

In my django app I have simple Category and Offer models:

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')})

My form class:

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", ),)
        )

and my view class, based on generic CreateView class:

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 requests are working fine. Problem is when I try to submit such form. I am always getting an error:

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

Looks like category property is None in Offer model clean method - but I am not sure why and how can I pass it there. I want to use model clean method as it covers also django's admin panel validation when new object is created there. Any ideas?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source