首页 > 解决方案 > Django:如何在一个模板中为通过外键连接的两个模型正确使用两个表单

问题描述

我有 2 个模型:ApplicantApplication. Application通过外键连接到Applicant。对于任何申请人,都可以有许多申请。

models.py

   class Applicant(models.Model):
        first_name = models.CharField(max_length=150, null=False, blank=False)
        last_name = models.CharField(max_length=150, null=False, blank=False)
        email = models.EmailField(blank=False, null=True) 
        ...

    class Application(models.Model):
        applicant = models.ForeignKey(Applicant, null=False, blank=False, on_delete=models.CASCADE)
        ...

我无法弄清楚如何让 2 个表单,对于 2 个模型,显示在 1 个模板中,以便在提交时两者ApplicantApplication可以完全填充和保存。我尝试过 inlineformsets,但我不断收到本质上Application必须有applicant实例的错误。然后我尝试只使用两个单独的表单并分别保存它们,但我得到了同样的错误。我该如何纠正?

forms.py

class ApplicantForm(ModelForm):
    veterinaryContactPermission = forms.BooleanField(widget=forms.CheckboxInput)
    class Meta:
        model= Applicant
        fields = '__all__'

class ApplicationForm(ModelForm):
    class Meta:
        model = Application
        fields = '__all__'

ApplicantFormset = inlineformset_factory(
    Applicant, Application, form=ApplicationForm,
    fields=['applicant'], can_delete=False 
)

views.py

class ApplicantApplication(CreateView):
    model = Applicant
    # form1 = ApplicantForm
    # form2 = ApplicationForm
    form_class = ApplicantForm
    template_name = 'animals/register.html'
    success_url = reverse_lazy('animals:applicant_profile_complete')

    def get_context_data(self, **kwargs):
        data = super(ApplicantApplication, self).get_context_data(**kwargs)
        data['form_applicant'] = ApplicantFormset()
        if self.request.POST:
            data['form_applicant'] = ApplicantFormset(self.request.POST, self.request.FILES)
            # context = data
        else:
            data['form_applicant'] = ApplicantFormset()
            # context = {'data':data, 'form1': self.form1, 'form2': self.form2}
        return data

    def form_valid(self, form):
        context = self.get_context_data()
        form_app = context['form_applicant']
        with transaction.atomic():
            self.object = form.save()
            if form_app.is_valid():
                form_app.instance = self.object
                form_app.save()
        return super(ApplicantApplication, self).form_valid(form)

ValidationError at /animals/register/
['ManagementForm data is missing or has been tampered with']
Request Method: POST
Request URL:    http://127.0.0.1:8000/animals/register/
Django Version: 3.1.4
Exception Type: ValidationError
Exception Value:    
['ManagementForm data is missing or has been tampered with']
Exception Location: /Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/django/forms/formsets.py, line 94, in management_form
Python Executable:  /Users/zb/Desktop/animalDirectoryTemplate/.venv/bin/python
Python Version: 3.7.1
Python Path:    
['/Users/zb/Desktop/animalDirectoryTemplate',
 '/Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip',
 '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7',
 '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload',
 '/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages',
 '/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/setuptools-39.1.0-py3.7.egg',
 '/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg']
Server time:    Mon, 18 Jan 2021 00:50:08 +0000
Traceback Switch to copy-and-paste view
/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/django/core/handlers/exception.py, line 47, in inner
                response = await sync_to_async(response_for_exception, thread_sensitive=False)(request, exc)
            return response
        return inner
    else:
        @wraps(get_response)
        def inner(request):
            try:
                response = get_response(request) …
            except Exception as exc:
                response = response_for_exception(request, exc)
            return response
        return inner
▶ Local vars
/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/django/core/handlers/base.py, line 179, in _get_response
        if response is None:
            wrapped_callback = self.make_view_atomic(callback)
            # If it is an asynchronous view, run it in a subthread.
            if asyncio.iscoroutinefunction(wrapped_callback):
                wrapped_callback = async_to_sync(wrapped_callback)
            try:
                response = wrapped_callback(request, *callback_args, **callback_kwargs) …
            except Exception as e:
                response = self.process_exception_by_middleware(e, request)
                if response is None:
                    raise
        # Complain if the view returned None (a common error).
▶ Local vars
/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/django/views/generic/base.py, line 70, in view
            return self.dispatch(request, *args, **kwargs) …
▶ Local vars
/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/django/views/generic/base.py, line 98, in dispatch
        return handler(request, *args, **kwargs) …
▶ Local vars
/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/django/views/generic/edit.py, line 172, in post
        return super().post(request, *args, **kwargs) …
▶ Local vars
/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/django/views/generic/edit.py, line 142, in post
            return self.form_valid(form) …
▶ Local vars
/Users/zb/Desktop/animalDirectoryTemplate/animals/views.py, line 321, in form_valid
            if form_app.is_valid(): …
▶ Local vars
/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/django/forms/formsets.py, line 308, in is_valid
        self.errors …
▶ Local vars
/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/django/forms/formsets.py, line 288, in errors
            self.full_clean() …
▶ Local vars
/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/django/forms/formsets.py, line 329, in full_clean
        for i in range(0, self.total_form_count()): …
▶ Local vars
/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/django/forms/formsets.py, line 112, in total_form_count
            return min(self.management_form.cleaned_data[TOTAL_FORM_COUNT], self.absolute_max) …
▶ Local vars
/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/django/utils/functional.py, line 48, in __get__
        res = instance.__dict__[self.name] = self.func(instance) …
▶ Local vars
/Users/zb/Desktop/animalDirectoryTemplate/.venv/lib/python3.7/site-packages/django/forms/formsets.py, line 94, in management_form
                    code='missing_management_form', …
▶ Local vars
Request information
USER
zbrenner

GET
No GET data

POST
Variable    Value
csrfmiddlewaretoken 
'MoRtdL69wHGAiSL2cEX3sTu1ODUtspeH6z86bMMEtjjWr9SIanAkiuiNWdeU8DUk'
first_name  
'Z'
last_name   
'B'
phone   
'644-777-2222'
email   
'zbreadkwelkdak@gmail.com'
streetAddress   
'8843 all ln'
city    
'San Clemente'
state   
'CA'
country 
'United States'
age 
'51'
job 
'Physician'
kids    
'0'
kidDeets    
''
adults  
'0'
adultDeets  
''
cats    
'1'
dogs    
'2'
otherAnimals    
'0'
animalDeets 
''
veterinaryClinic    
'Clinic'
veterinaryContactPermission 
'on'
veterinaryPhone 
'900-444-0000'
homeOwnership   
'on'
landlordApproves    
'on'
landlordContactPermission   
'on'
landlordName    
'Landlord'
landlordPhone   
'555-444-3333'
hoursAway   
'3'
haveBackYard    
'on'
yardSizeInAcres 
'150'
haveFence   
'on'
fenceDeets  
'6 Feet Tall, Mixed wire and wood'
yardPhoto   
''
housePhoto  
''
FILES
No FILES data

COOKIES
Variable    Value
sessionid   
'2uyil6sff65o46phqvayxewigoy3p01e'
csrftoken   
'IFiwj7RpZ998CENmWAbfLJIewayhTEwS2Qz9h8xUWLMuLVU2UjOwBkw0EKSIzScv'
META
Variable    Value
Apple_PubSub_Socket_Render  
'/private/tmp/com.apple.launchd.zHVr0NmLOt/Render'
BASH_FUNC_generate_command_executed_sequence%%  
"() {  printf '\\e\\7'\n}"
CONTENT_LENGTH  
'655'
CONTENT_TYPE    
'application/x-www-form-urlencoded'
CSRF_COOKIE 
'IFiwj7RpZ998CENmWAbfLJIewayhTEwS2Qz9h8xUWLMuLVU2UjOwBkw0EKSIzScv'
DJANGO_SETTINGS_MODULE  
'animalDirectoryTemplate.settings'

register.html

    {% extends 'base.html' %}
{% load static %}
{% load bootstrap %}
{% block head %}
    <link rel="stylesheet" href="{% static 'node_modules/bootstrap/dist/css/bootstrap.min.css' %}">
{% endblock %}
{% block content %}
    <h2>Application</h2>
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}

        <table>
{#            {{ form1.as_table }}#} {# The only way I've been able to display both forms which means I'm not using formset...? #}
            {{ form.as_table  }} {# Displays Applicant form #}
        </table>
        <table>
            {{ form_applicant.as_table }} {# Should display Application form but it doesn't. It doesn't display anything visible #}
{#           {{ form2.as_table }}#} {# The only way I've been able to display both forms #}
        </table>
        <input type="submit" value="Submit">
        <input type="submit" onclick="window.location='{% url 'animals:animals' %}'; return false;" value="Cancel">
    </form>
{% endblock %}

标签: djangodjango-modelsdjango-viewsdjango-formsdjango-templates

解决方案


您必须添加{{ formset.management_form }}.

Django 在视图和模板中使用表单集

例子:

<form method="post">
    {% csrf_token %}
    {{ form.as_table }}
    <hr>
    {{ form_applicant.management_form }}
    <table>
        {% for form in form_applicant %}
        {{ form }}
        {% endfor %}
    </table>
</form>

额外的

def post(self, request, *args, **kwargs):
    """
    Handles POST requests, instantiating a form instance and its inline
    formsets with the passed POST variables and then checking them for
    validity.
    """
    form_class = self.get_form_class()
    form = self.get_form(form_class)
    form_applicant = ApplicantFormset(self.request.POST, self.request.FILES)
    if (form.is_valid() and form_applicant.is_valid()):
        return self.form_valid(form)
    else:
        return self.form_invalid(form)


def form_valid(self, form):
    form_applicant = ApplicantFormset(self.request.POST, self.request.FILES)
    self.object = form.save()
    ....
    return HttpResponseRedirect(self.get_success_url())


def get_context_data(self, **kwargs):
    data = super(ApplicantApplication, self).get_context_data(**kwargs)
    data['form_applicant'] = ApplicantFormset()
    if self.request.POST:
        data['form_applicant'] = ApplicantFormset(self.request.POST, self.request.FILES)
        # context = data
    else:
        data['form_applicant'] = ApplicantFormset()
        # context = {'data':data, 'form1': self.form1, 'form2': self.form2}
    return data

推荐阅读