'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ç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ç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çamento -->
{% else %}
<!-- Adicionar Lanç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ç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 |
---|