Can we find better way to define the label for ModelChoiceField?

If a person wants to customize the label for a ModelChoiceField they need to define a subclass to ModelChoiceField, then use that field in all the relevant forms. This feels like a lot of boilerplate code and something we could offer a bit more out of the box on.

One first step would be to allow ModelChoiceField to take an argument that defines label_from_instance.

So rather than:

from django.forms import ModelChoiceField

class MyModelChoiceField(ModelChoiceField):
    def label_from_instance(self, obj):
        return "My Object #%i" % obj.id

class MyForm(forms.Form):
    my_object = MyModelChoiceField(queryset=MyModel.obects.all())

We could theoretically do something like:

class MyForm(forms.Form):
    my_object = MyModelChoiceField(queryset=MyModel.obects.all(), label_from_instance="my_object_label")
    def my_object_label(self, obj):
        return "My Object #%i" % obj.id

Or define a function:

def my_model_label(obj):
    return "My Object #%i" % obj.id

class MyForm(forms.Form):
    my_object = MyModelChoiceField(queryset=MyModel.obects.all(), label_from_instance=my_model_label)

It’s not a massive difference so that works against it. But it does open the door to allowing this to be defined at the model level or on the relationship definitions.

Do others feel that this part of the API could be streamlined a bit?

One benefit (?) (characteristic?) of the existing method is that by calling str on the object, the default behavior is to provide the same representation of the object regardless of the form in which it is used.

My gut reaction then is that I would prefer a solution that still relies upon the model to produce that representation. It would seem ideal to me if there were a clean way to allow __str__ to be used as the default while allowing a different function on the model to be called for the purpose of this widget. (It does seem to me that __str__ is too broadly used.)

In broad, general terms (as poor as the analogy may be), I’d be thinking along the lines of what’s available in the ModelAdmin.list_display attribute as the objective.

I agree it should still default to using str(model_instance).

For further clarification, my argument is around customization. This issue crops up a lot when you have different types of users such as in a multi-tenant application where your site admins want different info than the admins of a tenant.

Hey @CodenameTim.

The examples you give here are only 4 lines, rather than 5… — so my initial thought is that saving one line isn’t worth the tradeoff in API surface area, or the problem of there then being more than one right way to do this. :thinking:

I wonder if using ModelChoiceField.iterator isn’t the way to go here?

That’s a fair counter point.

I think this gets more to the issue. I don’t think the current way of customizing the labels for a ModelChoiceField is the “right way” for Django. It feels like everything else can be customized by either changing the arguments when creating the field or by manipulating the Form.Meta class.

For example, you can change the label for the field via the argument label=, via Meta.labels, and then through the custom field classes. There are very clean ways to manage that (perhaps not the most fair comparison).

Keep in mind the option elements value="" attribute has a corresponding argument (to_field_name). I think it’s fair to say that a <option> element has two critical components, the value and the displayed text. We make it pretty easy to customize one similar to how everything else gets customized (such as label), but not the other. That discrepancy in Django’s API can make it difficult to reason about, especially for a beginner.

So yes, it’s about removing boilerplate, but it’s also matching Django’s typical API for customization.

Ah… but there we hit on it… :sweat_smile:

The canonical way is always to declare the field explicitly. The Meta options are a shortcut.

Opinion: If we get right down to it, less Meta options would probably leave people less confused. I’m sceptical (but only sceptical) about adding more (This goes for DRF serializers and Django Filter FilterSets as much as Forms)

I think the ModelChoiceIterator API was documented in order to make this kind of customisation easier. (I half recall some good examples on the Trac ticket:thinking: Update: it was the example added to the docs in the PR I had in mind.) The iterator provides exactly the pairs you’re talking about, so I’d be keener to explore exposing the iterator as an init kwarg (just checked, this already happened :woman_facepalming: so…) using the already available iterator kwarg (or something along those lines) than just the label hook it calls to.

But I didn’t spend time at depth on this yet, so these are just initial thoughts. (Customising the iterator class may not end up any less code than the current subclass the field approach :woman_shrugging:)

1 Like

I’ll think some more on it. I’ll have to look at iterator a bit closer. I guess I haven’t used it a ton. It’s still not as seamless as I think Django typically strives to be, but it’s also doing more than label_from_instance.

I agree. I prefer the field specific configuration for the most part.