views:

425

answers:

4

I would like to create dynamic labels for a forms.ModelChoiceField and I'm wondering how to do that. I have the following form class:

class ProfileForm(forms.ModelForm):

    def __init__(self, data=None, ..., language_code='en', family_name_label='Family name', horoscope_label='Horoscope type', *args, **kwargs):
        super(ProfileForm, self).__init__(data, *args, **kwargs)

        self.fields['family_name'].label = family_name_label
        .
        .
        self.fields['horoscope'].label = horoscope_label
        self.fields['horoscope'].queryset = Horoscope.objects.all()

    class Meta:
        model = Profile

    family_name = forms.CharField(widget=forms.TextInput(attrs={'size':'80', 'class': 'contact_form'}))
    .
    .
    horoscope = forms.ModelChoiceField(queryset = Horoscope.objects.none(), widget=forms.RadioSelect(), empty_label=None)

The default labels are defined by the unicode function specified in the Profile definition. However the labels for the radio buttons created by the ModelChoiceField need to be created dynamically.

First I thought I could simply override ModelChoiceField as described in the Django documentation. But that creates static labels. It allows you to define any label but once the choice is made, that choice is fixed.

So I think I need to adapt add something to init like:

class ProfileForm(forms.ModelForm):

    def __init__(self, data=None, ..., language_code='en', family_name_label='Family name', horoscope_label='Horoscope type', *args, **kwargs):
        super(ProfileForm, self).__init__(data, *args, **kwargs)

        self.fields['family_name'].label = family_name_label
        .
        .
        self.fields['horoscope'].label = horoscope_label
        self.fields['horoscope'].queryset = Horoscope.objects.all()
        self.fields['horoscope'].<WHAT>??? = ???

Anyone having any idea how to handle this? Any help would be appreciated very much.

+1  A: 

You could use a ModelChoiceField and then change the choices in you ProfileForm.__init__ dynamically, eg (assuming that it is already a ModelChoiceField):

horoscopes = Horoscope.objects.all()
self.fields['horoscope'].choices = [(h.pk, h.name) for h in horoscopes]

h.name in this example will be used as the label of the choice!

lazerscience
When I try your solution I am getting error:"Select a valid choice. That choice is not one of the available choices."I'm afraid your solution doesn't work. Basically the model Profile has defined a foreign key horoscope and an Profile instance expects an instance of the model Horoscope, not just an integer h.pk.
Henri
Yes I meant "the default labels are defined in the unicode function specified in the Horoscope definition"
Henri
Sorry I mistook something here, it would probably work with a normal ChoiceField, but see my other solution with sub-classing the ModelChoiceField!
lazerscience
+2  A: 

You can make your own form field class and overwrite the method that generates the label:

class MyChoiceField(ModelChoiceField):
   def label_from_instance(self, obj):
        # return your own label here...
        return smart_unicode(obj)

use it in your form same as you did with the ModelChoiceField:

horoscope = MyChoiceField(queryset = .....)
lazerscience
Yes and that's also what I wrote in my original question. This creates alternative labels but STATIC labels. See my remark that reads "First I thought I could simply override ModelChoiceField as described in the Django documentation..."
Henri
A: 

I found something but I don't know if it's the best solution. I add something to the init part of class ProfileForm as follows:

class ProfileForm((forms.ModelForm):

    def __init__(self, data=None, ..., language_code='en', family_name_label='Family name', horoscope_label='Horoscope type', *args, **kwargs):
    super(ProfileForm, self).__init__(data, *args, **kwargs)

        # this function is added
        def get_label(self, language_code):
            """
            returns the label in the designated language, from a related object (table)
            """
            return HoroscopeLanguage.objects.get(horoscope=obj, language__language_code=language_code).horoscope_type_language

        self.fields['family_name'].label = family_name_label
        .
        .
        self.fields['horoscope'].queryset = Horoscope.objects.all()
        self.fields['horoscope'].label_from_instance = lambda obj: "%s: Euro %.2f" % (HoroscopeLanguage.objects.get(horoscope=obj, language__language_code=language_code).horoscope_type_language, obj.price)
        .
        .
        """
        The next code also works, the lambda function without the get_label function
        """
        self.fields['horoscope'].label_from_instance = lambda obj: "%s: Euro %.2f" % (obj.horoscope_type, obj.price)
        .
        .
        """
        But this code doesn't work. Anyone?
        """
        self.fields['horoscope'].label_from_instance = get_label(obj, language_code)
Henri
from django.utils.functional import curryself.fields['horoscope'].label_from_instance = curry(get_label, obj, language_code) or put the code in the form class' label_from_instance.
lazerscience
When using the function "get_label" directly or through "curry" I keep getting "global name 'obj' is not defined". When using "lambda obj: "%s: Euro..." I don't get that error.
Henri
A: 

Actually the last code example contains errors an should be:

# this function is added
def get_label(obj):
    return '%s: Euro %.2f' % (HoroscopeLanguage.objects.get(horoscope=obj, language__language_code=language_code).horoscope_type_language, obj.price)
.
.
.

self.fields['horoscope'].label_from_instance = get_labels

Then it works. There is no difference in using 'lambda obj:...' or 'def get_label(obj): ...'

Henri