Search code examples
pythondjangodjango-formsdjango-widget

How do I write my own Django form widget?


Could anyone provide a simple example, so that I understand the basic idioms of how this is done? I can't find any helpful documentation that I can understand on the topic.

To provide a little more context, I'd like to create my own radio buttons, that are rendered in a particular way.

I'm just looking for a very simple example to help me understand this concept.


Solution

  • It's hard to tell what you mean by "a very simple example". Here is a very simple example:

    from django.forms.widgets import Input
    
    class TelInput(Input):
        input_type = 'tel'
    

    But I don't think this will help you much.

    If you are looking for examples, best is still to check out django source code.

    I think this should be sufficient to understand how it works:

    from django.utils.encoding import force_text
    from django.utils.html import format_html
    from django.forms.utils import flatatt
    
    class Widget(...):
    
        def __init__(self, attrs=None):
            if attrs is not None:
                self.attrs = attrs.copy()
            else:
                self.attrs = {}
    
        def subwidgets(self, name, value, attrs=None, choices=()):
            """
            Yields all "subwidgets" of this widget. Used only by RadioSelect to
            allow template access to individual <input type="radio"> buttons.
            Arguments are the same as for render().
            """
            yield SubWidget(self, name, value, attrs, choices)
    
        def render(self, name, value, attrs=None):
            """
            Returns this Widget rendered as HTML, as a Unicode string.
            The 'value' given is not guaranteed to be valid input, so subclass
            implementations should program defensively.
            """
            raise NotImplementedError('subclasses of Widget must provide a render() method')
    
        def build_attrs(self, extra_attrs=None, **kwargs):
            "Helper function for building an attribute dictionary."
            attrs = dict(self.attrs, **kwargs)
            if extra_attrs:
                attrs.update(extra_attrs)
            return attrs
    
        def value_from_datadict(self, data, files, name):
            """
            Given a dictionary of data and this widget's name, returns the value
            of this widget. Returns None if it's not provided.
            """
            return data.get(name)
    
    
    class Input(Widget):
        """
        Base class for all <input> widgets (except type='checkbox' and
        type='radio', which are special).
        """
        input_type = None  # Subclasses must define this.
    
        def format_value(self, value):
            if self.is_localized:
                return formats.localize_input(value)
            return value
    
        def render(self, name, value, attrs=None):
            if value is None:
                value = ''
            final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
            if value != '':
                # Only add the 'value' attribute if a value is non-empty.
                final_attrs['value'] = force_text(self.format_value(value))
            return format_html('<input{} />', flatatt(final_attrs))
    

    Most relevant methods are render() that renders the widget as HTML code, and value_from_datadict() that extracts the value of the widget from the POST data dictionary.