# foreign key as hidden input in a formset

**URL:** <https://forum.djangoproject.com/t/foreign-key-as-hidden-input-in-a-formset/21751>\
**Category:** Forms & APIs\
**Created:** [June 21, 2023, 8:33am UTC](https://forum.djangoproject.com/t/foreign-key-as-hidden-input-in-a-formset/21751 "2023-06-21T08:33:50Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![RichardWoj](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/richardwoj/32/12824_2.png) [@RichardWoj](https://forum.djangoproject.com/u/RichardWoj)\
**Post date:** [June 21, 2023, 8:33am UTC](https://forum.djangoproject.com/t/foreign-key-as-hidden-input-in-a-formset/21751/1 "2023-06-21T08:33:50Z")

</div>

Hello,

I have a view which prepares different formsets (using a different relations between a “Business” model and a “Company” model, either “as\_customer” or “as\_beneficiary” to display businesses depending on the user choice. Using the same template, I get a a behaiviour I do not understand.

When displaying a queryset based on a “customer” relationship, the customer foreign key for the business is an input element with hidden type and the beneficiary element is a “regular” select field.  
When displayinf the queryset based on the “beneficiary” relationship, the customer foreign ley is a regular input elemet and the “beneficiary” field is hidden!

Any clue of what is going on?  
I would like to get both fields “customer” and “beneficiary” to be regular inputs in both cases (as\_customer and as\_beneficiary).

Here is an extract of the code:

Models involved are:

```auto
class Business(models.Model):
    name = models.CharField('Affaire',max_length=200, null = True, blank=True)
    customer = models.ForeignKey(Company,related_name = 'customer',on_delete=models.CASCADE,verbose_name='Client')
    beneficiary = models.ForeignKey(Company,related_name = 'beneficiary', on_delete=models.CASCADE,verbose_name='Bénéficiaire',
       null = True,blank = True)
    customer_ref = models.CharField('Référence client', max_length=250, 
        null=True, blank=True)
    framework_agreement = models.ForeignKey(Framework_agreement, on_delete=models.SET_NULL, verbose_name='Accord-cadre', null=True, blank=True)
    po_market = models.ForeignKey(PO_market, on_delete=models.SET_NULL, verbose_name='Marché à bons de commande', null=True, blank=True)
    default_contact = models.ForeignKey(Contact, related_name = 'set_business_contact', on_delete=models.SET_NULL, \
        null=True, blank=True, verbose_name='Contact client par défaut:')   
    default_addressee = models.ForeignKey(Contact, related_name="set_business_adressee" ,on_delete=models.SET_NULL, \
        null=True, blank=True, verbose_name='Destinataire par défaut:')
    default_address =models.ForeignKey(Address, on_delete=models.SET_NULL, \
        null=True, blank=True, verbose_name='Adresse par défaut:')
    account_exec = models.ForeignKey(Account_exec,on_delete=models.SET_NULL, null = True, blank = True, verbose_name='Chargé d\'affaires')
    amount = models.DecimalField('Montant',max_digits=9, decimal_places=2, null=True,blank=True)
    ponderation = models.ForeignKey(BusinessStatus, on_delete=models.RESTRICT, default=1)
    creation_date = models.DateField('Date de création', default=datetime.date.today)
    last_modification_date = models.DateTimeField('Date de dernière modification',
        auto_now= True,null = True, blank = True,)       

    def weighted_amount(self):
        status = BusinessStatus.objects.get(pk=self.ponderation_id)
        taux = status.weight
        if taux:
            amount = self.amount * taux / 100
        else:
            amount = 0
        return amount
    weighted_amount.short_description = "Montant pondéré"
    
    def amount_to_plan(self):
        invoices = self.invoice_set.all()
        a = self.amount
        for i in invoices:
            if i.type == 'FA' or i.type == 'PR': 
                a -= i.amount
            if i.type == 'AV':
                a += i.amount
        return a
    amount_to_plan.short_description = "Facturation à planifier"
    
    def billed_amount(self):
        return self.invoice_set.filter(is_issued=True).aggregate(total=Sum('amount'))['total'] or 0
    billed_amount.short_description = 'Total des montants facturés'
    
    def collected_amount(self):
        return self.invoice_set.filter(is_paid=True).aggregate(total=Sum('amount'))['total'] or 0
    collected_amount.short_description = "Total des montants encaissés"
     
    def amount_to_bill(self):
        status = BusinessStatus.objects.get(pk=self.ponderation_id)
        taux = status.weight
        invoices = self.invoice_set.all()
        amount_to_bill = self.amount
        if taux == 100:
            for i in invoices:
                if i.is_issued == True:
                    amount_to_bill -= i.amount
        return amount_to_bill
    amount_to_bill.short_description = "Montant à facturer"
    
    def status(self):
        return BusinessStatus.objects.get(pk=self.ponderation_id).text()
        
    def weight(self):
        return BusinessStatus.objects.get(pk=self.ponderation_id).weight        

    def __str__ (self):
        """String for representing the Model object (in Admin site etc.)"""
        return self.name
    
    def get_absolute_url(self):
        return reverse('customer_detail_form', kwarg={'pk':self.pk})
    
    class Meta:
        ordering = ['ponderation','amount']
        verbose_name = 'Affaire'
        verbose_name_plural = "Affaires"

```

class Company(models.Model):  
name = models.CharField(‘Nom’,max\_length=200,)  
SIRET = models.CharField(‘SIRET’,max\_length=16, null = True, blank = True)

```
creation_date = models.DateField('Date de création',default=datetime.date.today, 
    null = True,blank = True)
last_modification_date = models.DateTimeField('Date de dernière modification',
    auto_now= True,null = True, blank = True,)

class Meta:
    ordering = ['name']
    verbose_name = 'Client'
    
def total_business(self):
    # Renvoie le montant total de chiffre d'affaire signé avec ce client
    business=self.customer.all()
    total_business = 0
    for b in business:
        b_ponderation = b.ponderation
        if b_ponderation.weight == 100:
            total_business += b.amount
    return total_business

def total_to_bill(self):
    # Renvoie le montant total à facturer à ce client
    business=self.customer.all()
    total_to_bill = 0
    for b in business:
        b_ponderation = b.ponderation
        if b_ponderation.weight == 100:
            total_to_bill += b.amount_to_bill()
    return total_to_bill

def total_under_negotiation(self):
    # Renvoie le montant pondéré des affaires en cours de négociation
    business=self.customer.all()
    total_under_negotiation = 0
    for b in business:
        b_ponderation = b.ponderation
        if b_ponderation.weight != 100:
            total_under_negotiation += b.weighted_amount()
    return total_under_negotiation            
    
def __str__ (self):
    """Cette retourne une chaîne de caractère pour identifier l'instance de la classe d'objet."""
    return self.name

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

def get_contacts_absolute_url(self):
    return reverse('company_contacts_update', kwargs={'company_pk': self.pk})

def get_addresses_absolute_url(self):
    return reverse('company_addresses_update', kwargs={'company_pk': self.pk})

```

Extract of the view building the formset:

```auto
if request.POST.get('flexRadioCustomerBeneficiary') == 'as_customer':
            as_customer = True
            as_customer_checked = 'checked'
            as_beneficiary_checked = ''
            previous_view = 'as_customer'
            print('as_customer')
            business_queryset = Business.objects.filter(customer=company).order_by(
                Case(
                    When(ponderation__label="Terminée", then=Value(0)),
                    When(ponderation=0, then=Value(1)),
                    default='ponderation__weight',
                    output_field=IntegerField()
                ).desc(),
                '-ponderation__weight'
            )
            BusinessFormSet = inlineformset_factory(Company, Business, fk_name='customer',extra=0, can_delete=False, form = BusinessForm)
            business_formset = BusinessFormSet(instance=company, queryset=business_queryset, data=request.POST)
            print('business formset avec request.POST')
        else:
            as_beneficiary = True 
            as_customer_checked = ''
            as_beneficiary_checked = 'checked'
            previous_view = 'as_beneficiary'
            business_queryset = Business.objects.filter(beneficiary=company).order_by(
                Case(
                    When(ponderation__label="Terminée", then=Value(0)),
                    When(ponderation=0, then=Value(1)),
                    default='ponderation__weight',
                    output_field=IntegerField()
                ).desc(),
                '-ponderation__weight'
            )
            print('as_beneificiary')
            BusinessFormSet = inlineformset_factory(Company, Business, fk_name='beneficiary',extra=0, can_delete=False, form = BusinessForm)
            business_formset = BusinessFormSet(instance=company, queryset=business_queryset, data=request.POST)
          

```

And the code handling the Business form is:

```auto
class BusinessForm(ModelForm):
    def clean(self):
        print('clean business')
        cleaned_data = super().clean()
        name = cleaned_data.get('name')
        account_exec = cleaned_data.get('account_exec')
        amount = cleaned_data.get('amount')
        if not(name):
            msg = ('Veuillez compléter le nom de l\'affaire')
            self.add_error('name', msg)
        if not (account_exec):
            msg = ('Veuillez choisir le reponsable de l\'affaire')
            self.add_error('account_exec', msg)            
        if not (amount):
            msg = ('Veuillez définir le montant de l\'affaire')
            self.add_error('amount', msg)
            print('montant manquant')
        return cleaned_data        
    
    # fonctions d'initialisation des valeurs calculées        
    def init_calculated_fields(self):
        if self.instance.pk:
            self.fields['weighted_amount'].initial = self.instance.weighted_amount()
            self.fields['amount_to_plan'].initial = self.instance.amount_to_plan()
        else:
            self.fields['weighted_amount'].initial = 0
            self.fields['amount_to_plan'].initial = 0
    
    def __init__ (self, *args, **kwargs):
        super(). __init__ (*args, **kwargs)

        # On initialise les montants calculés
        self.init_calculated_fields()
        
        # Format d'affichage des champs
        for _, value in self.fields.items():
           value.widget.attrs['class'] = 'form-control text-primary' 
        field_attrs = ['name', 'framework_agreement', 'po_market', 'account_exec', 'ponderation', 'amount',
                       'weighted_amount', 'amount_to_plan']
        for field_attr in field_attrs:
            self.fields[field_attr].widget.attrs.update({'class': 'form-control text-primary w-50'})
        if self.instance.pk:
            if self.instance.amount_to_plan() > 0:
                self.fields['amount_to_plan'].widget.attrs.update({'class': 'form-control text-danger w-50'})
        self.fields['name'].widget.attrs.update({'class': 'form-control text-primary w-200'})
        self.fields['name'].widget.attrs.update({'placeholder': 'Saisir une nouvelle affaire'})
        
        self.fields['weighted_amount'].disabled = True
        self.fields['amount_to_plan'].disabled = True
       
        # Restreindre la liste de choix des po_market au client en cours
        if self.instance.pk:
            customer = self.instance.customer
            self.fields['framework_agreement'].queryset = Framework_agreement.objects.filter(company=customer)
            self.fields['po_market'].queryset = PO_market.objects.filter(company=customer)
        else:
            self.fields['framework_agreement'].queryset = Framework_agreement.objects.none()
            self.fields['po_market'].queryset = PO_market.objects.none()
        # On vérifie si l'affaire est déjà associée à un framework agreement
        if self.instance.framework_agreement:
            # Si oui, on réordonne les choix pour mettre le framework agreement associé en premier
            framework_agreement = self.instance.framework_agreement
            self.fields['framework_agreement'].initial = framework_agreement.id
        if self.instance.po_market:
            # Si oui, on réordonne les choix pour mettre le marché à BC associé en premier
            po_market = self.instance.po_market
            self.fields['po_market'].initial = po_market.id           

    weighted_amount = forms.DecimalField(disabled=True, label="Montant pondéré")
    amount_to_plan = forms.DecimalField(disabled = True, label="Montant à planifier")
    
    class Meta:
        model = Business
        fields = ['name', 'customer', 'beneficiary','framework_agreement','po_market','account_exec', 'amount','ponderation',]
        widgets = {
            'creation_date': DatePickerInput,
            'signature_date': DatePickerInput
        }

```

Here is the template block rendering the businesses:

```auto
<div class="row line-spacing mx-2">
    <div class="col-md-3">
      <div class="d-flex align-items-center">
        <i class="no-display">{{ business_form.id }}</i>
        {% if business_form.instance.id %}
          <a href="/salesforecast/businesses/{{business_form.instance.id}}" title="Gérer les échéances prévisionnelles de facturation">
            <i class="bx bxs-detail nav_icon"></i>
          </a>
        {% endif %}
        <b>{{ business_form.name }}</b>
      </div>
      <div class="fieldWrapper">{{ business_form.name.errors }}</div>
    </div>
    <div class="col-md-3">
      <div class="d-flex align-items-center">
        <label for="{{ business_form.customer.id_for_label }}">{{ business_form.customer.label }}</label>
        {{ business_form.customer }}
      </div>
      <div class="fieldWrapper">{{ business_form.customer.errors }}</div>
    </div>
    <div class="col-md-3">
      <div class="d-flex align-items-center">
        <label for="{{ business_form.beneficiary.id_for_label }}">{{ business_form.beneficiary.label }}</label>
        {{ business_form.beneficiary }}
      </div>
      <div class="fieldWrapper">{{ business_form.beneficiary.errors }}</div>
    </div>
    <div class="col-md-3">
      <div class="d-flex align-items-center">
        <label for="{{ business_form.account_exec.id_for_label }}">Responsable</label>
        {{ business_form.account_exec }}
      </div>
      <div class="fieldWrapper">{{ business_form.account_exec.errors }}</div>
    </div>
</div>
  <div class="row line-spacing mx-2"> 
    <div class="col-md-3 ">
      <label for="{{business_form.amount.id_for_label}}">{{business_form.amount.label}}</label>          
      {{ business_form.amount }}<div class="fieldWrapper">{{business_form.amount.errors}}</div>
    </div>
    <div class="col-md-3">
      <label for="{{business_form.ponderation.id_for_label}}">{{business_form.ponderation.label}}</label>          
      {{ business_form.ponderation }}<div class="fieldWrapper">{{business_form.ponderation.errors}}</div>
    </div>

    {%if business_form.weighted_amount.value != 0 %}
      <div class="col-md-3">
        <label for="{{business_form.weighted_amount.id_for_label}}">{{business_form.weighted_amount.label}}</label>          
        {{ business_form.weighted_amount }}
      </div>
    {%endif%}
    {%if business_form.amount_to_plan.value != 0 %}
      <div class="col-md-3">
        <label for="{{business_form.amount_to_plan.id_for_label}}">{{business_form.amount_to_plan.label}}</label>          
        {{ business_form.amount_to_plan }}
      </div> 
    {%endif%}
  </div>
  <div class="row line-spacing mx-2">
    <div class="col-md-1 d-flex align-items-center">
      <label for="{{business_form.framework_agreement.id_for_label}}">Accord-cadre</label>
    </div>
    <div class="col-md-5 d-flex align-items-center">
      {{business_form.framework_agreement}}<div class="fieldWrapper">{{business_form.framework_agreement.errors}}</div>
      {% if business_form.instance.id %}
        <!-- icones d'ajout et modification d'accord-cadre -->
      <a href="{% url 'cru_framework_agreement' business_pk=business_form.instance.id framework_agreement_pk=0%}?origin={{origin}}" 
            title="Ajouter un accord-cadre et y associer cette affaire" class="d-inline-block">
          <i class='bx bxs-plus-circle nav_icon'></i>
        </a>
        {% if business_form.framework_agreement.value %}
          <a href="{% url 'cru_framework_agreement' business_pk=business_form.instance.id framework_agreement_pk=business_form.framework_agreement.value %}?origin={{origin}}" 
              title="Modifier l'accord-cadre" class="d-inline-block">
            <i class='bx bxs-pencil nav_icon'></i>
          </a>
        {% endif %}                    
      {% endif %}              
    </div>
    <div class="col-md-2 d-flex align-items-center">
      <label for="{{business_form.po_market.id_for_label}}">Marché à bons de commande</label>
    </div>
    <div class="col-md-4 d-flex align-items-center">
      {{business_form.po_market}}<div class="fieldWrapper">{{business_form.po_market.errors}}</div>
      {% if business_form.instance.id %}
        <!-- icones d'ajout et modification de marché à bon de commande -->
        <a href="{% url 'cru_po_market' business_pk=business_form.instance.id po_market_pk=0%}?origin={{origin}}" 
            title="Ajouter un marché à bons de commande et y associer cette affaire" class="d-inline-block" >
          <i class='bx bxs-plus-circle nav_icon'></i>
        </a>
        {% if business_form.po_market.value %}
          <a href="{% url 'cru_po_market' business_pk=business_form.instance.id po_market_pk=business_form.po_market.value %}?origin={{origin}}"
              title="Modifier le marché à bons de commande" class="d-inline-block">
            <i class='bx bxs-pencil nav_icon'></i>
          </a>
        {% endif %} 
      {% endif %}   
    </div>
    <div class="line"></div>
  </div>

```

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [June 21, 2023, 11:49am UTC](https://forum.djangoproject.com/t/foreign-key-as-hidden-input-in-a-formset/21751/2 "2023-06-21T11:49:06Z")

</div>

Is that portion of the view you posted to handle the get of the initial display of the formsets or the post of the data from the submitted formsets?

Is this a case where “page 1” has posted some data, and you’re using _that_ posted data to prepare this “page 2” for display?

Note - since this is an inlineformset, the relationship from the “child object” back to the “parent object” is fixed. If you have a `Company`, then the set of `Business` related to that `Company` shown in an inlineformset should not be able to alter that relationship. If you want to be able to alter both FK fields in a set of `Business`, then you would want to create a regular modelformset for those `Business` objects.

---

<div class="post-metadata">

**Author:** ![RichardWoj](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/richardwoj/32/12824_2.png) [@RichardWoj](https://forum.djangoproject.com/u/RichardWoj)\
**Post date:** [June 21, 2023, 12:10pm UTC](https://forum.djangoproject.com/t/foreign-key-as-hidden-input-in-a-formset/21751/3 "2023-06-21T12:10:22Z")

</div>

Hello Ken,

Thanks for your answer. The piece of view code I provided handles the POST. The one handling the GET is the following:

```auto
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['origin'] = "customer_update" # permet de revenir à cette page pour les ajouts/modif d'accord cadre et marché à BC
        company = get_object_or_404(Company, pk=self.kwargs['company_pk']) 
        context['company_form'] = CompanyForm(instance = company)
        Framework_agreementFormset = inlineformset_factory(Company, Framework_agreement, form = Framework_agreementForm)
        fa_formset = Framework_agreementFormset(instance=company)
        context['fa_formset']= fa_formset
        PO_marketFormset = inlineformset_factory(Company,PO_market, form = PO_marketForm)
        po__formset = PO_marketFormset(instance=company)
        context['po_formset'] = po__formset
        context['previous_view'] = 'as_customer'
        context['as_customer_checked'] = 'checked'
        context['as_beneficiary_checked'] = ''
        
        BusinessFormSet = inlineformset_factory(Company,Business,fk_name='customer', extra=0, can_delete=False,form = BusinessForm)
        # Obtenir la queryset des Business triés par le poids (ponderation) de BusinessStatus en ordre décroissant
        # en plaçant les objets avec label="Terminée" à la fin
        business_queryset = Business.objects.filter(
            customer=company
        ).order_by(
            Case(
                When(ponderation__label="Terminée", then=Value(0)),
                When(ponderation=0, then=Value(1)),
                default='ponderation__weight',
                output_field=IntegerField()
            ).desc(),
            '-ponderation__weight'
        )
        business_formset = BusinessFormSet(instance=company, queryset=business_queryset)
        context['business_formset'] = business_formset
               
        return context

```

By default, I display a view “as\_customer” - and the customer field is hidden as well.

If I understant correctly your note ( Note - since this is an inlineformset, the relationship from the “child object” back to the “parent object” is fixed. If you have a `Company` , then the set of `Business` related to that `Company` shown in an inlineformset should not be able to alter that relationship. If you want to be able to alter both FK fields in a set of `Business` , then you would want to create a regular modelformset for those `Business` objects), that means that when using an inlineformset, you cannot alter the foreign key and a good way to prevent any alteration is to hide the field!!! That’s fine for me, as I have a path to display and update a single business. I just have to hide the label of the foreign key field in the template rendering the formset!

Do you confirm my understanding?

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [June 21, 2023, 2:10pm UTC](https://forum.djangoproject.com/t/foreign-key-as-hidden-input-in-a-formset/21751/4 "2023-06-21T14:10:24Z")

</div>

Confirmed, except:

> [@RichardWoj](#):
>
> a good way to prevent any alteration is to hide the field!!!

making it hidden doesn’t _prevent_ alteration. It’s still possible for someone to alter the value of that field. However, I believe Django handles that possibility in the formset, so that even if the user were to alter the form, it shouldn’t update the field. (conjecture, don’t know that for certain.)
