Add an argument to ModelChoiceField for callable function to set label for instance.

274 views
Skip to first unread message

Lawrence Vanderpool

unread,
Sep 20, 2016, 9:52:55 PM9/20/16
to Django developers (Contributions to Django itself)
Older related ticket: https://code.djangoproject.com/ticket/4620


The problem of setting the label for ModelChoiceField is one that comes up on IRC every once in a while. It's a common enough problem that I think the documentation-recommended solution of subclassing ModelChoiceField and setting label_from_instance ( https://docs.djangoproject.com/en/1.10/ref/forms/fields/#modelchoicefield ) is overkill, and probably a maintenance problem.

ModelChoiceField already implements an optional callable in the `get_limit_choices_to` function. This change would implement a similar API to allow a callable to be passed in to the constructor rather than having to do the subclassing dance.

It'd still be backwards compatible, but the documentation should be changed to reflect the "easier" solution of passing in the callable.

The old ticket was labelled wontfix, but I think we should take a look again. I'd love for this to be my first contribution to Django! Thanks for any feedback. And if I forgot something or broke a rule for the ML, I apologize in advance @.@

Tim Graham

unread,
Sep 20, 2016, 10:09:23 PM9/20/16
to Django developers (Contributions to Django itself)
The approach you suggested was suggested in the thread of ticket you mentioned: https://groups.google.com/d/topic/django-developers/7DDEX73zVrI/discussion

Brian Rosner: "I am a +1 on a configuration parameter since the alternative by subclassing is a bit too involved for something fairly trivial."

Why was it rejected? What new arguments are you bringing to the table that weren't considered before?

You say that subclassing is a burden, but I'd argue that even redefining the field on the model form isn't DRY, at which point what about making the customization in the form's __init__()?

self.fields['field_name'].label_from_instance = lambda obj: 'new label'

I think the barrier to violate the Zen of Python ("There should be one-- and preferably only one --obvious way to do it.") is a bit high here, so perhaps you could elaborate on how this is causing maintenance problems?

Alexander Hill

unread,
Sep 20, 2016, 11:40:50 PM9/20/16
to Django developers (Contributions to Django itself)
I like this!

Having read through the existing ticket and discussion, really the only reason given is a cultural one: that subclassing is the way this kind of behaviour "should" be achieved. I disagree – IME, APIs that encourage parametrising small chunks of behaviour are succinct and flexible, and are often much more pleasant to deal with than subclassing.

For me there are two issues here. Firstly, the creeping cognitive burden – another symbol in your IDE, another class to look up, more coupling, more boilerplate, more tiny choices to make about what to call your class and where to put it, etc.

The second issue is of readability: if I'm reading a form definition which includes somebody's BookModelChoiceField, I don't know what that does. Maybe all it does is override label_from_instance, but I don't know that until I go look it up. However, as a Django user I do know what ModelChoiceField does, I know the behaviour of the label_func parameter, so I can take that all in at a glance and move on.

This all lies on a spectrum in that the more places you use your subclass, the more justification it has for existing. But when subclassing is the only solution, you end up defining classes that make only tiny changes to behaviour and that you instantiate in only one place, and to me that's an opportunity for improvement in an API.

Alex

P.S.

Another way I have dealt with this issue generically is by overriding ModelChoiceField._get_choices() to return an overridden ModelChoiceIterator which yields for each choice not a tuple, but a ModelChoice – a tuple subclass with an "instance" attribute. It's a little convoluted, but it means that in your template (and wherever else), you have access to the actual model instance and can do whatever you like there. If you're not doing custom things in your template it's not that useful, though.




--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/f79d0b45-5073-4332-80df-0ca21cba1d98%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Lawrence Vanderpool

unread,
Sep 21, 2016, 12:43:57 PM9/21/16
to Django developers (Contributions to Django itself)
My reasoning is basically Alex Hill's. Subclassing for one bit of functionality and using a callable for another is an inconsistent API, and of the two, the callable is preferrable from a maintainability and readability standpoint (for all the reasons he's already stated so well!)

Erick Peirson

unread,
Nov 10, 2016, 1:44:08 PM11/10/16
to Django developers (Contributions to Django itself)
+1 to everything that Alex Hill said. It seems super inconsistent to me that ``to_field_name`` is available as an argument to the constructor, but we have to subclass to override ``label_from_instance``. 

It would be one thing if there were some complex logic going on there, but the vanilla ``label_from_instance`` method is simply calling ``django.utils.encoding.force_text``....

The benefits far exceed the costs, IMHO.

E
Reply all
Reply to author
Forward
0 new messages