自定义表单字段的异常未被传播,并形成重新发布以使用tupple

时间:2016-11-03 13:42:52

标签: python django django-forms django-generic-views

我对python(3.5)/ django(1.10)相当新,我遇到了以下问题:

我正在使用Django的通用CreateView来创建模型和相应的子模型。

我的目标是保存购买信息。购买由一个账单和一个或多个收据组成。为此,我创建了一个自定义表单(BillForm),其中包含一个自定义字段,用户可以输入将用于创建收据的逗号分隔值。

这就是我所拥有的:

models.py

class Bill(models.Model):
    """ A given bill whenever an item is purchased."""
    number = models.CharField(_('Number'), max_length=20)
    purchase_date = models.DateTimeField(_('Purchase Date'))
    company = models.ForeignKey(Company, verbose_name=_('Company'),
        on_delete=models.DO_NOTHING)
    ...

    def _get_receipts(self):
        ''' returns all receipts that related to a bill '''
        return Receipt.objects.filter(bill = self)
    receipts = property(_get_receipts)

class Receipt(models.Model):
    """ A receipt confirming a product is in our posession """
    number = models.CharField(_('Number'), max_length=20)
    bill = models.ForeignKey(Bill, verbose_name=_('Bill'),
        on_delete=models.DO_NOTHING)

urls.py:

    url(_(r'^create_purchase/$'), views.ObjectCreateView.as_view(
        model=Bill,
        form_class=BillForm
    ),
    name="create_purchase"),

forms.py:

class MultipleReceiptsField(forms.Field):
    ''' A custom field to store a list of coma separated receipts '''
    def to_python(self, value):
        ''' Normalize data to a set of receipts numbers '''
        if not value:
            return set()

        return set(v.strip() for v in value.split(',') if v.strip())

    def validate(self, value):
        ''' check if the values passed are less than 20 char long (limit for
        model). '''
        super(MultipleReceiptsField, self).validate(value)
        # had made the following a tuple, then updated to list and worked
        invalid_receipts = [r for r in value if len(r) > 20]

        if invalid_receipts:
            # EXCEPTION THROWN HERE BUT NOT PROPAGATED
            raise ValidationError(
                [ValidationError(_("Length of receipt %(r) is too large (max 20 characters)"),
                         code=INVALID_PARAMS, params={'r':int(r)}) # it was params={r:r} should have used 'r'
                for r in invalid_receipts]
            )

class BillForm(forms.ModelForm):
    ''' A form used to create a Purchase Bill. '''
    receipts = MultipleReceiptsField(label=_("Receipts"), widget=forms.Textarea)

    def __init__(self, *args, **kwargs):
        ''' constructor used to filter the companies. '''
        # executes
        super(BillForm, self).__init__(*args, **kwargs)
        self.fields['company'].queryset =\ Company.objects.filter(is_provider=True).filter(is_active=True)

    def save(self, commit=True):
        ''' createe receipts when saving a bll '''
        # save logic | NEVER EXECUTES...
        # for each elementt in receipts, create a receipt object and
        # set its bill value to the bill that we just created
        bill = super(BillForm, self).save(commit=False)
        receipts_cd = self.cleaned_data["receipts"]

        ...

        return bill

    class Meta:
        model = Bill
        fields = ('company', "number", "currency", "price", "receipts")

view.py:

class ObjectCreateView(IsInPurchaseGroup, CreateView):
    model = None
    form_class = None
    #fields = "__all__"
    template_name = "purchases/object_form.html"

    def get_context_data(self, **kwargs):
        context = super(ObjectCreateView, self).get_context_data(**kwargs)
        context["title"] = str(ACTIONS[CREATE_ACTION]) + " " +\
            str(self.model._meta.verbose_name)
        context["button"] = ACTIONS[CREATE_ACTION]

        return context

    def get_success_url(self):
        next_url, kwargs = get_next_url(self.request)

        if not next_url:
            # works because the pluralization of both models end with s
            next_url =\
                "purchases:{}s".format((str(self.model).split(".")[-1])[:-2])

            # if i am creating a bill, then i must navigate to
            # create_purchased_products in the warehouse department.
            if self.model == Bill:
                next_url = "warehouse:create_purchased_products"
                kwargs = {"receipts" : self.object.receipts}

        return reverse_lazy(next_url, kwargs=kwargs)

object_form.html

{% extends "purchases/base.html" %}
{% load i18n %}

{% block body %}
<h3>{{ title }}</h3>

{% if error_message %}
<p><strong>{{ error_message }}</strong></p>
{% endif %}

<form action="" method="post">{% csrf_token %}
    <table>{{ form.as_table }}</table>
    <div class="buttons">
        <input type="Submit" value="{{ button }}"/>
    </div>
 </form>

{% endblock %}

问题:

每当在网址上提供form_class时,以下功能都不会被执行:

  • BillForm.save()
  • ObjectCreateView.get_success_url()

如果我省略url上的form_class并设置变量fields = "__all__"(只是创建我的Bill模型的对象),则调用方法ObjectCreateView.get_success_url()

我的问题

为什么BillForm.save()ObjectCreateView.get_success_url()没有被执行?我知道BillForm有问题,但我似乎无法理解......

非常感谢任何帮助。

更新

  • 上传了object_form.html并更新了forms.py
  • 没有异常被抛出。单击“提交”后,POST将发送到服务器。在客户端(浏览器)上,不会发生任何更改(表单仍然是已输入的数据)。
  • 发现我的错误;但我仍然不确定为什么会这样。
    • 我在MultipleReceiptsField.validate()中抛出了一些例外,但它们没有被传播(当我提交表单时,我没有获得异常,只是保留在发布时)。
    • 修复异常后(看到放置无效输入时会出现验证错误)并尝试提交正确的数据,表格会继续重新发布。
    • 然后我将invalid_receipts变量从元组更新为列表并开始工作

我是否错过了元组和列表之间的一些细微差别?

forms.py - 无效:

class MultipleReceiptsField(forms.Field):
''' A custom field to store a list of coma separated receipts '''
def to_python(self, value):
    ''' Normalize data to a set of receipts numbers '''
    if not value:
        return set()

    return set(v.strip() for v in value.split(',') if v.strip())

def validate(self, value):
    ''' check if the values passed are less than 20 char long (limit for
    model). '''
    super(MultipleReceiptsField, self).validate(value)
    invalid_receipts = (r for r in value if len(r) > 20)

    if invalid_receipts:
        raise ValidationError(
            [ValidationError(_("Length of recipt %(r) is too large (max 20 characters)"),
                    code=INVALID_PARAMS, params={'r':int(r)})
            for r in invalid_receipts]
        )

forms.py - working:

lass MultipleReceiptsField(forms.Field):
''' A custom field to store a list of coma separated receipts '''
def to_python(self, value):
    ''' Normalize data to a set of receipts numbers '''
    if not value:
        return set()

    return set(v.strip() for v in value.split(',') if v.strip())

def validate(self, value):
    ''' check if the values passed are less than 20 char long (limit for
    model). '''
    super(MultipleReceiptsField, self).validate(value)
    invalid_receipts = [r for r in value if len(r) > 20]

    if invalid_receipts:
        raise ValidationError(
            [ValidationError(_("Length of receipt %(r) is too large (max 20 characters)"),
                    code=INVALID_PARAMS, params={'r':int(r)})
            for r in invalid_receipts]
        )

1 个答案:

答案 0 :(得分:0)

我认为方法中的问题保存。方法保存返回对象的实例。如果不能保存则返回None。你的方法一直都没有返回。调用其他方法后,创建表单保存视图检查结果。如果没有看到poccess中止。你需要返回一些实例或者像这样调用super.save:

def save(self, commit=True):
        inst = super(BillForm, self).save(commit=False)
        #addition logic, all what you need.
        return inst

或者您需要在视图类上覆盖有效方法,而不需要调用super()。valid()并覆盖表单中的save方法。

抱歉,我的英语非常糟糕。