Dynamic choices, or maybe a better way to do my task?

What I’m currently trying to accomplish is having dynamic choices in a form, kind of like this:

class DynamicChoiceTestForm(forms.Form):
    item = forms.ChoiceField(required=False)

class InitialForm(forms.Form):
    comma_separated_input = forms.CharField(required=False)

def dynamic_choice_test(request):
    if request.method == "GET":
        initial_form = InitialForm()
        dynamic_form = DynamicChoiceTestForm()

    else:
        initial_form = InitialForm(request.POST) # User submitted comma separated values

        if initial_form.is_valid():
            list_of_choices = initial_form.cleaned_data['comma_separated_input'].split(',') # Prepare list of choices for dynamic form
            initial_form = InitialForm() # Display blank initial form for next iteration

        dynamic_form = DynamicChoiceTestForm(request.POST)

        if dynamic_form.is_valid(): 
            selection_from_multiple_choice = dynamic_form.cleaned_data['item'] # Get choice selection from last iteration
            print(selection_from_multiple_choice) # Do something with selected choice, but stay in view

            dynamic_form = DynamicChoiceTestForm() # Set choices from initial_form for next iteration based on list_of_choices
            

    return render(request, 'dynamic_choice_test.html', context={'initial_form': initial_form, 'dynamic_form': dynamic_form})

I’m confused by all the answers related to dynamic choices I’ve seen, and was hoping for guidance, maybe specific to my usecase. (Edit: Specifically, I think they say the choices need to be the same when displaying the form and when retrieving it from POST. Setting the same choices when retrieving sounds tough.)

The above is a toy example, but my view is meant to allow users to search for terms. There will be an initial form and three formsets. They enter a list of strings into the first form textarea, then the view searches all those terms, and then redisplays them in the appropriate formset based on how many results there were for each term:

  1. no_matches_formset shows items with no matches, to allow users to enter a new term
  2. multiple_matches_formset shows items with multiple matches, to allow users to select which match they want (the purpose of the dynamic choices)
  3. one_match_formset shows items with one match. No more actions are necessary for these, they just hold the terms until all terms have one match, after which all terms are submitted to the database to act as foreign key values for another model.

The view keeps iterating and redisplaying the same template after each POST, until all terms have one match.

The code I have is getting pretty ugly already, and I haven’t even included the one_match_formset yet, so I’m wondering if there’s a cleaner way to do this in general:

def add_genes(request):
    context = {}

    if request.method == 'GET':
        search_initial_form = SearchInitialForm()
        search_no_matches_formset = SearchNoMatchesFormSet(prefix="no_matches")
        search_multiple_matches_formset = SearchMultipleMatchesFormSet(prefix="multiple_matches")
    else:
        search_initial_form = SearchInitialForm(request.POST)
        search_no_matches_formset = SearchNoMatchesFormSet(request.POST, prefix="no_matches")
        search_multiple_matches_formset = SearchMultipleMatchesFormSet(request.POST, prefix="multiple_matches")

        if search_initial_form.is_valid() and search_no_matches_formset.is_valid() and search_multiple_matches_formset.is_valid():

            # Get search terms from initial textarea and no_matches formset
            search_terms = search_initial_form.cleaned_data['search_terms']
            
            for form in search_no_matches_formset:
                if 'search_term' in form.cleaned_data:
                    search_term = form.cleaned_data['search_term']
                    if search_term != '':
                        search_terms.add(search_term)

            search_results = HgncGene.objects.find_matching_items(search_terms)



            # Generate new forms
            search_initial_form = SearchInitialForm()

            no_matches_formset_initial_data = []
            for search_term_with_no_matches in search_results['no_matches']:
                no_matches_formset_initial_data.append({"search_term": search_term_with_no_matches})
            search_no_matches_formset = SearchNoMatchesFormSet(initial=no_matches_formset_initial_data, prefix="no_matches")

            multiple_matches_formset_initial_data = []
            for search_term_with_multiple_matches in search_results['multiple_matches']:
                # Need to set choices in form below to items from search_term_with_multiple_matches['items'].
                multiple_matches_formset_initial_data.append({"search_term": search_term_with_multiple_matches['search_string']})
            search_multiple_matches_formset = SearchMultipleMatchesFormSet(initial=multiple_matches_formset_initial_data, prefix="multiple_matches")
    
    context['search_initial_form'] = search_initial_form
    context['search_no_matches_formset'] = search_no_matches_formset
    context['search_multiple_matches_formset'] = search_multiple_matches_formset

    return render(request, 'sickgenes/molecule_match.html', context)

Edit: And here are the forms:

class SearchInitialForm(forms.Form):
    search_terms = forms.CharField(
        widget=forms.Textarea(),
        required=False,
    )

    def clean_search_terms(self):
        search_terms_string = self.cleaned_data['search_terms']

        search_terms = set()
        for search_string in search_terms_string.splitlines():
            search_string = search_string.strip()
            if search_string:
                search_terms.add(search_string)

        return search_terms
    
class SearchNoMatchesForm(forms.Form):
    search_term = forms.CharField(max_length=300, required=False)
    delete = forms.BooleanField(required=False)

SearchNoMatchesFormSet = formset_factory(SearchNoMatchesForm, extra=0)

class SearchMultipleMatchesForm(forms.Form):
    search_term = forms.CharField(max_length=300, required=False)
    item_id = forms.ChoiceField(required=False)

SearchMultipleMatchesFormSet = formset_factory(SearchMultipleMatchesForm, extra=0)

Edit: The idea I’m leaning towards is just getting the selected choice from the uncleaned data, and initializing the form choices with just that choice so that it validates. Or maybe there’s a way to just disable validation for this field?

Ok, I think this is working: In the view, I’m just setting form.fields['item_id'].choices = <choices from the search results>. And then when receiving POSTed data, I get the search term from the search_term field in the form, then rerun the search to again set the choices from the search results before validating.