不传播自定义表单字段和使用元组重发表单的异常



我是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 %}

问题:

在url上指定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
  • 没有抛出异常。单击Submit后,将向服务器发送一个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]
        )

我认为save方法有问题。方法保存返回对象的实例。如果不能保存,返回None。你的方法总是返回None。在调用其他方法后创建视图检查表单保存结果。如果它没有看到进程中止。你需要返回一些实例或调用super。保存如下:

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

或者您需要在视图类上重写valid方法,而不需要调用super().valid()并在form中重写save方法。

对不起,我的英语很差。

最新更新