Django'daki bir sayfada birden çok formu ele almanın uygun yolu


205

İki form bekleyen bir şablon sayfam var. Sadece bir form kullanırsam, işler bu tipik örnekte olduğu gibi iyidir:

if request.method == 'POST':
    form = AuthorForm(request.POST,)
    if form.is_valid():
        form.save()
        # do something.
else:
    form = AuthorForm()

Bununla birlikte, birden çok formla çalışmak istersem, görünümün yalnızca formlardan birini değil diğerini gönderdiğimi nasıl bilmesini sağlarım (yani, hala request.POST ancak yalnızca gönderimin hangi formu işlemek istediğini) olmuş)?


Bu çözüm Yanıta göre expectedphrase ve bannedphrase farklı formlarda ve düğmeleri teslim isimleri expectedphraseform ve bannedphraseform şekilleridir.

if request.method == 'POST':
    if 'bannedphrase' in request.POST:
        bannedphraseform = BannedPhraseForm(request.POST, prefix='banned')
        if bannedphraseform.is_valid():
            bannedphraseform.save()
        expectedphraseform = ExpectedPhraseForm(prefix='expected')
    elif 'expectedphrase' in request.POST:
        expectedphraseform = ExpectedPhraseForm(request.POST, prefix='expected')
        if expectedphraseform.is_valid():
            expectedphraseform.save() 
        bannedphraseform = BannedPhraseForm(prefix='banned')
else:
    bannedphraseform = BannedPhraseForm(prefix='banned')
    expectedphraseform = ExpectedPhraseForm(prefix='expected')

2
Çözümünüzle ilgili mantıklı bir hata yok mu? 'Bannedphrase' gönderirseniz beklenen ifade biçimi doldurulmaz.
Ztyx

2
Bu soru aynı anda birden fazla form işleme ilgilidir, bir seferde sadece bir formu idare edecek
parlayan

Yanıtlar:


145

Birkaç seçeneğiniz var:

  1. İki form için eyleme farklı URL'ler koyun. Sonra iki farklı formla başa çıkmak için iki farklı görüş fonksiyonuna sahip olacaksınız.

  2. POST verilerinden gönderme düğmesi değerlerini okuyun. Hangi gönderme düğmesinin tıklandığını söyleyebilirsiniz: Birden çok gönderme düğmesi django formunu nasıl oluşturabilirim?


5
3) POST verilerindeki alan adlarından hangi formun gönderileceğini belirleyin. Sizinkileriniz, olası tüm değerlerin boş olmadığı benzersiz alanlara sahip değilse bazı gizli girişleri ekleyin.
Denis Otkidach

13
4) Formu tanımlayan gizli bir alan ekleyin ve bu alanın değerini görünümünüzde kontrol edin.
Soviut

Mümkünse POST verilerini kirletmekten uzak dururum. Bunun yerine form action url'sine bir GET parametresi eklemenizi öneririm.
pygeek

7
# 1 gerçekten en iyi bahsiniz. POST'u gizli alanlarla kirletmek istemiyorsunuz ve görünümünüzü şablonunuza ve / veya formunuza bağlamak istemiyorsunuz.
meteorainer

5
@meteorainer, bir numara kullanırsanız, iletileri ana çerçeve içinde, ileti çerçevesini veya sorgu dizelerini kullanmadan bunları başlatan formlara geri iletmenin bir yolu var mı? Bu cevap en yakın görünüyor, ancak burada her iki formu da ele alan sadece bir görünüm var: stackoverflow.com/a/21271659/2532070
YPCrumble

46

Gelecekte başvurmak için bir yöntem böyle bir şeydir. bannedphraseform ilk formdur ve beklenenfraseform ikinci formdur. Birincisi vurulursa, ikincisi atlanır (bu durumda makul bir varsayımdır):

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')

7
prefix = kullanmak gerçekten de 'uygun yoldur'
Zengin

prefix-kwarg işi yaptı, güzel!
Stephan Hoyer

1
Bu öneklerle harika bir fikir, şimdi bunları kullandık ve bir cazibe gibi çalışıyorlar. Ancak, hangi formun gönderildiğini tespit etmek için hala gizli bir alan eklememiz gerekiyordu, çünkü her iki form da bir ışık kutusunda (her biri ayrı bir formda). Doğru ışık kutusunu yeniden açmamız gerektiğinden, tam olarak hangi formun gönderildiğini bilmemiz gerekir ve ardından ilk formda herhangi bir doğrulama hatası varsa, ikincisi otomatik olarak kazanır ve ilk form sıfırlanır, ancak yine de hataları görüntülememiz gerekir. ilk form. Bilmeniz gerektiğini düşündüm
Enduriel

Bu paterni üç forma ayırmak zor olmaz mıydı? Mesela, ilk formdan is_valid (), sonra ilk iki vb. Kontrol edilerek ... Belki sadece uyumlu bir form bulunduğunda handled = Falsegüncellenen Truebir formunuz var mı?
binki

14

Django'nun sınıf tabanlı görünümleri genel bir FormView sağlar, ancak tüm amaç ve amaçlar için yalnızca bir formu işlemek üzere tasarlanmıştır.

Django'nun genel görünümlerini kullanarak aynı hedef eylem URL'sine sahip birden çok formu ele almanın bir yolu, 'TemplateView'ı aşağıda gösterildiği gibi genişletmektir; Bu yaklaşımı, bir Eclipse IDE şablonunda yaptığım kadar sık ​​kullanıyorum.

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)

Html şablonu aşağıdaki etkiye sahiptir:

...

<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>

...

1
Aynı sorunla mücadele ediyorum ve her gönderiyi ayrı bir form görünümünde işlemek ve sonra ortak bir şablon görünümüne yönlendirmek için bir yol bulmaya çalışıyordum. Burada önemli olan, şablon görünümünü içerikten ve kayıt için form görünümlerinden sorumlu tutmaktır. Doğrulama bir sorundur. Formları oturuma kaydetmek aklımdan geçti ... Hala temiz bir çözüm arıyor.
Daniele Bernardini

14

Aynı sayfada bağımsız olarak doğrulanan birden çok forma ihtiyacım vardı. Eksik olduğum temel kavramlar 1) gönder düğmesi adı için form önekini kullanarak ve 2) sınırsız bir form doğrulamayı tetiklemiyordu. Başka birine yardımcı olursa, @man-nelson ve @ daniel-sokolowski'nin cevaplarına ve @zeraien'in yorumlarına dayanarak TemplateView kullanarak iki form AForm ve BForm'un basitleştirilmiş örneğimi burada görüyorum ( https://stackoverflow.com/a/17303480 / 2680349 ):

# views.py
def _get_form(request, formcls, prefix):
    data = request.POST if prefix in request.POST else None
    return formcls(data, prefix=prefix)

class MyView(TemplateView):
    template_name = 'mytemplate.html'

    def get(self, request, *args, **kwargs):
        return self.render_to_response({'aform': AForm(prefix='aform_pre'), 'bform': BForm(prefix='bform_pre')})

    def post(self, request, *args, **kwargs):
        aform = _get_form(request, AForm, 'aform_pre')
        bform = _get_form(request, BForm, 'bform_pre')
        if aform.is_bound and aform.is_valid():
            # Process aform and render response
        elif bform.is_bound and bform.is_valid():
            # Process bform and render response
        return self.render_to_response({'aform': aform, 'bform': bform})

# mytemplate.html
<form action="" method="post">
    {% csrf_token %}
    {{ aform.as_p }}
    <input type="submit" name="{{aform.prefix}}" value="Submit" />
    {{ bform.as_p }}
    <input type="submit" name="{{bform.prefix}}" value="Submit" />
</form>

Bence bu aslında temiz bir çözüm. Teşekkürler.
chhantyal

Bu çözümü gerçekten çok seviyorum. Bir soru: _get_form () yönteminin MyView sınıfının bir yöntemi olmamasının bir nedeni var mı?
hava saldırısı

1
@ AndréTerra kesinlikle olabilir, ancak muhtemelen diğer görünümlerde tekrar kullanabilmeniz için TemplateView'den devralan genel bir sınıfta olmasını istersiniz.
ybendana

1
Bu harika bir çözüm. Ben işe yarayacak şekilde __get_form bir satırını değiştirmek gerekiyordu: data = request.POST if prefix in next(iter(request.POST.keys())) else None Aksi takdirde inişe yaramadı.
larapsodia

Bunun gibi tek bir <form> etiketi kullanmak, gerekli gönderme düğmesinin tıklatıldığına bağlı olarak form başına olması gerektiğinde genel olarak zorunlu alanların gerekli olduğu anlamına gelir. İki <form> etiketine bölme (aynı eylemle) çalışır.
Flash

3

Çözümümü Django Forms kullanılmayan yerlerde paylaşmak istedim. Tek bir sayfada birden çok form öğesi var ve tüm formlardan gelen tüm POST isteklerini yönetmek için tek bir görünüm kullanmak istiyorum.

Yaptığım şey, görünmez bir giriş etiketi ekledim, böylece hangi formun gönderildiğini kontrol etmek için görünümlere bir parametre iletebiliyorum.

<form method="post" id="formOne">
    {% csrf_token %}
   <input type="hidden" name="form_type" value="formOne">

    .....
</form>

.....

<form method="post" id="formTwo">
    {% csrf_token %}
    <input type="hidden" name="form_type" value="formTwo">
   ....
</form>

views.py

def handlemultipleforms(request, template="handle/multiple_forms.html"):
    """
    Handle Multiple <form></form> elements
    """
    if request.method == 'POST':
        if request.POST.get("form_type") == 'formOne':
            #Handle Elements from first Form
        elif request.POST.get("form_type") == 'formTwo':
            #Handle Elements from second Form

Bence bu iyi ve kolay bir çıkış yolu
Shedrack

2

Bu biraz geç, ama bulduğum en iyi çözüm bu. Form adı ve sınıfı için bir arama sözlüğü yaparsınız, ayrıca formu tanımlamak için bir öznitelik eklemeniz gerekir ve görünümlerinizde ile gizli bir alan olarak eklemeniz gerekir 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)

Umarım bu gelecekte yardımcı olur.


2

Sınıf tabanlı görünümler ve farklı 'eylem' eylemleriyle yaklaşım kullanıyorsanız

İki form için eyleme farklı URL'ler koyun. Sonra iki farklı formla başa çıkmak için iki farklı görüş fonksiyonuna sahip olacaksınız.

Aşırı yüklenmiş get_context_datayöntemi kullanarak farklı formlardaki hataları kolayca işleyebilirsiniz , örn:

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

şablonu:

<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>

2

görünüm:

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")
    ...

şablonu:

{% 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 %}

4
Cevabınızı açıklar mısınız? Bir benzer sorunu olan başkalarına yardımcı olur ve sizin veya sorgulayanların kodunda hata ayıklamaya yardımcı olabilir ...
creyD

0

Yukarıdakileri ele almanın basit yolu.

Html Şablonunda Post koyduk

<form action="/useradd/addnewroute/" method="post" id="login-form">{% csrf_token %}

<!-- add details of form here-->
<form>
<form action="/useradd/addarea/" method="post" id="login-form">{% csrf_token %}

<!-- add details of form here-->

<form>

Görünümünde

   def addnewroute(request):
      if request.method == "POST":
         # do something



  def addarea(request):
      if request.method == "POST":
         # do something

URL'de aşağıdaki gibi gerekli bilgileri verin

urlpatterns = patterns('',
url(r'^addnewroute/$', views.addnewroute, name='addnewroute'),
url(r'^addarea/', include('usermodules.urls')),
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.