Trying to get ModelChoiceField value directly

Using a ModelChoiceField with the to_field_name set.

condition = forms.ModelChoiceField(queryset=Condition.objects.all(), to_field_name='condition_id')

dropdown is populated with the value changed to the field instead of pk when source is viewed.

However the form returns an object for this field and I still have to use Condition.condition_id to get desired result.Obviously I can make it work with an extra couple of lines of code but there should be a more direct way. If not then why have to_field_name at all?

What is your ultimate desired result?

Perhaps you’re using the wrong type of field here?

The purpose of a ModelChoiceField is to aid with the selection of an object. It wouldn’t be doing what it’s supposed to do if it returned anything else.

Guess I’m just thinking about it more as a form helper to build a dropdown list populated by the queryset.

I have a table with records that just has a condition name and condition id. I was using a ModelChoiceField to populate a dropdown for a user to select the condition. I only need the condition id of the selection to pass on to the next step of business logic. I thought it would work like a normal dropdown once rendered and the value of the option would be assigned to that field name.

I guess it wasn’t designed for that. Still curious though why there would be an argument to change the values of the options(to_field_name) if you don’t have access to those values anyway. I suppose it could be useful if you needed to access it via Javascript. .

Once you have the object, you have access to all the fields of that object.

The to_field_name does change the value on which it will do the search for the matching object, but by definition, a ModelSelectField is going to yield an object.

I’m not understanding what you’re objecting to here.

Maybe if you post a small snippet of code showing what you’re trying to do in context, we might be able to clear this up.

I’m not trying to object to anything just trying to learn and comprehend. I accept your answer it doesn’t behave as a normal select. I was just asking in case I had missed something because it would save me some complexity in my code. Thanks for your time.

I think you have, which is why I’m asking for a snippet of code.

You made the comment earlier:

which, on the surface, in an inaccurate statement. It’s not “an extra couple lines of code” - it’s typically a few extra characters on one line of code.

This leads me to believe that you’re thinking it’s more work than what it generally is. That’s really what I’m trying to clear up here.

def brand_search(request):
if request.method == 'POST':
        form = BrandSearchForm(request.POST)
        if form.is_valid():
            cd = form.cleaned_data
            payload = {'category': cd['category'], 'brand': cd['brand']}
            url = "https://ogboomer.pythonanywhere.com/someproject/someapp/"
            for key in cd:
                if key == 'condition':
                    payload[key] = cd[key].condition_id
                else:
                    payload[key] = cd[key]
        response = requests.post(url, json=payload)
        data = response.json()
    return JsonResponse(response.json(), safe=False)

The form will end up having several ModalChoiceFields. The way I see it, I will have to cycle through each field and convert each one that is an object to the desired string value. Then it can be json encoded to send to the my API on another server. If it was just the fieldname value combination I could skip the iteration and just send So yeah it does take some extra lines of code to ‘make it work’

You’ve got at least two options here.

First, what I would seriously suggest is that you change this from a ModelChoiceField to a ChoiceField. You can still use the query to retrieve the choices, but it’s not going to convert the response to an object reference.

But, getting outside the “common case”, you have a couple ways of making it work as-is.

If this is something consistent across all such instances of the data being submitted, there’s nothing saying that you need to use the “cleaned_data” version. You could use the raw data from the data attribute of the form directly. Since you’re creating JSON from this form data, there’s not a lot of value in converting the submitted data to an internal format only to convert it back again.
In other words, instead of creating another dict “payload”, you could send form.data as the payload.

Or, you could check to see if the entity being assigned to payload is an object and retrieve the primary key from it:

payload = {
  key: value.pk if 'models' in str(type(value)) else value
  for key, value in cd.items()
}

Or, if you don’t like the “type” expression in that generator, you could create a list for that comparison.
For example, if you have a list of fields using the model select field named “select_list”:

payload = {
  key: value.pk if key in select_list else value
  for key, value in cd.items()
}

I originally was going with a ChoiceField but it actually doesn’t take a queryset as an argument you have to supply an iterable of 2-tuples or a callable that will give you such.

This is the slap in the face I was looking for. New code that works.

def brand_search(request):
    if request.method == 'POST':
        form = BrandSearchForm(request.POST)
        if form.is_valid():
            data = form.data
            # del data['csrfmiddlewaretoken']
            url = "https://ogboomer.pythonanywhere.com/someproject/someapp/"
            response = requests.post(url, json=data)
            do_save_search(json.loads(response.content.decode('utf-8')))
    return render(request, 'brandprofile/profile_detail.html', {'profile': data['profile_id'], 'form': form}) 

I had to comment out the del statement and deal with it on the API side as it was throwing an error that data was a query dictionary and unmutable.

I was thinking of cleaned_data in the terms of being safe data from things like code injection and what not.

Thanks again for your time.

A query, using the values_list function, returns a list of tuples.
e.g. User.objects.values_list('id', 'username')

First, the is_valid test checks for valid values. Even though you’re not going to use them, the clean process is going to ensure that the data provided does pass all the tests.

Beyond that, you aren’t doing anything with the data - you’re just passing it along. Now, it’s nice to ensure that the data is clean, but ultimately, that’s the responsibility of the service handling the request.

See QueryDict.copy
data = form.data.copy()

How would you write the actual code in practice:

condition = ChoiceField(?????)

I don’t know the model you’re trying to use here. But it’s basically going to be:
condition = ChoiceField(choices=MyModel.objects.values_list('id', 'some_other_field'))

This works when MyModel is expected to be constant throughout the life of the process. (It’s going to be evaluated at import time.)

If you need it to be more dynamic, then you can put that query in a function, and pass that function as the callable.

tester = forms.ChoiceField(choices=Condition.objects.values_list('condition_id', 'name'))

This works for me as the Condition table will not change. I think this would be the better route as it is less ‘overhead’ than the ModelChoiceField.

Thanks for explaining it, I’ve learned quite a bit from this conversation.

1 Like