开发者

How do I get Django forms to show the html required attribute?

I have this form field:

email = forms.EmailField(
  required=True,
  max_length=100,
)

It has the required attribute, but in the html it is not adding the html attribute required. In fact it's not even using email as the field type, it's using text... though it appears to get max_length just fine.

Actual:

&l开发者_如何学JAVAt;input id="id_email" type="text" name="email" maxlength="100">

Expected:

<input id="id_email" type="email" name="email" maxlength="100" required="true">

How can I get Django to use the correct attributes in html forms?


Django form elements are written against <input /> as it exists in HTML 4, where type="text" was the correct option for e-mail addresses. There was also no required="true".

If you want custom HTML attributes, you need the attrs keyword argument to the widget. It would look something like this:

email = forms.EmailField(
    max_length=100,
    required=True,
    widget=forms.TextInput(attrs={ 'required': 'true' }),
)

You can check out more documentation about widgets here. Discussion of attrs is near the bottom of that page.

Regarding type="email", you might be able to send that to your attrs dictionary and Django will intelligently override its default. If that isn't the result you get, then your route is to subclass forms.TextInput and then pass it to the widget keyword argument.


Combining Daniel and Daniel answers, I usually use this mixin for my forms:

from django.contrib.admin.widgets import AdminFileWidget
from django.forms.widgets import HiddenInput, FileInput


class HTML5RequiredMixin(object):

    def __init__(self, *args, **kwargs):
        super(HTML5RequiredMixin, self).__init__(*args, **kwargs)
        for field in self.fields:
            if (self.fields[field].required and
               type(self.fields[field].widget) not in
                    (AdminFileWidget, HiddenInput, FileInput) and 
               '__prefix__' not in self.fields[field].widget.attrs):

                    self.fields[field].widget.attrs['required'] = 'required'
                    if self.fields[field].label:
                        self.fields[field].label += ' *'

So when i have to create a new form or modelform i just use:

class NewForm(HTML5RequiredMixin, forms.Form):
    ...


Since Django 1.10, this is built-in.

From the release notes:

Required form fields now have the required HTML attribute. Set the new Form.use_required_attribute attribute to False to disable it.


There's also the template-only solution using a filter. I recommend django-widget-tweaks:

{% load widget_tweaks %}

{{ form.email|attr:'required:true' }}

That was easy.


Monkeypatching Widget is your best bet:

from django.forms.widgets import Widget
from django.contrib.admin.widgets import AdminFileWidget
from django.forms import HiddenInput, FileInput

old_build_attrs = Widget.build_attrs

def build_attrs(self, extra_attrs=None, **kwargs):
    attrs = old_build_attrs(self, extra_attrs, **kwargs)

    # if required, and it's not a file widget since those can have files
    # attached without seeming filled-in to the browser, and skip hidden "mock"
    # fileds created for StackedInline and TabbedInline admin stuff
    if (self.is_required
            and type(self) not in (AdminFileWidget, HiddenInput, FileInput)
            and "__prefix__" not in attrs.get("name", "")):
        attrs['required'] = 'required'

    return attrs

Widget.build_attrs = build_attrs


As you've realized, setting your Field required attribute to True is only for backend validation, as explained in the Django documentation.

What you really want is to add a required attribute to the Widget of the field:

email.widget.attrs["required"] = "required"

But if you really want to write elegant, DRY code, you should make a base form class that dynamically looks for all your required fields and modifies their widget required attribute for you (you can name it whatever you wish, but "BaseForm" seems apt):

from django.forms import ModelForm

class BaseForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(BaseForm, self).__init__(*args, **kwargs)
        for bound_field in self:
            if hasattr(bound_field, "field") and bound_field.field.required:
                bound_field.field.widget.attrs["required"] = "required"

And then have all your Form objects descend from it:

class UserForm(BaseForm):
    class Meta:
        model = User
        fields = []

    first_name = forms.CharField(required=True)
    last_name = forms.CharField(required=True)
    email = forms.EmailField(required=True, max_length=100)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜