Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

setting help_text for each choice in a RadioSelect

Tags:

python

django

I can set the help_text attribute on any form field, but is it possible to set help_text on the choices used for a RadioSelect()?

I'd looking for a clean way to show some help information under each radio button.

Below is the code for the model and the form, I can render the name attribute in a template with the label, input element and help text. I'd also like to be able to render membership_type attribute with a label ('Membership Type'), radio buttons ('open membership' and 'closed membership'), and help text associated to each radio element ('anyone can join this group' and 'only select members can join this group').

class Group(models.Model):
  MEMBERSHIP_CHOICES = (
    ('O', 'Open membership'),
    ('C', 'Closed membership'),
  )

  name = models.CharField(max_length=255)
  membership_type = models.CharField(max_length=1, choices=MEMBERSHIP_CHOICES, default="O")

class GroupForm(forms.ModelForm):
  name = forms.CharField(label="Group name", help_text="Enter a name for your new group")

  class Meta:
    model = Group
    widgets = { "membership_type": forms.RadioSelect }
like image 915
buckley Avatar asked Feb 05 '10 02:02

buckley


2 Answers

@Rishabh is correct but I'll elaborate further as, at first glance, it doesn't appear to be the solution, although it is; or, at least, it can be kludged to get a useful effect without having to dive too deep into django forms.

The second element of the tuple is presented inside the "label" tag - so any 'inline elements' are permissible; for example:

The desired result

Or something like it

<ul>
  <li><label for="id_ticket_0">
      <input type="radio" id="id_ticket_0" value="PARTTIME" name="ticket"> 
      <em>Part Time</em> Valid on Friday Night and Saturday Only
  </label></li>
  <li><label for="id_ticket_1">
      <input type="radio" id="id_ticket_1" value="DAYTIME" name="ticket"> 
      <em>Casual</em> Valid on Saturday Only
  </label></li>
  <li><label for="id_ticket_2">
       <input type="radio" id="id_ticket_2" value="EARLYBIRD" name="ticket"> 
       <em>Early Bird</em> Valid on Friday, Saturday, and Sunday. $15 discount for booking before 1am January 3rd, 2011
   </label></li>
</ul>

The simple example

The trick is to "mark_safe" the content of the description then stuff whatever you need into:

from django.utils.safestring import mark_safe
choices = (
  ('1', mark_safe(u'<em>One</em> | This is the first option. It is awesome')),
  ('2', mark_safe(u'<em>Two</em> | This is the second option. Good too.'))
)

The complex example

So in this example we:

  1. assemble the choices into a list (any iterable structure will do)
  2. pass the structure to the form's init to create our radio options on the fly
  3. use a comprehension list to create an extended description for each radio option

The data structure: Tickets are my own classes and they have attributes:

  • tickets.code - as in a ticket code
  • label - a pithy short description
  • help - a longer description

But more about that later. First lets create some instances:

from mymodule import ticket
# so lets create a few
fulltime = ticket('FULLTIME',160,'Full Time',
              "Valid Monday to Friday inclusive")
parttime = ticket('PARTTIME',110,'Full Time',
              "Valid outside of business hours only")
daytime  = ticket('DAYTIME',70,'Day Time',
              "Valid only on weekends and public holidays")

# and put them together in a list any way you like
available_tickets = [fulltime, parttime, daytime]

# now create the form
OrderForm(tickets=available_tickets)

That probably happened in your view code. Now to see what happens in the form

class OrderForm(ModelForm):

    def __init__(self, *args, **kwargs):
        self.tickets = kwargs.pop('tickets')
        super(OrderForm, self).__init__(*args, **kwargs)

        choices = [(t.code, mark_safe(u'<em>%s</em> %s' % (t.label, t.help)))
                for t in self.tickets]
        self.fields['ticket'] = forms.ChoiceField(
            choices = choices,
            widget  = forms.RadioSelect()
        )
like image 116
John Mee Avatar answered Oct 23 '22 19:10

John Mee


For others coming across this 10+ years later, I was able to do this by creating a custom widget that overrides the default template for radio buttons, and passing custom attributes to it.

# Custom widget    
class RadioSelectHelpTextWidget(forms.RadioSelect):
                option_template_name = 'custom_templates/forms/radio_option_help_text.html'
        
# Form class that calls widgets, passed custom attributes to widget
class TemplateCreateForm(ModelForm):
            created_by = forms.ModelChoiceField(required=False,queryset=User.objects.none())
            class Meta:
                model = Template
                fields = ['name', 'type', 'created_by']
                widgets = {
                    'name': forms.TextInput(attrs={'class': 'input'}),
                    'type': RadioSelectHelpTextWidget(
                        attrs={
                            'help_text': {
                                'custom': 'This is custom help text.',
                                'html': 'This is help text for html.'
                            }
                        }
                    )
                }

Template for custom radio buttons (custom_templates/forms/radio_option_help_text.html)

{% load custom_template_filters %}
{% if widget.wrap_label %}
    <label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>
{% endif %}
{% include "django/forms/widgets/input.html" %}
{% if widget.wrap_label %}
    {{ widget.label }}
    {% if widget.attrs.help_text %}
        {% if widget.value in widget.attrs.help_text %}
            <p class="is-size-7">
             {{ widget.attrs.help_text|dict_get:widget.value }}
            </p>
        {% endif %}
    {% endif %}
    </label>
{% endif %}

The result: enter image description here

like image 1
asebold Avatar answered Oct 23 '22 18:10

asebold