'Django. How to show only related choices in a two table relationship?
Why does this work? This is hard coded with contrato_id = 1, but the goal dynamicaly present related choices, not the full table model.
class LancamentoFormset(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['id_fatura'].queryset=FaturaBlog.objects.filter(id_contrato=1).all()
But none of them works?
a)
class LancamentoFormset(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['id_fatura'].queryset = FaturaBlog.objects.filter(id_contrato=self.object.pk).all()
Exception Type: AttributeError Exception Value: 'LancamentoBlogForm' object has no attribute 'object'
b)
class LancamentoFormset(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.id_fatura_choices = [*forms.ModelChoiceField(FaturaBlog.objects.filter(id_contrato=self.object.pk).all().choices)]
Exception Type: AttributeError Exception Value: 'LancamentoBlogForm' object has no attribute 'object'
c)
class LancamentoFormset(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['id_fatura'] = FaturaBlog.objects.filter(id_contrato=contratoblog__id).all()
Exception Type: NameError Exception Value: name 'contratoblog__id' is not defined
d)
class LancamentoFormset(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['id_fatura'] = super(LancamentoFormset, self).get_context_data()
Exception Type: AttributeError Exception Value: 'super' object has no attribute 'get_context_data'
BUT I do have context, as return self.render(context) returns data for base.html
context [{'True': True, 'False': False, 'None': None}, {}, {}, {'object': <ContratoBlog: Terceiro Contrato Blog>, 'contratoblog': <ContratoBlog: Terceiro Contrato Blog>, 'form': <django.forms.formsets.LancamentoBlogFormFormSet object at 0x107cfb0d0>, 'view': <blog.views.ContratoLancamentoEdit object at 0x107cfb0a0>, 'contrato_dados': <ContratoBlog: Terceiro Contrato Blog>, 'fatura_list': <QuerySet [<FaturaBlog: c.3.vct.2022-07-15>, <FaturaBlog: c.3.vct.2022-08-15>, <FaturaBlog: c.3.vct.2022-09-15>]>, 'lancamento_list': <QuerySet [<LancamentoBlog: L7.2022-02-01>, <LancamentoBlog: L8.2022-04-01>, <LancamentoBlog: L9.2022-06-01>]>}]
e)
class LancamentoFormset(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['id_fatura'] = FaturaBlog.objects.filter(id_contrato = lancamentoblog__id_contrato).all()
Exception Type: NameError Exception Value: name 'lancamentoblog__id_contrato' is not defined
f)
class LancamentoFormset(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['id_fatura'] = FaturaBlog.objects.prefetch_related('id_contrato').all()
#neither select_related or prefetch_related works
#self.fields['id_fatura'] = FaturaBlog.objects.select_related('id_contrato').all()
NO ERROR. BUT queryset does not return any results, the field is empty in the form.
I've been searching and testing for days. Here is the case: the choice field is related to a model that has two forignkey and the desired output is to show only choices from the related contract, previously assingned. If no parameters are given, the choice field works fine, but is show all records insted of just de records related.
So, de goal is to present to the user just the records related to the same contract. This is the expected behavior
CONTRATO
ID [ 1,2,3,4,5,6,...]
FATURA
ID ID_CONTRATO
1 1
2 1
3 1
4 2
5 2
6 3
7 3
LANCAMENTO
ID ID_CONTRATO ID_FATURA
1 3 choices should be [6,7]
2 3 choices should be [6,7]
3 3 choices should be [6,7]
4 2 choices should be [4,5]
5 2 choices should be [4,5]
6 1 choices should be [1,2,3]
7 1 choices should be [1,2,3]
For the full picture, de code below is the full setup in Django:
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
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}"
Here is my 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
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})
Here is my code for Fomrs.py
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')
)
Here is my code for urls.py
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 %}
Well, that's all to it so far. But still couldn/t get it to work as desiered. Does any one has a direction for solution?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
