Django: как правильно использовать две формы в одном шаблоне для двух моделей, которые связаны внешним ключом

#django #django-models #django-views #django-forms #django-templates

#django #django-модели #django-представления #django-forms #django-шаблоны

Вопрос:

У меня есть 2 модели: Applicant и Application . 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 шаблоне таким образом, чтобы при отправке оба Applicant и Application могли быть полностью заполнены и сохранены. Я пробовал 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 %}
 

Комментарии:

1. Не могли бы вы также включить свой HTML, пожалуйста? Похоже, вы что-то упускаете из виду, основываясь на этой ошибке.

2. @markwalker_ Хорошо, да. Мне не удалось отобразить обе формы, не передавая в контекст обе формы, а также formset, что для меня не имеет смысла.

3. @markwalker_ Я только что понял это. По какой-то причине я индексировал поле foreignkey, а не другие поля. Я исправил параметры inlineformset, и теперь он работает. Спасибо, что помогли мне продумать это.

4. @markwalker_ Оказывается, я этого не понял. Форма заявителя сохраняется в БД, а приложение — нет. По какой-то причине он не проверяется, поэтому view отправляет только форму заявителя. Я не могу понять, почему это так.

5. Да, как указал @NKSM в своем ответе, это потому, что вам не хватает формы управления. Я подозревал это.

Ответ №1:

Вы должны добавить {{ 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
 

Комментарии:

1. Спасибо. Я добавил ‘formset.management_form`, но это не помогло. Кроме того, если я использую {% for form in formset %} {{form}} {% endfor %} , ничего не отображается .. только кнопка отправки.

2. Разве это не то, что делает мой def get_context_data(self, **kwargs): метод? if self.request.POST: else Я попробовал предложенный вами код и оценил его, но я получил AttributeError и Signature of method 'ApplicantApplication.form_invalid()' does not match signature of base method in class 'FormMixin

3. get_context_data запускается после метода post . Итак, в get_context_data форма уже недействительна.

4. Спасибо за вашу помощь. Я внес изменения и теперь получаю ошибку в строке 109 return self.form_invalid(form, form_applicant) TypeError takes 2 arguments but was given 3 . строка 109 находится в методе POST и указывает, что форма недействительна, и именно здесь у меня также возникли проблемы с get_context_data методом. Изначально это был один из моих источников инструкций: dev.to/zxenia /…

5. Удалите form_applicant из return self.form_invalid(форма, form_applicant . Я забыл его удалить.