Two forms in one template

I have two forms in one template, but one of them doesn’t have data to my database.

Why does it happen and how to fix it?

When I fill out the Pack form and press “submit” the terminal shows that: “POST” /sklad HTTP/1.1" 200 9937

This data doesn’t save to my database.

When I fill out the Sklad form and press “submit” the terminal shows that: “POST” /sklad HTTP/1.1" 302 0

This data saves to my database fine.

views.py

class SkladCreateView(LoginRequiredMixin, CustomSuccessMessageMixin, CreateView):
model = Sklad
template_name = 'sklad.html'
form_class = SkladForm
success_url = reverse_lazy('sklad')
success_msg = 'Материал сохранён'
def get_context_data(self, **kwargs):
    kwargs['sklad_form'] = SkladForm
    kwargs['pack_form'] = PackForm
    kwargs['list_sklad'] = Sklad.objects.all().order_by('material')
    kwargs['list_pack'] = Pack.objects.all().order_by('name_pack')
    return super().get_context_data(**kwargs)     
def form_valid(self, form):
    self.object = form.save(commit=False)
    self.object.author = self.request.user
    self.object.save()
    return super().form_valid(form)

models.py

class Sklad(models.Model):
	author = models.ForeignKey(User, on_delete = models.CASCADE, verbose_name='автор склада', null=True)
	material = models.CharField('название', max_length=200)
	unit = models.CharField('единица измерения', max_length=200)
	description = models.CharField('описание', max_length=200, null=True)
	price_buy = models.IntegerField('цена закупки', )
	price_sell = models.IntegerField('цена продажи', )
	amount = models.IntegerField('количество', default='0')

	def __str__(self):
		return self.material

class Pack(models.Model):
	author = models.ForeignKey(User, on_delete = models.CASCADE, verbose_name='автор упаковки', null=True)
	name_pack = models.CharField('название', max_length=100)
	price_pack = models.IntegerField('цена', )

	def __str__(self):
		return self.name_pack

forms.py

class SkladForm(forms.ModelForm):
    class Meta:
        model = Sklad
        fields = (
            'material',
            'unit',
            'description',
            'price_buy',
            'price_sell',
            'amount',
        )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields:
            self.fields[field].widget.attrs['class'] = 'form-control'

class PackForm(forms.ModelForm):
    class Meta:
        model = Pack
        fields = (
            'name_pack',
            'price_pack',
        )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields:
            self.fields[field].widget.attrs['class'] = 'form-control'

I think we’re probably going to need to see the template, too.

{% extends 'index.html' %}



    {% block content %}
<div class="row">
    <h1 class="mt-4">Материалы</h1>
</div>

<div class="row">
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary mr-4" data-toggle="modal" data-target="#staticBackdrop">
  Добавить материалы
</button>


<a class="btn btn-primary" href={% url 'pack' %} role="button">
  Упаковка
</a>

<button type="button" class="btn btn-primary mr-4" data-toggle="modal" data-target="#staticPack">
  Добавить упаковку
</button>

</div>


<!-- так выводиться сообщения из класса CustomSuccessMessageMixin-->
    {% if messages %}
    <div class="alert alert-success">
      {% for m in messages %}
      {{m}}
      {% endfor %}
    </div>
    {% endif %}


    {% if update %}

    <p>update</p>


        <form id="update_data" method="post">
            {% csrf_token %}
            {{sklad_form}}
        </form>

        <button form="update_data" type="submit" class="btn btn-primary">Сохранить материал</button>

    {% else %}


<table class="table table-bordered mt-4">
  <thead>
    <tr>
      <th scope="col">Наименование</th>
      <th scope="col">Еденица измерения</th>
      <th scope="col">Цена закупки</th>
      <th scope="col">Цена продажи</th>
      <th scope="col">Количество</th>
      <th scope="col">Удалить</th>
    </tr>
  </thead>
  <tbody>
  
    {% for i in list_sklad %}
    <!-- эта проверка подсвечивает строку при обновлении или создании -->
    <tr {% if i.id|slugify == request.GET.id %} style="background:green" {% endif %}>
      {% if request.user == i.author %}
      <th><a href="{% url 'detail_sklad' i.id %}">{{i.material}}</a></th>
      <td>{{i.unit}}</td>
      <td>{{i.price_buy}}</td>
      <td>{{i.price_sell}}</td>
      <td>{{i.amount}}</td>
      <td>
        <form id="delete_form{{i.id}}" action="{% url 'delete_sklad' i.id %}" method="post">{% csrf_token %}</form>
        <a href="javascript:void()" onclick="delete_question{{i.id}}()" >удалить</a>
        <script>
          function delete_question{{i.id}}() {
            if (confirm("Вы уверены?")) {
              document.getElementById('delete_form{{i.id}}').submit()
            }
          }
        </script>
      </td>
    </tr>
    {% endif %}
    {% endfor %}
  </tbody>
</table>

<table class="table table-bordered mt-4">
  <thead>
    <tr>
      <th scope="col">Наименование</th>
      <th scope="col">Цена продажи</th>
      <th scope="col">Удалить</th>
    </tr>
  </thead>
  <tbody>
  
    {% for i in list_pack %}
    <!-- эта проверка подсвечивает строку при обновлении или создании -->
    <tr {% if i.id|slugify == request.GET.id %} style="background:green" {% endif %}>
      {% if request.user == i.author %}
      <th><a href="{% url 'detail_sklad' i.id %}">{{i.name_pack}}</a></th>
      <td>{{i.price_pack}}</td>
      <td>
        <form id="delete_form{{i.id}}" action="{% url 'delete_sklad' i.id %}" method="post">{% csrf_token %}</form>
        <a href="javascript:void()" onclick="delete_question{{i.id}}()" >удалить</a>
        <script>
          function delete_question{{i.id}}() {
            if (confirm("Вы уверены?")) {
              document.getElementById('delete_form{{i.id}}').submit()
            }
          }
        </script>
      </td>
    </tr>
    {% endif %}
    {% endfor %}
  </tbody>
</table>

{% endif %}


<!-- Modal materials-->
<div class="modal fade" id="staticBackdrop" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="staticBackdropLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="staticBackdropLabel">Добавить материал</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form id="add_form" method="post">
            {% csrf_token %}
            {{sklad_form}}
        </form>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">Закрыть</button>
        <button form="add_form" type="submit" class="btn btn-primary">Сохранить</button>
      </div>
    </div>
  </div>
</div>
<!-- Modal pack-->
<div class="modal fade" id="staticPack" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="staticPackLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="staticPackLabel">Добавить материал</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form id="add_form_pack" method="post">
            {% csrf_token %}
            {{pack_form}}
        </form>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">Закрыть</button>
        <button form="add_form_pack" type="submit" class="btn btn-primary">Сохранить</button>
      </div>
    </div>
  </div>
</div>


    {% endblock %}

Ok, so the situation here is that you actually have two separate forms, one for each model.

What I have done in this type of situation is that I have different views, where each form submits to its own view. Those views are each based on CreateView, but for the different Models being submitted. I then have an action attribute in the form element that directs the submit button to the right view.

I don’t know if this is the “easiest” or “best” way to handle it, but it’s the method we use.

Ken

You can support two forms in one view by giving at least one a prefix, and then rendering both their fields inside one HTML <form>. Then they will render and read their data from a single POST request. Make sure you have transactions on for saving two sets of data.

@KenWhitesell

Im facing a similar problem. What do you mean with ‘submits to its own view’. As in an own URL?
Or do you mean, you create two functions in views.py?

This is what im trying to accomplish right now, but its not saving the attachment. It redirects me to ‘home’. I would like a html page rendering the possibility to create a new note and uploading an attachment.

p.s. formbijlage is the attachment, formnotities is the note.

def detailgoed(request, id_zaak, id_gegevensdrager):
   zaakref = Zaak.objects.all().filter(id=id_zaak)
   goeddetail = Gegevensdrager.objects.all().filter(id=id_gegevensdrager)
   notitie = Notitie.objects.all().filter(gegevensdrager_id = id_gegevensdrager)
   bijlage = Bijlage.objects.all().filter(gegevensdragerid = id_gegevensdrager)
   #goed = Gegevensdrager.objects.get(id=id_gegevensdrager)

   #Maak notitie hier toevoegen
   if request.method == 'POST':
      formnotitie = forms.MaakNotitie(request.POST)
      formbijlage = forms.VoegtoeBijlage(request.POST)

      if formnotitie.is_valid():
         tijdelijk = formnotitie.save(commit=False)
         tijdelijk.Gemaakt_door = request.user
         tijdelijk.gegevensdrager_id = goeddetail[0]
         tijdelijk.zaak_naam = zaakref[0]

         # tijdelijk.zaakid = zaakref
         # tijdelijk.gegevensdrager_soortsocode = soortengoed
         tijdelijk.save()

         # hier de juiste redirect nog toevoegen
         return redirect('home')
         # return redirect('detailgoed', id_gegevensdrager)

     elif formbijlage.is_valid():
         tijdelijk1 = formbijlage.save(commit=False)
         tijdelijk1.Toegevoegd_door = request.user
         tijdelijk1.gegevensdragerid = goeddetail[0]

         tijdelijk1.save()
         # hier de juiste redirect nog toevoegen
         return redirect('overzicht')


   else:
      formnotitie = forms.MaakNotitie()
      formbijlage = forms.VoegtoeBijlage()


   return render(request, 'gegevensdrager.html',
                 {'zaakref': zaakref ,
                  'goeddetail': goeddetail ,
                  'notities': notitie,
                  'bijlagen':bijlage,
                  'formnotitie': formnotitie,
                  'formbijlage':formbijlage})

To directly answer your first question, yes - I have separate views for each form, including separate URLs defined for each, all residing in the same views.py file. In that particular situation, someone can either fill out the form for “Mode 1” or “Mode 2”, but never both. So part of my template looks something close to this:

<div id="create_mode_1">
<form action="{% url 'submit_mode_1' parent_id %}">
...
<button type="button">Mode 1</button>
</form>
</div>

and this form contains all the fields for mode 1, which is followed by:

<div id="create_mode_2">
<form action="{% url 'submit_mode_2' parent_id %}">
...
<button type="button">Mode 2</button>
</form>
</div>

with all the fields for mode 2.

Then, there’s a little bit of JavaScript / jQuery that shows or hides the right form based upon a radio button above this section on the page, and other code that submits the form as an AJAX request to the proper URL.

For your second question regarding your example, I’d be guessing that formnotitie is valid when the form is submitted, because that’s where it gets redirected to “home”.

As a couple of general comments on your coding style that you haven’t asked for…

You don’t need to combine “filter” with “all”.

Gegevensdrager.objects.filter(id=id_gegevensdrager)

is perfectly valid.

But even better is:

Gegevensdrager.objects.get(id=id_gegevensdrager)

which returns one object and not a queryset.

Or, if you want some “guards” around the possibility that someone could issue a POST with an invalid value for either id_zaak or id_gegevensdrager:

get_object_or_404(Zaak, id=id_zaak)

Ken