views:

33

answers:

2

I have a model

class MyModel(models.Model):
    name = models.CharField(max_length=80, unique=True)
    parent = models.ForeignKey('self', null=True, blank=True)

I want to render a ModelChoiceField for that model that looks like:

<select name="mymodel" id="id_mymodel">
    <option value="1" title="Value 1" class="">Value 1</option>
    <option value="2" title="Value 2" class="Value 1">Value 2</option>
</select>

The differences between this output and the default output for a ModelChoiceField are the title and class elements in the OPTION tag. They don't exist in ModelChoiceField's default output.

For my purposes:

  • The title element is supposed to be the Option Name.
  • The class element is supposed to be self.parent.name. (this is my problem)

So, in the HTML snippet above, Value 1 has no parent and Value 2 has a parent of Value 1.

What is the best mechanism to change ModelChoiceField's default HTML output?


EDIT: I understand how to create a new Widget for rendering HTML. The problem is how to render a value from the underlying model in each of the options.

+1  A: 

Look at my example about how to create custom fields in this post

Saff
See the edit to my question. Thanks, but the problem is not just rendering some markup, but rendering data from the underlying object in the markup.
celopes
+2  A: 

You could make your own widget:

from django.forms.widgets import Select 

class MySelect(Select):

    def __init__(self, attrs=None, choices=(), model):
       self.model = model
       super(Select, self).__init__(attrs)

    def render_options(self, choices, selected_choices):
        def render_option(option_value, option_label):
            option_value = force_unicode(option_value)
            option = self.model.objects.get(pk=option_value)
            selected_html = (option_value in selected_choices) and u' selected="selected"' or ''
            return u'<option value="%s"%s class="%s">%s</option>' % (
                escape(option_value), selected_html,
                str(obj.parent.name),
                conditional_escape(force_unicode(option_label)))
        # Normalize to strings.
        selected_choices = set([force_unicode(v) for v in selected_choices])
        output = []
        for option_value, option_label in chain(self.choices, choices):
            if isinstance(option_label, (list, tuple)):
                output.append(u'<optgroup label="%s">' % escape(force_unicode(option_value)))
                for option in option_label:
                    output.append(render_option(*option))
                output.append(u'</optgroup>')
            else:
                output.append(render_option(option_value, option_label))
        return u'\n'.join(output)

If you also want to chance the field's label: the field class has a method label_from_instance.

lazerscience
I don't think you saw the edit to my question (posted minutes before you added your answer). How do you propose I access the underlying model to obtain the parent information?
celopes
If you look at my code: You pass the underlying model to the widget when you initialize it and get the parent (and other information) via option = self.model.objects.get(pk=option_value), then you can render any of the information in this option object; isn't that what you wanted? and yes i read your edit and understood it like that...
lazerscience
Sorry, I should have read your code way closer than I did. I upvoted you. I would rather have a solution that would not hit the db once for every option though... I think I'm going to adapt your idea and pass a dict with the values I need into the widget.
celopes
well yes i made it just quick and dirty. but get all options in a dict and get the right one for every iteration...
lazerscience