在Django模板中显示两个表单

0 投票
2 回答
2731 浏览
提问于 2025-04-18 17:54

我的目标是把两个模型放在一个模板里。我尝试了很多方法,但都没有成功。最开始我有两个视图、两个模型和两个表单。后来我发现有人在用inlineformsets,所以我放弃了其中一个视图,设置了inlineformset。

现在我到了这个阶段,但似乎遇到了瓶颈。

模板在浏览器中正常显示,'object_list'部分能按预期显示数据库内容,而'form'部分也能正确渲染表单并验证/保存数据。问题出在'formset'上。没有任何字段被渲染(我本来期待看到一个下拉框,因为这个字段是外键),而当我按下'提交'按钮时,我得到了:

AttributeError at /settings/

'NoneType' object has no attribute 'save'

如果能帮我找到错误或者给我一些其他解决方案的建议,我将非常感激。

代码:

models.py

from django.db import models

class RevisionSettings(models.Model):    
    global_revision_type = models.CharField(max_length = 5, unique=True, blank = True)
    global_revision_description = models.CharField(max_length = 300, unique=True, blank = True)

    class Meta:
        ordering = ["global_revision_type"]

    def __unicode__(self):  
        return u'%s %s' % (self.global_revision_type, self.global_revision_description)


class RevisionDefaultType(models.Model):
    defaultrevisiontype = models.ForeignKey(RevisionSettings)

    class Meta:
        ordering = ["defaultrevisiontype"]

    def __unicode__(self):  
        return unicode(self.defaultrevisiontype)

views.py

class RevisionSettingsView(CreateView):
    template_name = 'settings/revisionsettings_view.html'
    model = RevisionSettings
    form_class = SettingsForm
    success_url = reverse_lazy('globalsettings')
    success_message = 'Successfully added your new revision type'

    def get(self, request, *args, **kwargs):
        self.object = None
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        formset = SettingsFormSet(instance = RevisionSettings)
    return self.render_to_response(
        self.get_context_data(form=form,
                              formset=formset))

    def post(self, request, *args, **kwargs):
        self.object = None
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        formset = SettingsFormSet(self.request.POST)
        if 'rev_settings_form_1' in self.request.POST:
            if form.is_valid():
                return self.form_valid(form)
            else:
                return self.form_invalid(form)
        elif 'rev_settings_form_2' in self.request.POST:
            if formset.is_valid():
                return self.formset_valid(formset)
            else:
                return self.form_invalid(formset)

    def form_valid(self, form):
        self.object = form.save()
        self.object.save()
        return HttpResponseRedirect(self.get_success_url())

    def formset_valid(self, formset):
        self.object.save()
        formset.instance = self.object
        formset.save()
        return HttpResponseRedirect(self.get_success_url())

    def form_invalid(self, form, formset):
        return self.render_to_response(self.get_context_data(form=form,formset=formset))

    def get_context_data(self, **kwargs):
        kwargs['object_list'] = RevisionSettings.objects.order_by('global_revision_type')
        return super(RevisionSettingsView, self).get_context_data(**kwargs)

forms.py

from django import forms
from django.forms.models import inlineformset_factory

from .models import RevisionSettings, RevisionDefaultType

class SettingsForm(forms.ModelForm):

    class Meta:
        model = RevisionSettings

class DefaultSettingsForm(forms.ModelForm):

    class Meta:
        model = RevisionDefaultType

SettingsFormSet = inlineformset_factory(RevisionSettings, RevisionDefaultType)

revisionsettings_view.html

(我去掉了大部分HTML样式,以便信息更直接)

{% extends 'base_private.html' %}

{% block content %}

    {% for object in object_list %}

        <tr>
            <td align="center">{{ object.global_revision_type }}</td>
            <td align="center">{{ object.global_revision_description }}</td>
            <td align="center"><a href="/settings/{{ object.id }}/delete" class="tooltip-test" title="" data-original-title="Delete selected revision type"><span class="glyphicon glyphicon-remove-circle"></span></a></td>
        </tr>

    {% endfor %}

    <form action = '{{ action }}' method = 'POST' class="form-horizontal" role="form">
        {% csrf_token %}

        <tr>
            <td align="center">
                {{ formset.management_form }}
                {% for form in formset %}
                    {{ form.id }}
                    {{ form.defaultrevisiontype.label_tag }}
                    {{ form.defaultrevisiontype }}
                {% endfor %}
            </td>
        </tr>

        <span class="input-group-addon">
                <input type = 'submit' name = 'rev_settings_form_2' value = 'Update Default Revision Type' class = 'btn btn-success'>
        </span>

            <td align="center">{{ form.global_revision_type }}{{ form.global_revision_type.errors }}</td>
            <td align="center">{{ form.global_revision_description }}{{ form.global_revision_description.errors }}</td>
        </tr>

        <span class="input-group-addon">
                <input type = 'submit' name = 'rev_settings_form_1' value = 'Add Revision Type' class = 'btn btn-success'>
        </span>

    </form>

{% endblock %}

2 个回答

0

谢谢大家的帮助。你们的建议真的让我找到了这个解决办法。主要的改变是下面这个 'def get'。我去掉了原来的表单集合,改成这样传递表单。

def get(self, request, *args, **kwargs):
     form = self.settings_form_class
     formset = self.default_form_class

     return self.render_to_response(self.get_context_data(form = form, formset = formset))

我之前不知道这样是可以的!再次感谢大家。

1

对于两个表单来说,使用表单集(formsets)其实有点过于复杂了。这其实并不难,但文档写得不太好。你可以把两个表单设置成同一种类型,只需要加个前缀就行。

def parent_apply(request):
  if request.method == 'POST':
    parent_form = SignupForm(request.POST, prefix="parent")
    student_form = StudentApplyForm(request.POST, prefix="student")
    if parent_form.is_valid() and student_form.is_valid():
      parent = parent_form.save()
      student = student_form.save(parent)
    else: messages.error(request, "Please correct the errors marked in red.")
  else:
    parent_form = SignupForm(prefix="parent")
    student_form = StudentApplyForm(prefix="student")

  return render_to_response('template_path/forms.html', { 'parent_form':parent_form, 'student_form':student_form }, context_instance=RequestContext(request))

这些表单就是普通的Django表单,不需要什么特别的设置。你可以选择验证和保存的顺序,甚至可以在一个表单没有通过验证的情况下,仍然保存另一个表单。

在你的HTML模板中,把两个表单放在同一个标签里,这样它们就可以同时提交。如果你想让这两个表单分别去不同的处理函数,就要指定两个不同的元素。

撰写回答