I’m having trouble implementing a datalist using forms in django. It’s very confusing and I can’t seem to figure out how to do it. Seems like it should be the type of thing that takes 5 minutes. Any idea why I can’t seem to use this basic HTML5 element?
It looks like you could use this by creating a custom widget that would use that html element when rendering the field. This will likely involve creating a custom template to use when rendering that widget.
I don’t understand how they built this…There’s a list of built in widgets and datalist isn’t one of them. Is there no way to create a custom widget and tell it what HTML5 element I want, and have it try to render that?
Yes, that’s exactly what you want to do - create a custom widget with a template that uses datalist.
First, read about specifying widgets. Notice how the widget is a parameter in the form field definition, and refers to a class. Since it’s just a class, you could write your own class and use it, instead.
Now, if you read about Widget, you’ll see how classes inheriting from the base Widget class have a method named get_context. The get_context dictionary identifies that there’s a key in that dictionary named template_name, which identifies the name of the template to be used to render that widget.
So from reading this, it appears to me like you have two similar options for replacing the template name on a widget by creating a subclass of the Select widget. You could either just create your subclass and define a new value for the self.template_name attribute in your init method, or you could create a get_context method in your subclass that replaces the value of template_name within the context. (IMO, the first is easier, the second is more flexible - really depends upon your needs for further customization.) Both allow you to specific your own template to use to render your field. I would then look at the standard template to get some ideas as to how to code your template - perhaps even just copying it to replace the parts you need replaced.
Just a couple notes to keep in mind:
Django is just python. All the facilities that exist within python are available to you. So it’s all just code that can be modified / replaced as needed.
Django is designed to be extended / enhanced / modified. In most cases, anytime you want to change how something behaves, you can pretty much expect there to be a ‘hook’ or connection point for you to make your modifications.
Ok, you’re off to a good start. But yes, there are a couple things to improve.
Your mySubclass should be a subclass of a widget, not a form.
Did you look at the TextInput class I referenced in a previous reply? Reading and understanding what it is doing, should address the specific error you’ve listed.
How would I subclass a widget? I can’t see how to import Widget or Input or any of those classes from Django.
And yes I read the TextInput link you sent. It seems to be a class that sets two variables, “input_type” to ‘text’ and “template_name” to a django template.
I’m still not seeing the connection on how this creates a datalist in the end.
Importing a widget - no different than importing any other class. You use the import statement with the module structure being the path to the appropriate file:
from django.forms.widgets import Input
The template_name is the name of the template to render for your widget. You’ll need to create your own template. You can start by looking at the Django-provided select widget which contains an html select element - which is what I believe you want to replace with the datalist element .
Yah that’s the part trying to use it, but it doesn’t even get that far. It gives me an error on an exact copy of their subclass.
class mySubclass(Input):
input_type = 'text'
template_name ="django/forms/widgets/text.html"
class CommentForm(forms.Form):
name = forms.CharField()
url = forms.URLField()
comment = forms.CharField(widget=mySubclass)
error:
venv\lib\site-packages\django\forms\widgets.py", line 205, in __deepcopy__
obj.attrs = self.attrs.copy()
AttributeError: 'mySubclass' object has no attribute 'attrs'
OK sweet, got that part working without the need for the “attrs” attribute. Seems I just needed to manually stop and restart the server. I feel like I’m getting close now. Thanks for all your help on this by the way! 4 hours so far! WOW! amazing!
So now I’ve copied the code from the select widget as you suggested, and it looks like this after modification:
<datalist name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% for group_name, group_choices, group_index in widget.optgroups %}{% if group_name %}
<optgroup label="{{ group_name }}">{% endif %}{% for option in group_choices %}
{% include option.template_name with widget=option %}{% endfor %}{% if group_name %}
</optgroup>{% endif %}{% endfor %}
</datalist>
I’m pretty sure I still need to change that “django/forms/widgets/attrs.html” to something, but I’m not sure what. And right now, I’ve got another issue locating the template.
my template dirs looks like this:
'DIRS': [os.path.join(BASE_DIR, 'templates')],
So I have my templates for my apps on the same level as the manage.py file and the other apps.
I put the copied template code from above into “templates/forms/datalist.html”. I also modified mySubclass to be:
class mySubclass(Input):
input_type = 'text'
template_name ="forms/datalist.html"
But now I’m getting the error:
venv\lib\site-packages\django\template\backends\django.py", line 84, in reraise
raise new from exc
django.template.exceptions.TemplateDoesNotExist: forms/datalist.html
You probably don’t need to change the reference to the attrs.html template. It’s a pre-existing hook for supplying custom attributed to the html element. That can stay the same.
I’d start by looking at how you’re referencing other templates in your app to see what directories you’re using and how you’re using them in your code. (I organize my templates differently - I use the pattern of having a templates directory in each app. But this is one topic that always trips me up.)
So I dug up one of my functional settings files, the templates section looks like this:
for the widget template i started from select widget template:
<input list="{{ widget.name }}s" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
<datalist id="{{ widget.name }}s">
{% for group_name, group_choices, group_index in widget.optgroups %}{% if group_name %}
<optgroup label="{{ group_name }}">{% endif %}{% for option in group_choices %}
{% include option.template_name with widget=option %}{% endfor %}{% if group_name %}
</optgroup>{% endif %}{% endfor %}
</datalist>
To be able to override widgets template You need to install “django.forms” as app in your settings and set variable:
FORM_RENDERER = ‘django.forms.renderers.TemplatesSetting’