поддержание чистоты моделей django, проверка объекта внешнего ключа и использование сохранения ModelForm

В методе clean моей модели я проверяю, указан ли экспонент внешнего ключа is_premium, а также проверяю, что у него не более MAX_DISCOUNTS_PER_EXHIBITOR активных объектов.

Он отлично работает в админке django. Как при добавлении, так и при редактировании.

Я хотел бы, чтобы он работал в моих пользовательских представлениях (не django-admin). Я использую ModelForms.

первый подход

Я назначаю экспонента в поле зрения, чтобы первоначально commit=False сохранить объект.

И вылетает с DoesNotExist: У скидки нет экспонента, потому что clean выполняется, а экспонент еще не назначен. Каким должен быть правильный способ его реализации?

модели.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
                )
            )

формы.py

class DiscountForm(forms.ModelForm):

    class Meta:
        model = Discount
        fields = (
            'title',
            'description',
            'is_activa',
        )

просмотры.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, но он вылетает с той же ошибкой:

формы.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

просмотры.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 может это сделать, то это, безусловно, можно реализовать в пользовательских моделях.


person andilabs    schedule 15.04.2015    source источник


Ответы (1)


При назначении экспонента с использованием 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)

и полностью отказаться от установки exhibitor внутри __init__ вашей формы

Таким образом, данные вашей формы (которые используются для создания вашего объекта Discount) будут правильными и могут быть очищены.

[Изменить] Как создать скрытое поле ввода в ModelForm:

class DiscountFormAdd(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(DiscountFormAdd, self).__init__(*args, **kwargs)
        self.fields['exhibitor'].widget = forms.HiddenInput()

    ...

или вы также можете использовать атрибут widgets метакласса

class DiscountFormAdd(forms.ModelForm):

    class Meta:
        model = Discount
        widgets = {'exhibitor': forms.HiddenInput()}
        ...
person Emma    schedule 15.04.2015
comment
как должен выглядеть класс формы? Я не хочу отображать в пользовательском интерфейсе экспонента - person andilabs; 15.04.2015
comment
Вы можете использовать скрытый виджет ввода docs.djangoproject.com/en/ 1.8/ref/forms/widgets/#hiddeninput - person Emma; 15.04.2015
comment
Я обновил свой ответ, чтобы показать, как скрыть поле формы из ModelForm. - person Emma; 15.04.2015