Form attributes duplicating in '.controls' div when using CheckboxSelectMultiple widget with Crispy Forms - django

I have a form that's meant to filter results in a list view. I'm using HTMX to submit a GET request whenever the form changes, and I'm using Crispy Forms to render the form (including the hx attributes on the <form> tag).
I've used this pattern multiple times before with no issues, but on this particular form I want to use the CheckboxSelectMultiple widget. When the form is rendered, the hx attributes that I'm applying to the <form> tag are getting duplicated on the <div class="controls"> tag that holds the checkboxes.
Form
class MyListFilterForm(forms.Form):
status = forms.MultipleChoiceField(choices=MyModel.STATUS_CHOICES, required=False,
widget=forms.CheckboxSelectMultiple)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'GET'
self.helper.disable_csrf = True
self.helper.attrs = {
'hx_get': reverse('myapp:my_view'),
'hx_trigger': 'change',
'hx_target': '#my_table_body',
}
Rendered HTML
<form hx-get="/path/to/view/" hx-target="#my_table_body" hx-trigger="change" method="get">
<div id="div_id_status" class="control-group">
<label for="" class="control-label ">
Status
</label>
<div class="controls" hx-get="/path/to/view/" hx-target="#my_table_body" hx-trigger="change">
<label class="checkbox" for="id_status_0"><input type="checkbox" name="status" id="id_status_0" value="1">Choice 1</label>
<label class="checkbox" for="id_status_1"><input type="checkbox" name="status" id="id_status_1" value="2">Choice 2</label>
<label class="checkbox" for="id_status_2"><input type="checkbox" name="status" id="id_status_2" value="3">Choice 3</label>
</div>
</div>
</form>
This duplication of the hx attributes is causing the HTMX request to fire twice whenever one of these checkboxes is clicked. Not only that, but the first request is firing without the form data, so it's also causing incorrect results.
If I remove the CheckboxSelectMultiple widget and let the default SelectMultiple widget be used, then there is no issue and everything works fine, but CheckboxSelectMultiple is the desired widget for this form.
I know I could render the form manually in the template, or maybe do some custom HTMX event handling to stop the first request from firing, but ideally I'd like to just figure out how to render the form through Crispy Forms without getting the duplicate attributes.
I'm using Django 3.2.7 and Crispy Forms 1.13.0

Related

How to put a link in ModelChoiceField empty_label?

I'm working on an app, with a form that is a ModelForm. I have select boxes for the model foreign keys that display all the objects of the related tables. Since there are many objects in the table, I would like to display only a few and have an extra option that when clicked on displays a kind of modal with the list of all objects of the table.
My form.py:
class ltaForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ltaForm,self).__init__(*args, **kwargs)
self.fields['idagent'] = ModelChoiceField(queryset = Agent.objects.all()[:7], empty_label="<a>Plus</a>")
self.fields['idagent'].widget.attrs.update({'class': "form-control m-1 d-inline-block"})
class Meta:
model = Lta
fields = '__all__'
My template:
<form>
...
<div class="row m-1">
<div class="col-5">
<label for="{{ ltaform.idagent.id_for_label }}">Emmetteur</label>
{{ ltaform.idagent}}
<button type="button" class="btn btn rounded rounded-circle float-start bg-light" data-bs-toggle="modal"
data-bs-target="#agentModal" id="agentVolModalButton">
<i class="fa-solid fa-plus text-secondary"></i>
</button>
</div>
</div>
...
</form>
What I would like is to have an extra option that triggers that modal or popup that I described, so I was wondering if I could set a link in the ModelChoice empty_label to do that. But the way I do it here it just displays as plain text in the html. Is there a way to achieve what I want to achieve?
Try changing it to.
{{ ltaform.idagent|safe }}

Using crispy in Django to change the field size

I have a very simple form for my template. I would like to change its size.
How can you do this? by adding a widget in my forms.py file or by adding a certain class in my template?
My template:
<form method="POST">
{% csrf_token %}
<div class="form-row">
<div class="form-group col-md-12 mb-0">
{{ basic_form.choices_product|as_crispy_field }}
</div>
</div>
<button type="submit" class="btn btn-lg btn-block btn-danger">Wyszukaj <i class="fas fa-heart fa-lg"></i></button>
</form>
I was looking for information on the internet, but to no avail.
I use Bootstrap 4, Django 2+.
When your template is evaluated, the variable specified between your {{ ... }} will be replaced with an "input" element in the HTML DOM. You want to render this "input" element with the Bootstrap 4 class "form-control-lg" or a style attribute such as "line-height", which will change height of the element.
crispy-forms allows you to do all this from forms.py
Specifically, you can change the way form fields are rendered with crispy-forms by using the Layout class from the crispy_forms.layout module. Full details are given here in the crispy-forms docs.
In your case, you would need to (1) Add a FormHelper to your form; (2) Add a layout to your helper; and (3) Use the Field layout object to set attributes on any field.
So you can change forms.py like so:
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field
from django import forms
class BasicForm(forms.Form):
[...] # Define your fields here
def__init__(self, *args, **kwargs):
# Add a FormHelper
self.helper = FormHelper()
# Add a layout to your helper
self.helper.layout = Layout(
# Use the Field layout object to set attributes on
# choices_product field. type="text" should be there by
# default if you have used a CharField or TextField.
Field(
'choices_product', css_class="form-control form-control-lg", id="exampleInputSize3", placeholder=".form-control-lg",
)
)
super(BasicForm, self).__init__(*args, **kwargs)

Is there any idea that i can put closing tag(/>) in text input tag of html in django form processing?

//here is form in django code
<form method="post" novalidate>
{% csrf_token %}
{{ form }}
<button type="submit">Submit</button>
</form>
//now the form is rendered as
<input type="text" name="phone_email" required id="id_phone_email">
<input type="text" name="full_name" required id="id_full_name">
//now this html code doesn't have any input field closing tag(/>) any i am being able to run the jsx code because of the absence of closing input tag.any idea how can i put closing tag(/>) in django??
To override how a form renders the HTML for its input fields, you could create a custom form widget.
from django.forms.widgets import Input
class JSXInput(Input):
template_name = 'myapp/widgets/jsxinput.html'
And add this widget to your form field:
class MyForm(Form):
full_name = forms.CharField(widget=JSXInput)
Then you can crib off of the django/forms/widgets/input.html template in the django.forms.templates module and add your closing brace.
<input type="{{ widget.type }}" .... />
This was for Django 2, but iirc it is pretty similar in 1.11.
However, you should ask yourself whether or not your JSX code should be rendered by Django, or instead be a static asset that then talks to a Django REST API.

Applying bootsrap and custom css to {{form}} elements in django

I am trying to apply CSS to individual {{form}} fields on my html template.
I am referencing this solved question:
CSS styling in Django forms`
using the answer I created my forms.py page as follows:
from django import forms
from .models import ContactInfo
class ContactForm(forms.ModelForm):
# class meta brings in the fields from models
class Meta:
model = ContactInfo
# grab the fields from models ContactInfo class
fields = ('Fullname')
# specify which bootstrap class each html widget should take
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
self.fields['Fullname'].widget.attrs.update({'class': 'form-control'})
html using {{form}} elements:
<div class="form-group">
<label for="id_Fullname">Full Name:</label>
{{form.Fullname}}
</div>
The working html w/o usage of {{form}}:
<div class="form-group">
<label for="fullname">Full Name:</label>
<input type="text" class="form-control" id="fullname">
</div>
How do I get the "label" tag to point to the id inside {{form.Fullname}}. My guess is it is not doing that right now hence it doesn't apply the css. That or the class i specified in the widgets in the forms.py is not working how I want it to.
<div class="form-group">
{% for field in form %}
<label for="id_Fullname" id="{{ field.name }}">Full Name:</label>
{{ field }}
{% endfor %}
</div>
try this
You can use the id_for_label property of field:
Use this property to render the ID of this field. For example, if you are manually constructing a <label> in your template.
<div class="form-group">
<label for="{{ form.Fullname.id_for_label }}">Full Name:</label>
{{ form.Fullname }}
</div>
This was an error of placing the def init function inside Class Meta: instead of Class ContactInfo. I had not realized I had indented it improperly.

Get the type of field in django template

I am using django forms and I want to use Twitter Bootstrap's css in my html.
so my template looks like this:
{% for field in form %}
<div class="form-group">
{{ field.label_tag }}<!--Same thing as : <label for="{{field.id_for_label}}"></label> -->
<input type="{{field.type}}" class="form-control" id="{{field.auto_id}}" placeholder="Email">
</div>
{% endfor %}
I can't figure out out to get the type value. {{field.type}} .
Is there any way to get the type of the input field in django templates?
Thanks in advance
Update:
The reason I am asking this question is because I want to be able to apply bootstrap classes to the input element. But in Bootstrap3, to use the default css for input types you would have to add form-control class to the input element like so: <input type="text" class="form-control" id="{{field.auto_id}}" placeholder="">.
If I use django's field {{field}} then I can't add the form-control class.
I hope this clarifies some things.
I also saw this app https://github.com/dyve/django-bootstrap3 that looks like it does what I wanted to do. It surprises me that django doesn't allow accessing the form type to allow more flexibility.
According to https://stackoverflow.com/a/31538555/254553
You can use:
{{ [FIELD].field.widget.input_type }}
[FIELD] is your field name.
I don't think you need to worry about the field_type. Django will itself handle that for you depending on the form field.
Lets say we have a ContactForm like:
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField(widget=forms.Textarea)
sender = forms.EmailField()
cc_myself = forms.BooleanField(required=False)
Then {{form.subject}} will automatically create <input> element in the template.
<input id="id_subject" type="text" name="subject" maxlength="100" />
Similarly, {{form.message}} in the template will create:
<input type="text" name="message" id="id_message" />
Though if you really need to get the form field type in the template, you can create a custom template filter like below.
from django import template
register = template.Library()
#register.filter(name='field_type')
def field_type(field):
return field.field.widget.__class__.__name__
Now, in your template, you need to do something like:
{{form.field_name|field_type}}
In the above example, {{form.message|field_type}} will return TextInput.
If you want to access the field type then refer to this answer.
If you want to override the default type of the field, use attrs parameter when defining the widget for the field.
Eg.
field_name = forms.CharField(widget=forms.Textarea(attrs={'type': 'custom type'}))
Also note that you can pass any key value pair to attrs and they will be used as attributes in the html tag when the form is rendered.
I also had this problem. I used Django Widget Tweaks to add classes (https://github.com/kmike/django-widget-tweaks).
Then you can do something like this:
{% for field in form %}
<div class="form-group {% if field.errors %}has-error {% endif %}">
{% render_field field class="form-control" %}
{% if field.errors %}
<span class="help-block">{{ field.errors.0 }}</span>
{% endif %}
</div>
{% endfor %}
I think another way of dealing with this is to use django crispy forms but I have not tried that yet.
You are able to override the __init__ method of the form in order to pass attributes to the HTML without having to know the type of field. Works with both standard forms and modelforms
class CoolForm(forms.Form):
field_name = forms.CharField(...)
def __init__(self, *args, **kwargs):
super(CoolForm, self).__init__(*args, **kwargs)
self.fields['field_name'].widget.attrs = {
'class': 'form-control'
}
You can pass any HTML attribute through to the template this way, for example 'placeholder': 'email#exam.pl'