Proper way to handle multiple forms on one page in Django

后端 未结 10 1987
既然无缘
既然无缘 2020-11-22 09:19

I have a template page expecting two forms. If I just use one form, things are fine as in this typical example:

if request.method == \'POST\':
    form = Au         


        
相关标签:
10条回答
  • 2020-11-22 09:25
    if request.method == 'POST':
        expectedphraseform = ExpectedphraseForm(request.POST)
        bannedphraseform = BannedphraseForm(request.POST)
        if expectedphraseform.is_valid():
            expectedphraseform.save()
            return HttpResponse("Success")
        if bannedphraseform.is_valid():
            bannedphraseform.save()
            return HttpResponse("Success")
    else:
        bannedphraseform = BannedphraseForm()
        expectedphraseform = ExpectedphraseForm()
    return render(request, 'some.html',{'bannedphraseform':bannedphraseform, 'expectedphraseform':expectedphraseform})
    

    This worked for me accurately as I wanted. This Approach has a single problem that it validates both the form's errors. But works Totally fine.

    0 讨论(0)
  • 2020-11-22 09:28

    view:

    class AddProductView(generic.TemplateView):
    template_name = 'manager/add_product.html'
    
        def get(self, request, *args, **kwargs):
        form = ProductForm(self.request.GET or None, prefix="sch")
        sub_form = ImageForm(self.request.GET or None, prefix="loc")
        context = super(AddProductView, self).get_context_data(**kwargs)
        context['form'] = form
        context['sub_form'] = sub_form
        return self.render_to_response(context)
    
    def post(self, request, *args, **kwargs):
        form = ProductForm(request.POST,  prefix="sch")
        sub_form = ImageForm(request.POST, prefix="loc")
        ...
    

    template:

    {% block container %}
    <div class="container">
        <br/>
        <form action="{% url 'manager:add_product' %}" method="post">
            {% csrf_token %}
            {{ form.as_p }}
            {{ sub_form.as_p }}
            <p>
                <button type="submit">Submit</button>
            </p>
        </form>
    </div>
    {% endblock %}
    
    0 讨论(0)
  • 2020-11-22 09:30

    This is a bit late, but this is the best solution I found. You make a look-up dictionary for the form name and its class, you also have to add an attribute to identify the form, and in your views you have to add it as a hidden field, with the form.formlabel.

    # form holder
    form_holder = {
        'majeur': {
            'class': FormClass1,
        },
        'majsoft': {
            'class': FormClass2,
        },
        'tiers1': {
            'class': FormClass3,
        },
        'tiers2': {
            'class': FormClass4,
        },
        'tiers3': {
            'class': FormClass5,
        },
        'tiers4': {
            'class': FormClass6,
        },
    }
    
    for key in form_holder.keys():
        # If the key is the same as the formlabel, we should use the posted data
        if request.POST.get('formlabel', None) == key:
            # Get the form and initate it with the sent data
            form = form_holder.get(key).get('class')(
                data=request.POST
            )
    
            # Validate the form
            if form.is_valid():
                # Correct data entries
                messages.info(request, _(u"Configuration validée."))
    
                if form.save():
                    # Save succeeded
                    messages.success(
                        request,
                        _(u"Données enregistrées avec succès.")
                    )
                else:
                    # Save failed
                    messages.warning(
                        request,
                        _(u"Un problème est survenu pendant l'enregistrement "
                          u"des données, merci de réessayer plus tard.")
                    )
            else:
                # Form is not valid, show feedback to the user
                messages.error(
                    request,
                    _(u"Merci de corriger les erreurs suivantes.")
                )
        else:
            # Just initiate the form without data
            form = form_holder.get(key).get('class')(key)()
    
        # Add the attribute for the name
        setattr(form, 'formlabel', key)
    
        # Append it to the tempalte variable that will hold all the forms
        forms.append(form)
    

    I hope this will help in the future.

    0 讨论(0)
  • 2020-11-22 09:37

    Django's class based views provide a generic FormView but for all intents and purposes it is designed to only handle one form.

    One way to handle multiple forms with same target action url using Django's generic views is to extend the 'TemplateView' as shown below; I use this approach often enough that I have made it into an Eclipse IDE template.

    class NegotiationGroupMultifacetedView(TemplateView):
        ### TemplateResponseMixin
        template_name = 'offers/offer_detail.html'
    
        ### ContextMixin 
        def get_context_data(self, **kwargs):
            """ Adds extra content to our template """
            context = super(NegotiationGroupDetailView, self).get_context_data(**kwargs)
    
            ...
    
            context['negotiation_bid_form'] = NegotiationBidForm(
                prefix='NegotiationBidForm', 
                ...
                # Multiple 'submit' button paths should be handled in form's .save()/clean()
                data = self.request.POST if bool(set(['NegotiationBidForm-submit-counter-bid',
                                                  'NegotiationBidForm-submit-approve-bid',
                                                  'NegotiationBidForm-submit-decline-further-bids']).intersection(
                                                        self.request.POST)) else None,
                )
            context['offer_attachment_form'] = NegotiationAttachmentForm(
                prefix='NegotiationAttachment', 
                ...
                data = self.request.POST if 'NegotiationAttachment-submit' in self.request.POST else None,
                files = self.request.FILES if 'NegotiationAttachment-submit' in self.request.POST else None
                )
            context['offer_contact_form'] = NegotiationContactForm()
            return context
    
        ### NegotiationGroupDetailView 
        def post(self, request, *args, **kwargs):
            context = self.get_context_data(**kwargs)
    
            if context['negotiation_bid_form'].is_valid():
                instance = context['negotiation_bid_form'].save()
                messages.success(request, 'Your offer bid #{0} has been submitted.'.format(instance.pk))
            elif context['offer_attachment_form'].is_valid():
                instance = context['offer_attachment_form'].save()
                messages.success(request, 'Your offer attachment #{0} has been submitted.'.format(instance.pk))
                    # advise of any errors
    
            else 
                messages.error('Error(s) encountered during form processing, please review below and re-submit')
    
            return self.render_to_response(context)
    

    The html template is to the following effect:

    ...
    
    <form id='offer_negotiation_form' class="content-form" action='./' enctype="multipart/form-data" method="post" accept-charset="utf-8">
        {% csrf_token %}
        {{ negotiation_bid_form.as_p }}
        ...
        <input type="submit" name="{{ negotiation_bid_form.prefix }}-submit-counter-bid" 
        title="Submit a counter bid"
        value="Counter Bid" />
    </form>
    
    ...
    
    <form id='offer-attachment-form' class="content-form" action='./' enctype="multipart/form-data" method="post" accept-charset="utf-8">
        {% csrf_token %}
        {{ offer_attachment_form.as_p }}
    
        <input name="{{ offer_attachment_form.prefix }}-submit" type="submit" value="Submit" />
    </form>
    
    ...
    
    0 讨论(0)
  • 2020-11-22 09:37

    If you are using approach with class-based views and different 'action' attrs i mean

    Put different URLs in the action for the two forms. Then you'll have two different view functions to deal with the two different forms.

    You can easily handle errors from different forms using overloaded get_context_data method, e.x:

    views.py:

    class LoginView(FormView):
        form_class = AuthFormEdited
        success_url = '/'
        template_name = 'main/index.html'
    
        def dispatch(self, request, *args, **kwargs):
            return super(LoginView, self).dispatch(request, *args, **kwargs)
    
        ....
    
        def get_context_data(self, **kwargs):
            context = super(LoginView, self).get_context_data(**kwargs)
            context['login_view_in_action'] = True
            return context
    
    class SignInView(FormView):
        form_class = SignInForm
        success_url = '/'
        template_name = 'main/index.html'
    
        def dispatch(self, request, *args, **kwargs):
            return super(SignInView, self).dispatch(request, *args, **kwargs)
    
        .....
    
        def get_context_data(self, **kwargs):
            context = super(SignInView, self).get_context_data(**kwargs)
            context['login_view_in_action'] = False
            return context
    

    template:

    <div class="login-form">
    <form action="/login/" method="post" role="form">
        {% csrf_token %}
        {% if login_view_in_action %}
            {% for e in form.non_field_errors %}
                <div class="alert alert-danger alert-dismissable">
                    {{ e }}
                    <a class="panel-close close" data-dismiss="alert">×</a>
                </div>
            {% endfor %}
        {% endif %}
        .....
        </form>
    </div>
    
    <div class="signin-form">
    <form action="/registration/" method="post" role="form">
        {% csrf_token %}
        {% if not login_view_in_action %}
            {% for e in form.non_field_errors %}
                <div class="alert alert-danger alert-dismissable">
                    {{ e }}
                    <a class="panel-close close" data-dismiss="alert">×</a>
                </div>
            {% endfor %}
        {% endif %}
       ....
      </form>
    </div>
    
    0 讨论(0)
  • 2020-11-22 09:39

    A method for future reference is something like this. bannedphraseform is the first form and expectedphraseform is the second. If the first one is hit, the second one is skipped (which is a reasonable assumption in this case):

    if request.method == 'POST':
        bannedphraseform = BannedPhraseForm(request.POST, prefix='banned')
        if bannedphraseform.is_valid():
            bannedphraseform.save()
    else:
        bannedphraseform = BannedPhraseForm(prefix='banned')
    
    if request.method == 'POST' and not bannedphraseform.is_valid():
        expectedphraseform = ExpectedPhraseForm(request.POST, prefix='expected')
        bannedphraseform = BannedPhraseForm(prefix='banned')
        if expectedphraseform.is_valid():
            expectedphraseform.save()
    
    else:
        expectedphraseform = ExpectedPhraseForm(prefix='expected')
    
    0 讨论(0)
提交回复
热议问题