'Django. How to make ModelChoiceField work with SingleObjectMixin, FormView, and inlineformset_factory?

This shouldn't be hard

How to show in a Django Model Form just the related records from Model.B for the user to choose. Ginven that it is provided the Model.A and Model.C data user is required to choose one from possible Model.B choices ?

Vizualization of the problem - squematics

So, I have been trying to make it function as planned, but there might be a tweak away from my knowledge.

The objective is to show only related options in a form with a choice field.

Model CONTRATO register all contracts

Model FATURA register all invoices and it has a ForeignKey with CONTRATO in a relation one-to-many (so one contract has many invoices)

Model LANCAMENTO register records from contracts that will be later assigned to an invoice in FATURA. So LANCAMETNO has a ForeingKey with CONTRATO in a relation on-to-many (so one contract has many recors in LANCAMENTO). And LANCAMENTO also has a ForeignKey with FATURA, that is null by default, so later it will be assigned a FATURA to that record in LANCAEMTNO.

The goal is to have this logic in a form. So when user goes to LANCAMETNO to assign a FATURA, it can choose only FATURA with the same contract_id as LANCAMETNO.

I got here resarching a lot, but this is as far I can go. I'm stuck.

Here's the code, in case someone could point me in the right direction.

Here is my code for Models:

from django.db import models
from django.urls import reverse, reverse_lazy

# Create your models here.
class ContratoBlog(models.Model):
    nome_contrato = models.CharField(max_length=100)

    def __str__(self):
        return f"{self.nome_contrato}"
    
    def get_absolute_url(self):
        return reverse('blog:detalhe_contrato', kwargs={'pk' : self.pk})


class FaturaBlog(models.Model):
    datavencimento = models.DateField(null=True, blank=True)
    status = models.CharField(max_length=50, null=True, blank=True)
    id_contrato = models.ForeignKey(ContratoBlog, on_delete=models.CASCADE)

    def __str__(self):
        return f"F.{self.id}.V.{self.datavencimento}"


class LancamentoBlog(models.Model):
    id_contrato = models.ForeignKey(ContratoBlog, on_delete=models.CASCADE)
    datalancamento = models.DateField(null=True, blank=True)
    detalhe = models.CharField(max_length=100, null=True, blank=True)
    id_fatura = models.ForeignKey(FaturaBlog, on_delete=models.CASCADE, blank=True, null=True)
   
    def __str__(self):
        return f"L{self.id}.{self.datalancamento}"

Code for Views:

from django.forms.models import inlineformset_factory
from multiprocessing import context
from urllib import request
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse, reverse_lazy
from blog.forms import  ContratoBlogLancamentoBlogFormset, ContratoBlogFaturaBlogFormset
from blog.models import ContratoBlog, FaturaBlog, LancamentoBlog
from django.views.generic import TemplateView, FormView, CreateView, ListView, DetailView, UpdateView, DeleteView
from django.views.generic.detail import SingleObjectMixin

# Create your views here.
# Views Just Home.
class HomeView(TemplateView):
    template_name = 'blog/home.html'


# Views for Model Contrato.
class ContratoBlogDetailView(DetailView):
    model = ContratoBlog
    template_name = "blog/contratoblog_detail.html"
    def get_context_data(self, **kwargs):
        # Call the base implementation first to get a context
        context = super().get_context_data(**kwargs)
        # Add in a QuerySet Complementary Data
        context['contrato_dados'] = ContratoBlog.objects.get(id=self.object.pk)
        context['fatura_list'] = FaturaBlog.objects.filter(id_contrato=self.object.pk).all()
        context['lancamento_list'] = LancamentoBlog.objects.filter(id_contrato=self.object.pk).all()
        return context

class ContratoBlogFormView():
    pass

#View para Faturas do Contrato
class ContratoBlogFaturaBlogEditView(SingleObjectMixin, FormView):

    model = ContratoBlog
    template_name = 'blog/contrato_fatura_edit.html'
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['fatura_list'] = FaturaBlog.objects.filter(id_contrato=self.object.pk).all()
        return context

    def get(self, request, *args, **kwargs):
        self.object = self.get_object(queryset=ContratoBlog.objects.all())
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object(queryset=ContratoBlog.objects.all())
        return super().post(request, *args, **kwargs)

    def get_form(self, form_class=None):
        return ContratoBlogFaturaBlogFormset(**self.get_form_kwargs(), instance=self.object)

    def form_valid(self, form):
        form.save()
        return HttpResponseRedirect(self.get_success_url())

    def get_success_url(self):
        return reverse('blog:detalhe_contrato', kwargs={'pk': self.object.pk})
    



#---------------------------------
#View para Lancamentos do Contrato
class ContratoBlogLancamentoBlogEditView(SingleObjectMixin, FormView):

    model = ContratoBlog
    template_name = 'blog/contrato_lancamento_edit.html'

    def get(self, request, *args, **kwargs):
        self.object = self.get_object(queryset=ContratoBlog.objects.all())
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object(queryset=ContratoBlog.objects.all())
        return super().post(request, *args, **kwargs)

    def get_form(self, form_class=None):
        return ContratoBlogLancamentoBlogFormset(**self.get_form_kwargs(), instance=self.object)

    def form_valid(self, form):
        form.save()
        return HttpResponseRedirect(self.get_success_url())

    def get_success_url(self):
        return reverse('blog:detalhe_contrato', kwargs={'pk': self.object.pk})

Code for the Forms:

from importlib.metadata import MetadataPathFinder
from django.forms import BaseInlineFormSet
from django import forms
from django.forms.models import inlineformset_factory
from blog.models import ContratoBlog, FaturaBlog, LancamentoBlog



class FooModelChoiceField(forms.Form):
    foo_select = forms.ModelChoiceField(queryset=None)

    def __init__(self, *args, **kwargs):
        super(FooModelChoiceField, self).__init__(*args, **kwargs)
        qs = FaturaBlog.objects.filter(fatura__id_contrato=self.id_contrato)
        self.fields['foo_select'].queryset = qs

ContratoBlogFaturaBlogFormset = inlineformset_factory(
    ContratoBlog, FaturaBlog, 
    fields=('datavencimento','status', 'id_contrato')
    )

ContratoBlogLancamentoBlogFormset = inlineformset_factory(
    ContratoBlog, LancamentoBlog, 
    fields=('datalancamento', 'detalhe', 'id_fatura')
    )

** Code for Urls:**

from django.urls import path, re_path
from blog.views import HomeView, ContratoBlogDetailView, ContratoBlogFaturaBlogEditView, ContratoBlogLancamentoBlogEditView

app_name = 'blog'

urlpatterns = [
    path('', HomeView.as_view(), name='home'),
    #re_path(r'^contrato/(?P<pk>\d+)$', ContratoBlogDetailView.as_view(), name='detalhe_contrato'),
    path('contrato/<int:pk>', ContratoBlogDetailView.as_view(), name='detalhe_contrato'),
    path('contrato/<int:pk>/fatura/edit/', ContratoBlogFaturaBlogEditView.as_view(), name='contrato_fatura_edit'),
    path('contrato/<int:pk>/lancamento/edit/', ContratoBlogLancamentoBlogEditView.as_view(), name='contrato_lancamento_edit'),

Template 1 - Shows All FATURA and All LANCAMETNO related to CONTRATO:

{% extends 'base.html' %}

{% block content %}
{% include 'blog/contrato_nav.html' %}

<p>
    Contrato ({{contratoblog}}). {{contratoblog.nome_contrato}}.  <BR>
</p>

<p><hr>
    Faturas: <a href="{{ contratoblog.get_absolute_url }}/fatura/edit/">[Editar]</a> <BR>
    <table>
    {% for fatura in fatura_list %}
    <tr>
        <td>[{{ fatura.id }}]</td> <td>Vct. {{ fatura.datavencimento|date:"d M y" }} </td><td>{{ fatura }}</td>  
    </tr>
    {% endfor %}
    </table>
</p>
<p><hr>
    Lan&ccedil;amentos: <a href="{{ contratoblog.get_absolute_url }}/lancamento/edit/">[Editar]</a> <BR>
    <table>
    {% for lancamento in lancamento_list %}
    <tr>
        <td> {{ lancamento.datalancamento|date:"d-M-y" }} </td> <td>{{ lancamento.detalhe }}. </td> <td>{{ lancamento.id_fatura }}</td>
    </tr>
    {% endfor %}
    </table>
</p>
{% endblock %}

Template 2 - Shows All LANCAMETNO related to CONTRATO and but SHOULD show only FATURA related, insted of show all records in fatura.

{% extends 'base.html' %}

{% block content %}

<h2>Lan&ccedil;amentos:</h2>

<hr>
  <form action="" method="post" >
    {% csrf_token %}
    {{ form.non_form_errors }}

    {% for hidden_field in form.hidden_fields %}
      {{ hidden_field.errors }}
      {{ hidden_field }}
      {{ hidden_field.field }}
      {{ hidden_field.help_text }}
    {% endfor %}
     {{ form.management_form }}

    <table>
      <h3>
        {% for hidden_field in form.forms %}
          {{ hidden_field.errors }}
        {% endfor %}
      </h3>
      {% for lancamento_form in form.forms %}
      <h5>
        {% if lancamento_form.instance.id %}
        {% else %}
          {% if form.forms|length > 1 %}
            <!-- Adicionar outro Lan&ccedil;amento -->
          {% else %}
            <!-- Adicionar Lan&ccedil;amento-->
          {% endif %}
        {% endif %}
      </h5>
      <tr><th>Data Lancaamento</th><th>Contrato</th><th>Detalhe</th><th>Fatura</th><th>Deletar</th> </tr>    
        <tr> 
          <td>{{lancamento_form.id}}{{ lancamento_form.datalancamento }}</td> 
          <td>{{ lancamento_form.id_contrato }}</td> 
           <td>{{ lancamento_form.detalhe }}</td> 
           <td>{{ lancamento_form.id_fatura }}</td> 
           <td>{{lancamento_form.DELETE}} Deletar.</td>
        </tr>
      {% endfor %}

    </table>
    <hr>
    <p>
      <button type="submit" value="Update Fatura" >Atualizar Lan&ccedil;amento</button>
      <a href="{{ contrato.get_absolute_url  }}" role="button" >Cancelar</a>
    </p>
  </form>
{% endblock content %}

The goal is to show only related options in a form with a choice field.

I got here resarching a lot, but this is as far I can go. I'm stuck.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source