# Forms fields based on database content

**URL:** <https://forum.djangoproject.com/t/forms-fields-based-on-database-content/8317>\
**Category:** Using Django\
**Created:** [June 11, 2021, 6:15pm UTC](https://forum.djangoproject.com/t/forms-fields-based-on-database-content/8317 "2021-06-11T18:15:25Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![logikonabstractions](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/logikonabstractions/32/4066_2.png) [@logikonabstractions](https://forum.djangoproject.com/u/logikonabstractions)\
**Post date:** [June 11, 2021, 6:15pm UTC](https://forum.djangoproject.com/t/forms-fields-based-on-database-content/8317/1 "2021-06-11T18:15:25Z")

</div>

Hey,

Basically, I need to create a form (an inline formset actually), that contains 1 input field (a %) with an associated label/description (to help user), as well as some foreign keys. How many times, and what the description should be for each input field depends on another table. Some models.py pseudo-code to give an idea:

```auto

class MetaGroup(Model)
	description = CharField(....)
	< other irrelevant fields >

class Discounts(Model)
	fk_metagroup = ForeignKey(MetaGroup, ...)
	fk_client = ForeignKey(Client, ....)
	percentage = Decimal(....)

```

The CONTENT of the `MetaGroup` table is what determines the things on which discounts could be applied. So basically, an discount entry is related to a specific client (`fk_client`) and to a specific MetaGroup (`fk_metagroup`). The description comes from `fk_metagroup.description`, and then the % determines what the discount is for that client with items from that MetaGroup. That all gets saved in the Discounts table, which is basically a list of all the discounts for on all the metagroups for all the clients. (I know this isn’t a very good structure to store that information, but that’s the way it’s been built and we’re not refactoring that now, so…)

For instance, the content of MetaGroup would look like:

![image](https://us1.discourse-cdn.com/flex026/uploads/djangoproject/original/2X/a/a4121cffee2441925a8575906ab8ba8bfa9ca30d.png)

In which case, my `DiscountsForm` would display 3 rows, with Plastic cable, Led Lights, Actuator in the 1st column, and the input fields would be 1 decimal fields for each (the applicable discount on each of those group). In a separate form, the user can create a new group (say, pk=4, Description=Halogene Lights). In that case, my Discounts forms would need to offer another row for the new MetaGroup “Halogene Lights”, etc. The Discount formset actually appears within the `ClientForm` as an `inline_formset` of the `ClientForm`, so setting the `fk_client` is straightforward. I will have to manually set the `fk_metagroup` manually when saving the form however.

I’m not sure what the best way to go about this is. Currently thinking something like:

```auto
class DiscountsForm(ModelForm):
    class Meta:
        model = Discount
        fields = ["percentage"]

DiscountFormset = inlineformset_factory(Client, Discount,
                                        form=DiscountForm,
                                        extra=1,
                                        can_delete=False)

```

However… a few issues:

- extra=… isn’t a set number. I guess I could set it to some callback function (e.g. `extra=how_many_discounts`, with `def how_many_discounts():` defined above in forms.py, checking with the MetaGroup how many forms needs to be in the formset.

- I will need to put the associated descriptions (and the associated `metagroup.pk` so I can set `fk_metagroup` when saving each `DiscountForm` instance) in the context for the template to build the table to display to the user somehow. I’m thinking an inclusion\_tag could do that. But then I’m somewhat spreading out the logic for how that form works. Can I add a method to my `DiscountForm` class and call that from the template to manage this?

Assume I get, say 4 discounts groups as per example above, along with 4 input fields from my `DiscountsForm`, then I would iterate on that in the template with a for loop, building my table. When the user saves the Client form, I will have to manage adding `fk_metagroup` to each new Discount instance (overriding `form_valid()` or `save()` or something like that the view… using a CreateView/Edit).

Any better ideas? Things I’m not seeing?

---

<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 13, 2021, 2:13am UTC](https://forum.djangoproject.com/t/forms-fields-based-on-database-content/8317/2 "2021-06-13T02:13:23Z")

</div>

I’m sorry, I’m having a really difficult time trying to figure out exactly what you’re trying to determine here.

The closest I’ve come so far is that what you’re really trying to do is create a formset, where one of the fields in each form is pre-assigned a value.

Am I anywhere close to being right or have I missed the boat completely?

---

<div class="post-metadata">

**Author:** ![logikonabstractions](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/logikonabstractions/32/4066_2.png) [@logikonabstractions](https://forum.djangoproject.com/u/logikonabstractions)\
**Post date:** [June 13, 2021, 3:42pm UTC](https://forum.djangoproject.com/t/forms-fields-based-on-database-content/8317/3 "2021-06-13T15:42:58Z")

</div>

Well, yes, there is that - e.g. the fk\_groupe. (And fk\_client I guess, but that’s dealt with when I save the form, formset.instance = self.object). But that’s not really the crux of the matter for me.

The crux of the matter is that each form instance in the formset corresponds to 1 entry in MetaGroup table (e.g. Plastic cable, LED lights, etc.). Thus that means that the fk\_groupe for each form in the formset must be set to the MetaGroup instance’s pk. So I guess that either in the template, or in an inclusion\_tag, or the form view, or perhaps class DiscountForm, I need to:

- Fetch the content of the MetaGroup table
- create/add a form to the formset corresponding each entry returned (LED Lights, Plastic cable, etc.)
- set the fk\_group for that form

But then, the inlineformset\_factory produces forms within a formset, but none of those forms will have any fk\_groupe assigned. So somehow I would have to assign them at some point, but I’m not sure what hook I have to actually do that. So my current approach would be to make that information (a list of fk\_groupe) available in the context to the template, and then assign it to each form instance as I iterate? (Or javascript would work & just .val() on each td).

I’m getting the pieces toghether to make that work. Thought it does seem complicated. I’m not really sure where I want to put the logic for that either.

---

<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 13, 2021, 5:44pm UTC](https://forum.djangoproject.com/t/forms-fields-based-on-database-content/8317/4 "2021-06-13T17:44:33Z")

</div>

What I _think_ you might be looking for is [Passing custom parameters to formset forms](https://docs.djangoproject.com/en/3.1/topics/forms/formsets/#passing-custom-parameters-to-formset-forms). In general, you’ll want to assign a different MetaGroup to each instance of the form, and mark that field as either read-only or disabled. (In this case, I would tend to suggest disabled.)

This topic was also discussed here: [Pass different parameters to each form in formset](https://forum.djangoproject.com/t/pass-different-parameters-to-each-form-in-formset/4040). (The author of the first comment also makes reference to: [https://datalowe.com/post/formsets-tutorial-1/.)](https://datalowe.com/post/formsets-tutorial-1/.))

---

<div class="post-metadata">

**Author:** ![logikonabstractions](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/logikonabstractions/32/4066_2.png) [@logikonabstractions](https://forum.djangoproject.com/u/logikonabstractions)\
**Post date:** [June 13, 2021, 6:23pm UTC](https://forum.djangoproject.com/t/forms-fields-based-on-database-content/8317/5 "2021-06-13T18:23:47Z")

</div>

Hey Ken

Amazing. Yes that would be one way to do it. I basically inherit from BaseFormset, modify the get\_kwargs() method.

Then each form instance in my formset would have it’s data provided this way, so I could just do {{form.groupe.description}} in the template & just have that display properly in my table.

That does seem like the better to do this. I didn’t think it was possible to pass variying different arguments to each form in a formset, but that does seem to provide a roadmap to do it.

---

<div class="post-metadata">

**Author:** ![logikonabstractions](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/logikonabstractions/32/4066_2.png) [@logikonabstractions](https://forum.djangoproject.com/u/logikonabstractions)\
**Post date:** [June 14, 2021, 4:32pm UTC](https://forum.djangoproject.com/t/forms-fields-based-on-database-content/8317/6 "2021-06-14T16:32:37Z")

</div>

Just thought I’d add a few code snippets, for others who may be looking for something similar. Does take a bit of experimenting & being pointed to the right sections in the docs (slightly more obscure).

So essentially, you first need to pass whatever data you need to your Formset constructor - so likely in the get\_context\_data() of your create view. For instance:

```auto
class CreateCustomerView(LoginRequiredMixin, CreateView):
    template_name = "admin_account/create_customer.html"
    model = Client
    form_class = ClientForm
    login_url = '/login'
    context_object_name = "client"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        if self.request.POST:
            context["form"] = ClientForm(self.request.POST)
            context["formset_groupesc"] = GroupeEscFormset(self.request.POST)
            context["formset_groupesc2"] = GroupeEsc2Formset(self.request.POST)
        else:
            group_details = list(Groupe.objects.order_by("desc"))
            group2_details = list(Groupe2.objects.order_by("desc"))
            # we need - fk_group, fk_group.description,
            details = [[g.id, g.desc] for g in group_details]
            details2 = [[g.id, g.desc] for g in group2_details]

            formset = GroupeEscFormset(form_kwargs={'group_details':details})
            formset2 = GroupeEsc2Formset(form_kwargs={'group_details':details2})
            context["form"] = ClientForm()
            context["formset_groupesc"] = formset
            context["formset_groupesc2"] = formset2

        return context

```

The key bit above is that when I instantiate my formset, I pass it as argument whatever data I need them to use - in my case the ID the each inline form will need for the foreign key, and the description I want to show the users. So basically a list of tuples here.

Then, you’ll need to extend BaseInlineFormset:

```auto

class BaseGroupEscInlineFormset(BaseInlineFormSet):
    def get_form_kwargs(self, index):
        """ this BaseInlineFormset method returns kwargs provided to the form.
            in this case the kwargs are provided to the GroupEscForm constructor
        """
        kwargs = super().get_form_kwargs(index)
        try:
            group_details = kwargs['group_details'][index]
        except KeyError as ex: # likely this is a POST, but the data is already in the form
            group_details = []
        return {'group_details':group_details}

GroupeEscFormset = inlineformset_factory(Client, Groupe_esc,
                                         form=GroupeEscForm,
                                         formset=BaseGroupEscInlineFormset,
                                         extra=len(Groupe.objects.all()),
                                         can_delete=False)

```

You can dig the documentation/django source code if you want. But what you really need to know is that when a form instance calls get\_form\_kwargs, by default it returns all the form kwargs. Which in my case would be all the tuples in my group\_details list. But when it calls, it also gives the index of the calling form instance (e.g. whatever # from 0 to TOTAL\_NUM\_FORMS). Since I arranged my data to have 1 tuple for each form in the formset, I can simply use the index passed to return only the relevent data.

Then you can use your data however you need in your template:

```auto
<table id="{{table_id}}" class="table table-bordered dbase-table" style="width:100%" cellspacing="0">
	<thead>
		<tr>
			<th>Description</th>
			<th>%</th>
			<th style="display:none;">Groupe</th>
			<th style="display:none;">Client</th>

		</tr>
	</thead>
	<tbody>
		{{ formset.management_form }}
		{{ formset.non_form_errors }}
		{% for form in formset %}
			<tr>
				<td>{{form.group_details.1}}</td>
				<td>{{form.pourcent}}</td>
				<td style="display:none;">
					<input id="{{form.prefix}}-fk_groupe" type="hidden" name="{{form.prefix}}-fk_groupe" value="{{form.group_details.0}}">
				</td>
				<td style="display:none;">
					<input id="{{form.prefix}}-fk_client" type="hidden" name="{{form.prefix}}-fk_client">
				</td>
			</tr>
		{% endfor %}
	</tbody>
</table>

```

---

<div class="post-metadata">

**Author:** ![logikonabstractions](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/logikonabstractions/32/4066_2.png) [@logikonabstractions](https://forum.djangoproject.com/u/logikonabstractions)\
**Post date:** [July 9, 2021, 3:25pm UTC](https://forum.djangoproject.com/t/forms-fields-based-on-database-content/8317/7 "2021-07-09T15:25:24Z")

</div>

Just an update - turns out that

```auto
GroupeEscFormset = inlineformset_factory(Client, Groupe_esc,
                                         form=GroupeEscForm,
                                         formset=BaseGroupEscInlineFormset,
                                         extra=len(Groupe.objects.all()),
                                         can_delete=False)

```

is not so gravy. More specifically, `extra=len(Groupe.objects.all())` causes an issue if you pull your code brand new and try to makemigrations on a brand new DB.

In that case, you will get a `"no such table: Groupe"` error for makemigration. The issue seems to be that, within its basic checks before actually doing anything, Django will somehow pickup on that line, and check that the query actually runs. However, if this is a brand new DB, there’s no Groupe table yet (we’re trying to create it!). The check fails, and thus makemigrations as well.

Unsure what to do instead - maybe by adding another level of indirection it could pass under the radar. Or obviously just commenting the line, creating the DB and then uncommenting, but that’s not exactly best practice.

---

<div class="post-metadata">

**Author:** ![CodenameTim](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/codenametim/32/31507_2.png) [@CodenameTim](https://forum.djangoproject.com/u/CodenameTim)\
**Post date:** [July 9, 2021, 3:46pm UTC](https://forum.djangoproject.com/t/forms-fields-based-on-database-content/8317/8 "2021-07-09T15:46:04Z")

</div>

Hi logikonabstractions,

Did you consider moving that call into the view(s)? You’re absolutely correct, defining that at the module level will cause problems. The other benefit of moving it into the view is that as new groups are added, the `extra` parameter will match the true count of `Groupe` in your database. If you continue to define it at the module level, it will always be the number of `Groupe` that existed when your server process started.

Let me know if you have any questions.

-Tim

---

<div class="post-metadata">

**Author:** ![logikonabstractions](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/logikonabstractions/32/4066_2.png) [@logikonabstractions](https://forum.djangoproject.com/u/logikonabstractions)\
**Post date:** [July 9, 2021, 5:24pm UTC](https://forum.djangoproject.com/t/forms-fields-based-on-database-content/8317/9 "2021-07-09T17:24:51Z")

</div>

Hey Tim,

Yeah that’s probably smarter. But I want to keep the formset definition in forms.py… so what, would you just add a method to the view that returns the # of object? Would that work? Or is there a way I can set the # of extra forms to put in the formset direclty in the view? I can’t think of it from the top of my mind, but surely that’s possible…

---

<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:** [July 9, 2021, 7:30pm UTC](https://forum.djangoproject.com/t/forms-fields-based-on-database-content/8317/10 "2021-07-09T19:30:52Z")

</div>

You’ve got a couple of options here:

- Create a “factory method” which calls the formset factory, returning the value.

```auto
def GroupeEscFormset():
    return inlineformset_factory(...)

```

> (Note, this creates the ugly looking line:  
> `my_formset = GroupeEscFormset()(parameters for the formset call)`  
> This can be avoided by accepting the parameters in the function definition and calling the formset creation in your factory function.)

In general, this should avoid that specific situation.

- When you create the instance of the formset class, the forms aren’t created at that time - you can set the instance variables before creating the forms.

For example, you have the following line in your view:  
`formset = GroupeEscFormset(form_kwargs={'group_details':details})`  
At the point that this statement executes, the forms have not yet been created! Django uses a “lazy construction” for the forms, allowing you to alter the attributes of the formset.  
If you add the following line _after_ that line above, it should alter the number of `extra` forms:  
`formset.extra = 15`  
You’ll get 15 extra forms when they’re finally created.  
(You do need to make sure you execute that line _before_ you make any access to any individual form. The first reference to a form in the formset causes them to be rendered.)

---

<div class="post-metadata">

**Author:** ![logikonabstractions](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/logikonabstractions/32/4066_2.png) [@logikonabstractions](https://forum.djangoproject.com/u/logikonabstractions)\
**Post date:** [July 12, 2021, 1:48pm UTC](https://forum.djangoproject.com/t/forms-fields-based-on-database-content/8317/11 "2021-07-12T13:48:59Z")

</div>

> For example, you have the following line in your view:  
> `formset = GroupeEscFormset(form_kwargs={'group_details':details})`  
> At the point that this statement executes, the forms have not yet been created! Django uses a “lazy construction” for the forms, allowing you to alter the attributes of the formset.

That bit is particularly helpful, not only for the case at hand but more generally wrt form instantiation. In fact Django in general - I guess I don’t really tend to keep in mind that lots of objects are lazy in django (like fields in forms for instance), so often the best answer to deeper question might just be to consider if the evaluation will be lazy and thus if I can alter the object along the way… Will keep that in mind, thanks!
