Mostrar campos de modelos relacionados en el template de django

publicado por: Anonymous

Tengo estos dos modelos, Invoice que representa una factura e Item que representa los articulos en la factura.

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)

Y estos son los formularios de donde se va a recoger la información:

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

Mi duda es como hago referencia en el template a alguno de los campos de ItemForm para que capture los datos?

Porque al menos con InvoiceForm no tengo problemas, me los muestra. Por ejemplo si hago esto no tengo incovenientes:

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

Pero al intentar hacer referencia a algún campo de ItemForm no se como deberia hacerse. Por ejemplo si quiero en el template mostrar el campo description de ItemForm como deberia hacer? Porque si intento algo asi, no me muestra nada:

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

Esta es mi vista, aunque tampoco sé como conectar ambos formularios, por ahora solo me interesa al menos que se muestren correctamente en el template:

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

Quizás estoy mal enfocado o me estoy complicando, pero en realidad no se como proceder.

Agradezco su ayuda.

solución

En general, cuando trabajas con ese tipo de relaciones (Autor/Libro, Factura/Item, etc.) lo que deberías usar son Formsets que te ayudan a trabajar con múltiples formularios en la misma vista.

En este caso puedes usar un 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
    })

Y en tu 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>
...

Algunas consideraciones:

  • El parámetro form del inlineformset_factory es opcional, yo lo estoy usando debido a que ya habías definido tu formulario.
  • El parámetro extra del inlineformset_factory sirve para indicar la cantidad de items que tendrás inicialmente, es decir, tu factura aparecerá con 4 formularios para Item.
  • Cuando usas FormSets y renderizas manualmente los formularios tienes que definir el management_form ({{ formset.management_form }})
  • Cuando usas FormSets y renderizas manualmente los formularios tienes que indicar el id para que funcione correctamente ({{ form.id }})
Respondido por: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *