Queryset a foreign key field in inlineformset

Hello,
I created a inlineformset form with cbv. It works fine but I have a problem with a field which is a foreign key (link with an another model than the two models in the inlineformset).
I want to limit the choice of this field. I must be related to the id of the parent.
Example : I have a contract with supplier’s contacts. With the inlineformset, I can select the contacts and affect a role. How can I limit the dropdown with only the contacts of the supplier ?

It would be easiest to discuss something like this in the context of the actual models, forms, and view involved. If you post your code, you’re more likely to get a more useful response.

(When posting code here, enclose each file between lines of three backtick - ` characters. This means you’ll have a line of ```, then your code, then another line of ```. This forces the forum software to keep your code properly formatted, which is critical with Python.)

Thank you Ken. I really hope you can help me. :slight_smile: This is my code :

Models :

class Contrat(models.Model):
    code_frn = models.ForeignKey(Fournisseur, related_name='contrat_fournisseur', on_delete=models.CASCADE)
    annee_effective = models.IntegerField(blank=False, default='2022')
    statut_choice = (
        ('Négociation', 'Négociation'),
        ('Finalisé', 'Finalisé'),
        ('Envoyé', 'Envoyé'),
        ('Signé', 'Signé'),
        ('Annulé', 'Annulé'),
    )
    statut_contrat = models.CharField(max_length=30, default='11', blank=False, null=False, choices=statut_choice)
    code_perimetre = models.ForeignKey(Perimetre, related_name='contrat_perimetre', on_delete=models.CASCADE)
    date_envoi = models.DateField(blank=True, null=True)
    date_signature = models.DateField(blank=True, null=True)

    def get_absolute_url(self):
        return reverse('contrat_detail', kwargs={'pk': self.pk})

    def __str__(self):
        return str(self.code_frn)

class Fournisseur(models.Model):
    code_frn = models.CharField(max_length=5, null=False, unique=True)
    libellé_frn = models.CharField(max_length=150)
    forme_juridique = models.CharField(max_length=50, blank=True)
    capital = models.CharField(max_length=20, blank=True)
    rcs = models.CharField(max_length=150, blank=True)
    adresse1 = models.CharField(max_length=100)
    adresse2 = models.CharField(max_length=100, blank=True)
    cp = models.CharField(max_length=10, blank=True)
    ville = models.CharField(max_length=100)
    pays = models.CharField(max_length=50)
    num_tva = models.CharField(max_length=50, blank=True)
    statut_choice = (
        ('Actif', 'Actif'),
        ('Inactif', 'Inactif')
    )
    statut = models.CharField(max_length=30, blank=True, null=True, choices=statut_choice)
    categorie = models.ForeignKey(Categorie, related_name='categorie_fournisseur', on_delete=models.CASCADE)

    def __str__(self):
        return self.libellé_frn

class Contact(models.Model):
    code_frn = models.ForeignKey(Fournisseur, related_name='contact_fournisseur', on_delete=models.CASCADE)
    civilite_choice = (('M.', 'M.'), ('Mme', 'Mme'))
    civilite = models.CharField(max_length=3, blank=False, null=False, choices=civilite_choice)
    nom = models.CharField(max_length=30, blank=False, null=False)
    prenom = models.CharField(max_length=30, blank=False, null=False)
    qualite = models.CharField(max_length=70, blank=False, null=False)
    telephone = models.CharField(max_length=30, blank=True, null=True)
    email = models.CharField(max_length=150, blank=False, null=False)
    statut_choice = (
        ('Actif', 'Actif'),
        ('Inactif', 'Inactif')
    )
    statut = models.CharField(max_length=30, default='Actif', blank=False, null=False, choices=statut_choice)

    def __str__(self):
        return str(self.nom)

class Contact_role_contrat(models.Model):
    code_contrat = models.ForeignKey(Contrat, related_name='code_contrat_contact', on_delete=models.CASCADE)
    code_contact = models.ForeignKey(Contact, related_name='code_fournisseur_contact', on_delete=models.CASCADE)
    code_role_choice = (('11', 'Signataire'), ('12', 'Commercial'), ('13', 'Dirigeant'), ('14', 'Comptable'))
    code_role = models.CharField(max_length=30, default='11', blank=True, null=True, choices=code_role_choice)

    def __str__(self):
        return str(self.code_contact)

Views :

class ContratsContactRoleEditView(SingleObjectMixin, FormView):
        model = Contrat
        template_name = 'contrat/contrat_contact_edit.html'

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

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

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

        def form_valid(self, form):
            form.save()
            messages.add_message(
                self.request,
                messages.SUCCESS,
                'Modifications enregistrées.'
            )
            return HttpResponseRedirect(self.get_success_url())

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

Forms :

class ContratContactForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(ContratContactForm, self).__init__(*args, **kwargs)
        #There is the problem : I don't how to find the code_frn value ? How can I have the code of the supplier to filter the field code_contact ?
        code_frn = Fournisseur.objects.get(id=1)
        self.fields['code_contact'].queryset = \
             Contact.objects.filter(code_frn_id=code_frn)

ContratContactFormset = inlineformset_factory(Contrat, Contact_role_contrat, fields=('code_contrat', 'code_contact', 'code_role',)
                                              , extra=1, form=ContratContactForm)

Thank you for posting this.

To make sure I’m clear -

  • Your inline formset is a set of ContratContactForm instances
  • ContratContactForm is using all fields in Contact_role_contrat in the form.
  • For each form, you want to limit the drop-down widget of the code_contact field.

Questions:
Is this all going to be the same set in the drop-downs for code_contact? Or are you looking to get a different set for each instance of the form?

What is the specific condition or restriction to apply to the drop-down?

How does this relate to the extra form? Should it be constrained?

(The solution is going to be slightly different depending on your answers.)

I have a contract (contrat) with a supplier (fournisseur) and I have the supplier’s contact (contact). I want to select the good contact (the contact belongs to the supplier and I affect a role on this contract, like signatory for example)

With the drop down, I want to constrain the user to be able to select only the contacts of the supplier consulted.
The drop-down for code_contact must contain all the contacts for the code_frn (this instance).

Unfortunately, I’m still not sure I’m following you here.

  • The Contrat being used has a foreign key to Fournisseur.
  • The Contact_role_contrat has foreign keys to both Contrat (which is given because this Contract_role_contrat is being rendered because it’s related to some identified Contrat) and to Contact (field name: code_contact).
  • Contact also has a foreign key to Fournisseur.

So, if I’m correct, you want the code_contact field to list all Contact relating to the same Fournisseur as the Contrat referenced by the Contact_role_contrat being edited.

Assuming this_contact_role_contrat is the name of the “current” Contact_role_contrat for which the form is being generated, then the set of Contact desired would be described as:
Contact.objects.filter(fournisseur__contrat__code_contrat_contact=this_contact_role_contrat)
(Alternately: Contact.objects.filter(fournisseur__contrat=this_contact_role_contrat.code_contrat))
(You can test these in the shell to verify you get the proper results.)

Have I finally got it?

Yes, you understand what I want.
I try your 2 solutions but I have an error : name ‘this_contact_role_contrat’ is not defined.
Did I miss something ?

class ContratContactForm(forms.ModelForm):

def __init__(self, *args, **kwargs):
    super(ContratContactForm, self).__init__(*args, **kwargs)
    self.fields['code_contact'].queryset = \
        Contact.objects.filter(fournisseur__contrat__code_contrat_contact=this_contact_role_contrat)

You need to select a Contact_role_contrat object, and assign it to this_contact_role_contrat.

Thank you, It works when I have already a contact selected in my contract. But when I have none contact entered in the InlineFormSet, I can’t retrieve the id of the contract because the instance is none.
How can I retrieve the id of the contract ? The id is in the current url…
For example, I write manually 29 for the pk.

‘’’
class ContratContactForm(forms.ModelForm):

def __init__(self, *args, **kwargs):
    super(ContratContactForm, self).__init__(*args, **kwargs)
    if self.instance.code_contrat_id:
        code_frn = Contrat.objects.get(id=self.instance.code_contrat_id)
        self.fields['code_contact'].queryset = \
              Contact.objects.filter(code_frn__libellé_frn=code_frn)
    else:
        code_frn = get_object_or_404(Contrat, pk=29)
        self.fields['code_contact'].queryset = \
            Contact.objects.filter(code_frn__libellé_frn=code_frn)

‘’’

Your CBV stores the url args in self.kwargs You can then pass that value in to the initializer for your form when you create the instance of your formset.

See the text and examples at Built-in class-based generic views | Django documentation | Django

Thank you for the link. I’m trying to understand how it works but I still have a bug.

In my CBV, I add this :

        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
            code_frn = Contrat.objects.get(id=self.kwargs['pk'])
            context['code_contact'] = Contact.objects.filter(code_frn__libellé_frn=code_frn)

            return context

When I use print(context), I can see the contact of my supplier (fournisseur) even if I have none contact. → Good news.
But now, how I can use this for filtering my dropdown code_contact ?
I tried def QuerySet(self)… but it doesn’t work. I just want to filter my foreign key with the context…
Please help me…
Thank you.

See Passing custom parameters to formset forms. Your view will need to pass that id when you create the forms using ContratContactFormset. Your __init__ method of the form will then have that parameter passed through to it.