Django ModelForm - 模板中多个表单,不同的动作视图和重定向

0 投票
1 回答
1178 浏览
提问于 2025-04-17 22:03

整体目标是:能够在同一个网页上更新和创建多个对象的记录。当用户更新或创建记录后,他们会被引导回到同一个页面。回到这个页面时,他们的操作结果应该能显示出来,并且他们可以安全地刷新页面,而不会重新提交表单。

这是我的 views.py 文件

@login_required()
def index(request):
    activity_model_form = ActivityModelForm()
    rhi_model_form = RhiModelForm()
    activity_list = Activity.objects.all()
    rhi_list = Rhi.objects.all()

    if request.method == 'POST':
        context = {
            'activity_list': activity_list,
            'rhi_list': rhi_list,
        }
        return render(request, 'ec/index.html', context)
    else:   
        context = {
            'activity_list': activity_list,
            'rhi_list': rhi_list,
            'activity_model_form': activity_model_form,
            'rhi_model_form': rhi_model_form,
        }
    return render(request, 'ec/index.html', context)

def add_activity(request):
    if request.method == 'POST':
        aForm = ActivityModelForm(request.POST)
        if aForm.is_valid():
            aForm.save()
    return HttpResponseRedirect(reverse('ec:index'))

def add_rhi(request):
    if request.method == 'POST':
        rForm = RhiModelForm(request.POST)
        if rForm.is_valid():
            newRhi = rForm.save()
    return HttpResponseRedirect('ec:index')

forms.py 文件

class RhiModelForm(forms.ModelForm):
    class Meta:
        model = Rhi

class ActivityModelForm(forms.ModelForm):
    class Meta:
        model = Activity

模板 (index.html)

    {% for activity in activity_list %}
       {{activity}}
    {% endfor %}
    <br>
    {% for rhi in rhi_list %}
        {{rhi}}
    {% endfor %}
    <br>
    <form aciton="{% url 'ec:add_rhi' %}" method="post">
        {% csrf_token %}
        {{ rhi_model_form }}
        <input type="submit" value="Add"/>
    </form>
    <br>
    <form action="{% url 'ec:add_activity' %}" method="post">
        {% csrf_token %}
        {{ activity_model_form }}
        <input type="submit" value="Add" />
    </form>

urls.py 文件

urlpatterns = patterns('',
           url(r'^$', views.index, name='index'),
           url(r'^forms/add_activity',views.add_activity, name='add_activity'),
           url(r'^forms/add_rhi',views.add_rhi,name='add_rhi'),
)

index.html 页面是主要页面,应该包含多个表单。在这种情况下,我决定使用模型表单,因为它们提供了很多内置功能。在这里看到的其他类似帖子中,创建不同的视图来处理这些表单的处理似乎是更好的方法。从那里,我想重新加载 index.html,但我想重新查询数据库,以获取更新或新创建的记录。

有两个问题:

1) 模型视图没有保存。当我尝试提交 rhi_model_form 时,我被重定向到 index.html,但没有保存记录。根据我在这里发布的内容,我使用了 newRhi = rForm.save(),但我也尝试过仅仅使用 rForm.save(),结果没有任何效果。为什么有些例子中保存方法会设置为一个变量?这样做有什么好处?无论如何,目前在我这里这两种方法都不管用。

2) 提交后,index 视图被渲染,但它会执行代码中的 if request.method == 'POST' 部分。为什么会这样?如果我刷新页面,它又想重新提交表单。我该如何让这个页面在提交后加载所有想要的数据,并确保刷新不会导致重新提交?

1 个回答

1

好的,我找到了如何重构这段代码的方法。对于其他新手来说,如果你想让用户在同一页面上提交不同的表单,并且不想在刷新页面时遇到麻烦的重新提交问题,下面是一个大致的结构可以参考:

  1. 模板:把每个模型的表单放在模板中,但要确保每个表单的动作属性指向不同的视图。
  2. URL配置:在你的urls.py文件中,为每个表单的动作标签设置正确的URL映射。
  3. 视图:创建的视图总数等于这个公式:1 + n,其中n是你想处理的表单数量,而“1”指的是你的实际页面。 为模板中的每个表单创建一个单独的视图。在这“n”个视图中,使用文档中看到的所有标准内容(比如request.method == 'post'yourform.is_valid())。但关键是,在处理完表单后,你需要使用redirect('view:name')!如果你只是想硬编码视图,也可以用redirect('/view/name/')。使用这个重定向函数,系统不会将任何数据传回它调用的主页面视图(上面提到的“1”)。

views.py

@login_required()
def index(request):
    rhi_model_form = RhiModelForm()
    activity_model_form = ActivityModelForm()
    rhi_list = Rhi.objects.all()
    activity_list = Activity.objects.all()
    context = {
        'rhi_model_form': rhi_model_form,
        'rhi_list': rhi_list,
        'activity_model_form': activity_model_form,
        'activity_list': activity_list,
    }
    return render(request, 'ec/index.html', context)

@login_required()
def add_rhi(request):
    if request.method == 'POST':
        rForm = RhiModelForm(request.POST)
        if rForm.is_valid():
            rForm.save()
            return redirect('ec:index')
        else:
            rhi_errors = rForm.errors
            rhi_model_form = RhiModelForm()
            context = {
                'rhi_errors': rhi_errors,
                'rhi_model_form': rhi_model_form,
            }
            return render(request, 'ec/index.html', context)

@login_required()
def add_activity(request):
    if request.method == 'POST':
        aForm = ActivityModelForm(request.POST)
        if aForm.is_valid():
            activity = aForm.save(commit=False)
            activity.lastModifiedBy = request.user
            activity.createdBy = request.user
            activity.save()
            return redirect('ec:index')
        else:
            activity_errors = aForm.errors
            activity_model_form = ActivityModelForm()
            context = {
                'activity_errors': activity_errors,
                'activity_model_form': activity_model_form,
            }
            return render(request, 'ec/index.html', context)

forms.py

class RhiModelForm(forms.ModelForm):
    class Meta:
        model = Rhi
        exclude = ['slug']

class ActivityModelForm(forms.ModelForm):
    class Meta:
        model = Activity
        exclude = ['createdBy','lastModifiedBy']

模板(在我的例子中是 - index.html)

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

    <form action="{% url 'ec:add_rhi' %}" method="post">
        {% csrf_token %}
         {{ rhi_model_form }}
        <input type="submit" value="Add RHI"/>
    </form>
    {% endif %}

    {% if activity_model_form %}
    {% if error_message %}<p><strong>{{error_message}}</strong></p>{% endif %}
    {% if activity_errors %}{{ activity_errors }} {% endif %}
    <form action="{% url 'ec:add_activity' %}" method="post">
        {% csrf_token %}
        {{ activity_model_form.non_field_errors }}
        {{ activity_model_form }}
        <input type="submit" value="Add Activity"/>
    </form>
    {% endif %}

总结一下,核心概念有:

  • POST:除非你想在服务器上更改东西,否则不要使用这种方法。基本上,除非该视图会处理用户的数据,否则不要在任何视图中包含、检查等“POST”的代码。在这个例子中,这就是为什么在索引视图中看不到任何关于“POST”的内容。
  • 重定向/渲染:这两个快捷方式非常重要。重定向会将代码流程发送到其参数中引用的视图,并且不会传递上下文。在我的例子中,这正是我想要的。渲染不接受视图作为参数,应该用于“发送”数据,这里是发送到模板。
  • 附注:在模板中,我用“if”控制包裹每个表单,因为如果当前提交的表单有错误,我不想向最终用户显示一个不同表单的单独“提交”按钮。(因为一个表单的动作调用一个独特的视图,并且如果它在视图中没有通过验证,我会重新加载失败的表单。)

撰写回答