Django: How to Build a Custom Form Widget

How do I write my own Django form widget?

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.

Django Custom widget rendering

We can do like below.

Widget

from django import forms
from django.template import loader
from django.utils.safestring import mark_safe

class MyWidget(forms.Widget):
template_name = 'widget_template.html'

def get_context(self, name, value, attrs=None):
return {'widget': {
'name': name,
'value': value,
}}

def render(self, name, value, attrs=None):
context = self.get_context(name, value, attrs)
template = loader.get_template(self.template_name).render(context)
return mark_safe(template)

widget_template.html

<div class="form-group input-group">
<span class="input-group-addon">Name</span>
<input type="text" class="form-control" id="mywidget-{{ widget.name }}" name="{{ widget.name }}" />
</div>

Modify django model form field with widget

Method 1

The first method calls the constructor super().__init__(*args, **kwargs) prior to manipulating the field. This enables developers to build the class in a default state and then play around with the classes components (attributes, functions).

The first method is most commonly used if the developer cannot achieve the results they want within the second method. This is because you're moving away from configuration to more of a manipulation of the class.

Method 2

The second method allows developers to define the configuration of the class before it is instantiated. This generally provides better usability and readability to other developers.


EXAMPLE:

Say you want your field bio to be a required for all general users except for superusers.

class ProfileForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop(user, None)
super().__init__(*args, **kwargs)
if self.user.is_superuser():
self.fields['bio'].required = False

class Meta:
model = Profile
fields = ['bio']

Using this method you can allow your Profile model's bio field attributes to be transposed to a form field upon instantiating and then making a small tweak to determine whether it's required for that particular user. This can be done without redefining the whole field.

Note: The form call in the GET request would look like ProfileForm(user=request.user)



Related Topics



Leave a reply



Submit