Andy Stagg
Andy Stagg

Reputation: 413

Django 2.2 set ModelChoiceField initial value without id/pk

I have a GET based search, passing a few search terms and paging info through the query string. I can grab the items from the query string without any issues, but passing them back to the template via the SearchForm to preserve the search is proving to be difficult.

items/?search=foo&category=bar&page=1&pageSize=20

forms.py

class SearchForm(forms.Form):
    search = forms.CharField(*)
    category = forms.ModelChoiceField(queryset=Items.objects.all(), *)

*simplified for brevity

In the view, I can retrieve all the values or their defaults from the query string, and I can even set the value for the search field, but the ModelChoiceField is the one I am struggling with. I can set an initial value, but not based on the select text...

views.py

class ItemList(View):
    template_name = 'items.html'

    def get(self, request):
        items = Item.objects.all()
        form = SearchForm()

        search = request.GET.get('search')
        category = request.GET.get('category')
        page = int(request.GET.get('page', 1))
        pageSize = int(request.GET.get('pageSize', 20))

        if category != None:
            #non working:
            #form.fields['category'].initial = category

            #working by setting a value 
            form.fields['category'].initial = 1

            items.filter(category__name=category)

        if search != None:
            form.initial['search'] = search
            items = items.filter(field__icontains=search)

        context = {
            'form': form,
            'items': items
        }
        return render(request, self.template_name, context)

I tried various methods of trying to retrieve the value/id from the form.category field unsuccessfully. I would hate to have to make a second call to the database to get the categories, and then pull the id from that query, but that seems to be the only thing?

I don't know if I should maybe add the page and pageSize fields to SearchForm and then suppress their display somehow, but my attempts at that haven't been successful, ie:

class SearchForm(forms.Form):
    search = forms.CharField()
    category = forms.ModelChoiceField(queryset=Items.objects.all())
    page = forms.IntegerField()
    pageSize = forms.IntegerField()

    class Meta:
        fields = ['search', 'category']
    form = SearchForm(request.GET)

    if form.is_valid():
        #never executes

page and pageSize are still are displayed as fields to the user in this example

I have tried various solutions using the __init__ constructor to set the initial values without success as well.

Would appreciate a nudge in the right direction, especially without leveraging the ListView CBV.

Upvotes: 0

Views: 584

Answers (1)

Andy Stagg
Andy Stagg

Reputation: 413

I figured out a way to not make a trip back to the database using the to_field_name attribute. Which contradictory to its name, sets the value attribute for the <option> tag.

category = forms.ModelChoiceField(queryset=Category.objects.all(), *)

results in the html:

<select name="category" class="form-control w100 " id="id_category">
  <option value="" selected>Category</option>
  <option value="1">Category 1</option>
  <option value="2">Category 2</option>
  <option value="3">Category 3</option>
</select>

so when trying to assign the initial value in the views method via:

form.fields['category'].initial = category #where category='Category 1'

it's trying to compare the value=1 to category and coming up blank.

solution: Adding to_field_name= attribute to the SearchForm field declaration:

category = forms.ModelChoiceField(queryset=Category.objects.all(), to_field_name='name', *)

results in the html:

<select name="category" class="form-control w100 " id="id_category">
  <option value="" selected>Category</option>
  <option value="Category 1">Category 1</option>
  <option value="Category 2">Category 2</option>
  <option value="Category 3">Category 3</option>
</select>

Making the form.fields['category'].initial = category compare the value="Category 1" to category

Upvotes: 2

Related Questions