Show fields of related models in the django template

1

I have these two models, Invoice representing an invoice e Item that represents the items on the invoice.

from django.db import models

# Create your models here.
class Invoice(models.Model):
    vendor = models.CharField(max_length=200)
    client = models.CharField(max_length=200)
    number = models.CharField(max_length=200)
    date = models.DateTimeField(max_length=200)
    due_date = models.DateTimeField()

    def __str__(self):
        return "Invoice number: {}".format(self.number)

class Item(models.Model):
    invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE)
    description = models.TextField()
    quantity = models.DecimalField(max_digits=19, decimal_places=2)
    rate = models.DecimalField(max_digits=19, decimal_places=2)
    amount = models.DecimalField(max_digits=19, decimal_places=2)
    subtotal = models.DecimalField(max_digits=19, decimal_places=2)
    tax = models.DecimalField(max_digits=19, decimal_places=2)
    notes = models.TextField()
    terms = models.TextField()

    def __str__(self):
        return "{}".format(self.description)

And these are the forms where the information will be collected:

from django import forms

from .models import Invoice, Item

class InvoiceForm(forms.ModelForm):

    class Meta:
        model = Invoice
        fields = ('vendor','client', 'number', 'date', 'due_date')
        widgets = {
            'date': forms.TextInput(attrs={'class':'datepicker'}),
            'due_date': forms.TextInput(attrs={'class':'datepicker'}),
        }


class ItemForm(forms.ModelForm):

    class Meta:
        model = Item
        fields = ('description', 'quantity', 'rate', 'amount',
            'subtotal', 'tax', 'notes', 'terms')

My question is how do I refer to any of the ItemForm fields in the template to capture the data?

Because at least with InvoiceForm I have no problems, he shows them to me. For example, if I do this, I do not have any inconveniences:

<!-- Client -->
<div class="row">
    <div class="input-field col s4">
    <label for="{{ form.client.id_for_label }}">Client</label>
    {{ form.client }}
    </div>
    <div class="input-field col s4 offset-s4">
    <label for="{{ form.due_date.id_for_label }}">Due Date</label>
    {{ form.due_date }}
    </div>
</div>

But when trying to make reference to some field of ItemForm I do not know how it should be done. For example if I want in the template to show the field description of ItemForm as I should do? Because if I try something like that, it does not show me anything:

<div class="input-field col s5">
{{ form.description }}
</div>  

This is my view, although I do not know how to connect both forms, for now I'm only interested in them unless they are displayed correctly in the template:

def invoice_generator(request):
    form = InvoiceForm
    return render(request, 'invoiceapp/invoice_generator.html', {'form': form})

Maybe I'm badly focused or I'm getting complicated, but I really do not know how to proceed.

I appreciate your help.

    
asked by Javier Cárdenas 24.12.2015 в 21:17
source

2 answers

3

In general, when you work with that type of relationship ( Autor/Libro , Factura/Item , etc.) what you should use are Formsets that help you work with multiple forms in the same view.

In this case you can use a InlineFormset :

from django.core.urlresolvers import reverse
from django.forms import inlineformset_factory
from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import InvoiceForm, ItemForm


def invoice_generator(request):
    ItemFormSet = inlineformset_factory(
        Invoice,
        Item,
        form=ItemForm,
        fields=('description', 'quantity', 'rate', 'amount', 'subtotal', 'tax'),
        extra=4
    )
    form = InvoiceForm()
    formset = ItemFormSet()
    if request.method == 'POST':
        form = InvoiceForm(request.POST)
        formset = ItemFormSet(request.POST)
        if form.is_valid() and formset.is_valid():
            form.save()
            formset.save()
            url = reverse('alguna_url')
            return HttpResponseRedirect(url)
    return render(request, 'invoiceapp/invoice_generator.html', {
        'form': form,
        'formset': formset
    })

And in your template invoice_generator.html :

...
<form method="POST">
    {% csrf_token %}
    {{ form.non_field_errors }}
    <!-- La factura -->
    {{ form.vendor.label_tag }}
    {{ form.vendor }}
    ...

    <!-- Los items -->
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form.non_field_errors }}
        {{ form.id }}
        {{ form.description }}
        {{ form.quantity }}
        ...
    {% endfor %}

    <button type="submit">Guardar</button>
</form>
...

Some considerations:

  • The parameter form of inlineformset_factory is optional, I am using it because you have already defined your form.
  • The parameter extra of inlineformset_factory is used to indicate the amount of items that you will have initially, that is, your invoice will appear with 4 forms for Item .
  • When you use FormSets and manually render the forms you have to define the management_form ( {{ formset.management_form }} )
  • When you use FormSets and manually render the forms you have to indicate the id to work correctly ( {{ form.id }} )
answered by 25.12.2015 / 02:43
source
1

I did it using a class-based view:

Createview 

And different forms for each model, and overwriting the method get_context_data of the class:

class EmpleadosCreate(CreateView):
    model = Empleado
    form_class = EmpleadoForm
    second_form_class = DireccionForm
    success_url = reverse_lazy('empleados:empleados_list')

    def get_context_data(self, **kwargs):
        context = super(EmpleadosCreate, self).get_context_data(**kwargs)
        context['active_client'] = True
        if 'form' not in context:
            context['form'] = self.form_class(self.request.GET)
        if 'form2' not in context:
            context['form2'] = self.second_form_class(self.request.GET)
        context['active_client'] = True
        return context

    def post(self, request, *args, **kwargs):
        self.object = self.get_object
        form = self.form_class(request.POST)
        form2 = self.second_form_class(request.POST)
        if form.is_valid() and form2.is_valid():
            usuario = form.save(commit=False)
            usuario.direccion = form2.save()
            usuario.save()
            return HttpResponseRedirect(self.get_success_url())
        else:
            return self.render_to_response(
              self.get_context_data(form=form, form2=form2))
    
answered by 17.01.2016 в 06:51