PAYATHON 2026

Django forms 报错:'Event' is not iterable

支付老李

结论

报错出在这一行:

form = PaymentForm(event, athlete)

PaymentForm 继承自 forms.Form。如果没有自定义 __init__(),Django 会把第一个位置参数当作表单的 data,第二个位置参数当作 files。这样一来,event 就被误当成了类似 request.POST 的字典。

渲染 {{ form }} 时,Django 会尝试从 event 中读取字段,并执行类似下面的判断:

if name not in data:

但 Event 实例不是可迭代的表单数据,因此会报错:

TypeError: argument of type 'Event' is not iterable

解决方法是把 event 和 athlete 定义为 PaymentForm 的自定义关键字参数,并在初始化表单时从 kwargs 中取出。

正确初始化 PaymentForm

表单可以这样定义:

from django import forms


class PaymentForm(forms.Form):
    card_number = forms.CharField()
    amount = forms.DecimalField(
        max_digits=10,
        decimal_places=2,
        widget=forms.HiddenInput()
    )

    def __init__(self, *args, **kwargs):
        self.event = kwargs.pop('event')
        self.athlete = kwargs.pop('athlete')

        super(PaymentForm, self).__init__(*args, **kwargs)

        # 根据 event 和 athlete 查询实际费用
        # fee = RegistrationFee.objects.get(
        #     event=self.event,
        #     athlete=self.athlete
        # )
        # self.fields['amount'].initial = fee.amount

自定义参数必须先通过 pop() 取出,然后再调用父类的 __init__(),因为 Django 的基础表单类不认识 event 和 athlete 这两个参数。

如果它们不是每次都必须提供,可以写成:

self.event = kwargs.pop('event', None)
self.athlete = kwargs.pop('athlete', None)

修改 payment view

GET 和 POST 请求都要传入相同的上下文参数:

from django.shortcuts import get_object_or_404, redirect, render


def payment(request, event_year, event_slug):
    event = get_object_or_404(
        Event,
        event_date__year=event_year,
        event_slug=event_slug
    )

    athlete = get_object_or_404(
        Athlete,
        email_address=request.session.get('user_email')
    )

    if request.method == 'POST':
        form = PaymentForm(
            request.POST,
            event=event,
            athlete=athlete
        )

        if form.is_valid():
            # 使用 form.cleaned_data 处理支付
            payment_data = form.cleaned_data

            # process payment_data...

            return redirect('home')
    else:
        form = PaymentForm(
            event=event,
            athlete=athlete
        )

    return render(request, 'events/event_payment.html', {
        'event': event,
        'athlete': athlete,
        'form': form,
    })

这里应该写成:

PaymentForm(event=event, athlete=athlete)

不能写成:

PaymentForm(event, athlete)

处理 POST 请求时,request.POST 仍作为表单数据传入:

PaymentForm(request.POST, event=event, athlete=athlete)

如果只是设置初始值

如果表单不需要保存 event 和 athlete,只需要在字段中显示计算出的费用,也可以直接使用 initial:

form = PaymentForm(initial={
    'amount': registration_fee,
})

处理 POST 请求时,照常绑定用户提交的数据:

form = PaymentForm(request.POST)

但不能只相信浏览器提交的 amount。隐藏字段也可以被用户修改,因此在支付前,服务端应根据 event 和 athlete 重新查询或计算最终费用。

普通 Form 没有 save()

题目中的 PaymentForm 是普通 forms.Form,不是 ModelForm,所以默认没有:

form.save()

验证通过后,应读取:

form.cleaned_data

再自行创建数据库记录或调用支付逻辑,例如:

if form.is_valid():
    payment = Payment.objects.create(
        athlete=athlete,
        event=event,
        amount=calculated_fee
    )

除非在 PaymentForm 中自行实现了 save() 方法,否则不能调用 form.save()。

Template 还需要 form 标签

当前模板中只有 {% csrf_token %} 和 {{ form }},没有实际的 HTML <form> 标签。可以改成:

{% extends "base.html" %}

{% block head %}
    {{ event.event_date.year }} {{ event.event_name }} Payment
{% endblock head %}

{% block content %}
    <h1>
        {{ event.event_date.year }} {{ event.event_name }}
        Entry Form for {{ athlete.first_name }} {{ athlete.last_name }}
    </h1>

    <form method="post">
        {% csrf_token %}

        <p>
            Payment for {{ athlete.first_name }} {{ athlete.last_name }}
        </p>

        {{ form.as_p }}

        <button type="submit">Register</button>
    </form>
{% endblock content %}

{{ form }} 只负责渲染表单字段,不会自动生成 <form> 标签、提交方式和提交按钮。

其他注意事项

如果 Event.objects.get() 找不到记录,会直接抛出 Event.DoesNotExist。因此后面再写:

if not event:
    raise Http404

不会生效。这里更适合使用 get_object_or_404()。

session 中也可能没有 user_email。生产环境中最好先检查这个值:

email = request.session.get('user_email')

if not email:
    return redirect('register', event_year=event_year, event_slug=event_slug)

问题不在模板本身。{{ form }} 在渲染表单时触发了相关处理流程,才暴露出初始化方式有误。将 event 和 athlete 改为明确的自定义关键字参数,并按普通 Form 的方式处理验证结果,就能解决这个问题。

备注:内容仅供参考。