Django ModelMultipleChoiceField bullet style - django

I have a form that offers options for signing up to mailing lists.
field = forms.ModelMultipleChoiceField(
queryset=MailingList.objects.filter(
list_active=True),
widget=forms.CheckboxSelectMultiple(
{'class': 'no-bullet-list',
'style': 'list-style: none;'}))
# Specifying class and style both is excessive, I'm still exploring.
Meanwhile, I've put this in my CSS sheet:
.no-bullet-list {
list-style-type: none;
}
This does what I want, rendering a correct set of choices as checkboxes, but it also puts bullets before them. This is because the rendered HTML looks like this:
<ul id="id_newsletters" class="no-bullet-list">
<li><label for="id_newsletters_0">
<input type="checkbox" name="newsletters" value="1"
class="no-bullet-list" style="list-style: none;"
id="id_newsletters_0">
Annonces</label>
</li>
That snippet comes from django/forms/templates/django/forms/widgets/multiple_input.html, which is the result of the definition of CheckboxSelectMultiple in django/forms/widgets.py.
class CheckboxSelectMultiple(ChoiceWidget):
allow_multiple_selected = True
input_type = 'checkbox'
template_name = 'django/forms/widgets/checkbox_select.html'
option_template_name = 'django/forms/widgets/checkbox_option.html'
def use_required_attribute(self, initial):
# Don't use the 'required' attribute because browser validation would
# require all checkboxes to be checked instead of at least one.
return False
def value_omitted_from_data(self, data, files, name):
# HTML checkboxes don't appear in POST data if not checked, so it's
# never known if the value is actually omitted.
return False
def id_for_label(self, id_, index=None):
""""
Don't include for="field_0" in <label> because clicking such a label
would toggle the first checkbox.
"""
if index is None:
return ''
return super().id_for_label(id_, index)
My attempt to add CSS attributes to the <li> only succeeded in adding to the <ul> and to the <input ...>, so it doesn't do what I want (remove the bullets). In addition, it appears there's no customisation hook to style the <li>.
The only way I see to do this is to copy the full widget definition and its template to my app and modify them. This is icky. Is there a portable way to style the checkbox list items?
FWIW, my own template does this:
<form method="post">{% csrf_token %}
<p>{{ form.non_field_errors }}</p>
{% for field in form %}
<p>
{{ field.label }}<br>
{{ field }}
{% if field.help_text %}
<small style="color: grey">{{ field.help_text }}</small>
{% endif %}
</p>
{% for error in field.error_messages %}
<p style="color: red">{{ error }}</p>
{% endfor %}
{% endfor %}
<button type="submit" class="btn btn-outline-primary">Je m'inscris</button>
</form>

Your forms.py should work. A couple steps to take to debug:
clear your browser history. I remember I was extremely frustrated with css, and it turns out that they were caching problems.
If step 1) not working, open your chrome develop tools, and see if your css is loading. If you get something like below:
GET http://127.0.0.1:8000/static/soforms/css/soforms.css
net:: ERR_ABORTED 404 (Not Found).
2-1) then try to make sure you have
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR.joinpath('static_files')
2-2) see if your css file is located in the right place.
2-3) in a rare case, it might be due to you forgot migrate after you generate your app. This actually happened to me today.

Related

trouble showing desired checkbox validation state w/ bootstrap5 for django model form w/ m2m field and checkboxselectmultiple widget

I have a checkboxselectmultiple on an m2m model field in an ModelForm that is required - meaning at least one of the choices must be selected. I am using the boostrap5 was-validated class on my form:
<form method="POST" action="{{ request.path }}" {% if attempt_submit %}class="was-validated"{% endif %}>
This question is about how the validation shows up on my form with bootstrap5. Should be red border and red ! if not validated, green border and checkmark if so. However, for my checkboxes, if I don't have any selected (and everything else on the form validates), the form will show each checkbox option as green instead of red. Yet, it does know that it's invalid because the page focus will come back up the checkbox area to show the user what to correct (and it doesn't pass form.is_valid() in views.py.
Why are these labels and boxes still showing green and how can I show them as red until I select one and it's now valid?
Along the lines of this post, I have tried adding
{% if form.sales_location.field.required %}required{% else %}form.sales_location.field.required=""{% endif %}
to the checkbox <input>, but then each field is required and if I select one, the other remaining options still remain red - as if every option would have to be selected for the form to validate. Am I supposed to do this anyway and then add something else (JS?) to disable that?
Not sure exactly what code would be helpful to see...
in models.py, this is the field:
sales_location = models.ManyToManyField(SalesLocation, verbose_name="Where do you sell your products? (select all that apply)" )
in forms.py
model = AssessmentProfile
fields = [
'sales_location',
...
]
widgets = {
'sales_location': forms.CheckboxSelectMultiple(attrs={
'class': 'form-check',}),
}
I add this because I read this post about making sure that I use a `ModelMultipleChoiceField' - but I assume that is already happening because it's a model form.(?)
Probably most important, in the template thisform.html, here's how I'm manually adding this form element:
<div class="field-wrapper">
{{ form.sales_location.label_tag }}
<ul id="id_sales_location" class="form-check">
{% for pk, choice in form.sales_location.field.widget.choices %}
<li>
<input {% for location in location_qs %}{% if location == pk %}checked='checked'{% endif %}{% endfor %}
name="sales_location" class="form-check-input" type="checkbox" value="{{ pk }}" id="id_sales_location_{{forloop.counter0}}"
{% if already_submitted %}disabled="disabled"{% endif %}>
<label class="form-check-label" for="id_sales_location_{{forloop.counter0}}">
{{ choice }}
</label>
</li>
{% endfor %}
</ul>
</div>
Also, I tried updating css to manually format red, but think that doesn't address the root of the problem, plus, I wasn't able to do it successfully anyway.
Thanks for taking a look and for any suggestions.
In the end, I used javascript to solve this problem.
I updated the form template
<div class="field-wrapper">
{{ form.sales_location.label_tag }}
<ul id="id_sales_location" class="form-check">
{% for pk, choice in form.sales_location.field.widget.choices %}
<li>
<input {% for location in location_qs %}{% if location == pk %}checked='checked'{% endif %}{% endfor %}
name="sales_location" class="form-check-input" type="checkbox" value="{{ pk }}" id="id_sales_location_{{forloop.counter0}}"
{% if not form.sales_location.field.required %} {% else %} required {% endif %}
{% if already_submitted %}disabled="disabled"{% endif %}>
<label class="form-check-label" for="id_sales_location_{{forloop.counter0}}">
{{ choice }}
</label>
</li>
{% endfor %}
</ul>
</div>
to add required to the input if the checkbox is required. This allows all the checkboxes to come up red when validating, if the field is empty.
Then, I added this javascript to remove 'required' if it's checked.
<script>
// Select all checkboxes using querySelectorAll.
var checkboxes = document.querySelectorAll("input[type=checkbox][name=sales_location]");
checkboxes.forEach(function(checkbox) {
checkbox.addEventListener('change', function() {
for (var cb of checkboxes) {
cb.removeAttribute('required');
}
})
});
</script>
If the field is not required, nothing changes. But if it is, then the required attribute on the <input>is gone and all the checkboxes show up green, which is what I wanted.
It's not perfect because if the checkboxes become unchecked, they don't change back to red. So I am making a dirty assumption that if someone checked a box, they wouldn't go back and uncheck it and try to submit. In which case, the validation would show green (and unchecked) until Submit was pressed again, but then it would take them back to this field which would be red again. If you know how to improve my code by adding the different case for the change function (only if the field is required), please feel free to add that. Cheers.

Check for errors and other values at the widget level - maybe using custom form field

How can I access if a field has)errors at the level of widget?
Using default I tried:
{% if widget.attributes.has_errors %} or {% if widget.has_errors %}
but are not working.
I use custom widget templates, I'm thinking to use a custom form Field and overwrite the default field.
I know clean method exist but I don't know how to push to the widget the dynamic(non default) data/attributes I want.
I tried:
class AWidget(forms.Widget):
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
has_errors = context['widget']['attrs'].pop('has_errors', None)
context['widget']['has_errors'] = has_errors
It works for errors but I don't know if is the best option plus I want to pass other values/attributes from Form Field,and I think will be better to try to overwrite the Form Field but I don't know exactly how.
Also accessing individual attributes using:
{{ widget.attrs.maxlength }} or {{ widget.attrs.items.maxlength }}
even if accedes in a for loop works
I know I can add a parent div with a class of error:
<div class="{% if form.field.errors %}pass_error{% endif %}">
{{ form.field }}
</div>
but, that implies big changes at the css level.
I already overwrite all Django widgets with custom widgets, on error I don't need just to change a border color, but to show or not different elements of the widget template and the position of some of them change.
I already modify the based widget to add errors, but I'm looking to do it in a more elegant way at the field level by passing from the field to the widget, parameters depending on error type.
So my question is what I need to overwrite to pass from field to widget errors and other variables ?
Not sure whether this could help in your specific use case ... but just in case, please note that when you build your form in the view, you can add extra parameters as needed, then pass them down to your custom widget.
Working example:
file "forms.py"
from django import forms
def build_ingredient_form(unit):
"""
Ingredient form factory
Here we build the form class dynamically, in order to acces 'unit' via closure.
References:
http://stackoverflow.com/questions/622982/django-passing-custom-form-parameters-to-formset#623030
"""
class IngredientForm(forms.Form):
#quantity = forms.DecimalField(max_digits=10)
quantity = UnitField(unit, required=False)
...
return IngredientForm
file "fields.py"
from django import forms
from .fields import UnitField
class UnitField(forms.CharField):
"""
Custom field to support UnitWidget
References:
- http://tothinkornottothink.com/post/10815277049/django-forms-i-custom-fields-and-widgets-in
"""
def __init__(self, unit, *args, **kwargs):
self.unit = unit
super(UnitField, self).__init__(*args, **kwargs)
self.widget = UnitWidget(unit)
...
file "widgets.py"
from django import forms
from .models import Unit
class UnitWidget(forms.TextInput):
def __init__(self, unit, attrs=None):
if unit is None:
self.unit = Unit()
else:
self.unit = unit
...
Well a widget is how you will render the field's data/value into the HTML rendered template, that's the only function of widgets, look the following example taken from the docs:
>>> name = forms.TextInput(attrs={'required': True})
>>> name.render('name', 'A name')
'<input name="name" type="text" value="A name" required>'
>>>
>>> name = forms.TextInput(attrs={'required': False})
>>> name.render('name', 'A name')
'<input name="name" type="text" value="A name">'
So, widgets are not aware of the data is valid(has errors) or not and should remain that way.
Is not a good idea to handle any data error/validation at the widget level, you want, I can ensure that, that if you change how your field looks like (the widget), your validations keeps working.
Said that ...
How can I access field errors?
When you are rendering a form you can do it field by field lets take this form by example:
class LoginForm(forms.Form):
username = forms.CharField(max_length=255)
password = forms.CharField(widget=forms.PasswordInput)
you can write to temlate:
<form action="." method="get">
<p>{{ loginform.username.label }}: {{ loginform.username }}</p>
<p>{{ loginform.password.label }}: {{ loginform.password}}</p>
<button type="submit">submit</button>
</form>
And this will render something like the following:
Now, suppose your form won't admit passwords with less than 8 characters:
class LoginForm(forms.Form):
username = forms.CharField(max_length=255)
password = forms.CharField(widget=forms.PasswordInput)
def clean_password(self):
password = self.cleaned_data['password']
if len(password) < 8:
raise forms.ValidationError(
"Password must have at least 8 characters, it has only %(password_length)s",
code='invalid password',
params={'password_length': len(password)}
)
return password
You can access the password errors like this:
<form action="." method="get">
{% csrf_token %}
<p>{{ form.username.label }}: {{ form.username }}</p>
<p>{{ form.password.label }}: {{ form.password}}</p>
<ul>
{% for error in form.password.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
<button type="submit">submit</button>
</form>
And now if you type a short password ...
I want the control to look different if there are errors.
You can add some style if there are errors just use {% if ... %} in your template code:
<p>
{{ form.password.label }}:
<span class="{% if form.password.errors %}pass_error{% endif %}">
{{ form.password }}
</span>
</p>
With CSS:
<style>
.pass_error input {
border-color: red;
}
</style>
And this is the result:
Conlusion.
Validate and handle data errors in the form or using validators, use widgets for display the data, of course, you can customize how the data is presented since you can specify a custom template for your widget.
I also recommend django-widget-twaeks if you want to add attributes to your widget in template code. This apps allows you to write code like (example from the app docs):
{% load widget_tweaks %}
<!-- change input type (e.g. to HTML5) -->
{% render_field form.search_query type="search" %}
<!-- add/change several attributes -->
{% render_field form.text rows="20" cols="20" title="Hello, world!" %}
<!-- append to an attribute -->
{% render_field form.title class+="css_class_1 css_class_2" %}
<!-- template variables can be used as attribute values -->
{% render_field form.text placeholder=form.text.label %}

Django select 2 multiple select slow load

I'm a beginner and I've been playing around with the multiple select option of select2.js. In a historical school database we have over 300k student_id's. I can get the select2 option to work, but it's extremely slow and takes forever to load. I've seen other pages with select 2 load massive amounts of data and work fine.
I'm using the following to javascript to load select2.
$(document).ready(function () {
$('.js-example-basic-multiple').select2();
});
In Django i'm loading the data in my template with:
<script src= "{% static '/search/user_select2.js' %}" type="text/javascript"></script>
<div class="col"><h4 style="margin-top: 0"><strong>Student ID List</strong></h4><select data-placeholder="Choose a list of 3-4 User ids..." class="js-example-basic-multiple" value = "{{ userid }}" style="width: 1110px" required>
{% for user in userid %}
<option value="{{ user }}"> {{ user }}</option>
{% endfor %}
</select>
userid is defined with the following arg in my view:
def multisearch(request):
userid = STudent.objects.filter(student_status_id = 1).values('studentid')
print(userid)
args = {'userid':userid}
return render(request, 'multisearch.html',args)
It takes a long time to load be cause you preload all options of your select2 input. I would suggest you to use select2 through django-autocomplete-light to avoid this issue. It will provide you tools to setup an autocomplete system and load matching options while typing text on your select2 input. Moreover, results paginated so that they are loaded as you scroll the select2 dropdown.
Basically you will have an autocomplete view:
from dal import autocomplete
from your_app.models import Student
class Status1StudentsAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = Student.objects.filter(student_status_id=1)
if self.q: # self.q is the user-typed query
qs = qs.filter(name__istartswith=self.q)
return qs
That, of course, needs to be routed:
from your_app.views import Status1StudentsAutocomplete
urlpatterns = [
url(
r'^autocomplete/students/status-1/$',
Status1StudentsAutocomplete.as_view(),
name='students_status1_autocomplete',
),
]
Then use an autocomplete widget for your field:
from dal import autocomplete
from django import forms
class SomeForm(forms.Form):
student = forms.ModelChoiceField(
queryset=Student.objects.filter(student_status_id=1),
widget=autocomplete.ModelSelect2(url='student_status1_autocomplete')
)
Finally just display your form as you usually would and don't forget to include the custom css/js with {{ form.media }}.
{% extend "your_layout.html" %}
{% block extrahead %} {# assuming extrahead block is within the <head> element #}
{{ block.super }}
{{ form.media }}
{% endblock %}
{% block content %} {# assuming you have a content block within your <body> element #}
<form method="POST">
{% csrf_token %}
{{ form }}
<button type="submit">Go!</button>
</form>
{% endblock %}

Django URL is being changed by a submit button

I'm very new to Django and not super familiar with web programming in general, so it's very likely that there is an easy fix to my problem that I'm just unaware of.
My web app is a photo gallery. People can click on a photo to see an enlarged version with buttons on either side for older or newer pictures. In addition, the photos in the gallery can be sorted by tags, which are passed along as URL parameters.
My problem is that when I click on one of the submit buttons, Django replaces the parameters in my URL with the name of the button, thus destroying my reference to what tag I was using. For example, "127.0.0.1:8000/gallery/view/6/?tag=people" upon clicking next, gets converted to "127.0.0.1:8000/gallery/view/6/?older=Older" when it's trying to process the URL.
Code from my HTML:
<form action="/gallery/view/{{ photo.id }}/?tag={{ tag }}" method="get">
{% if has_newer %}
<input type="submit" name="newer" value="Newer">
{% endif %}
<img src="{{ photo.photofile.url }}">
{% if has_older %}
<input type="submit" name="older" value="Older">
{% endif %}
</form>
In my view.py I pass in the tag plus other information in a render_to_response, but I'm not sure how to/if I can reclaim it while handling the buttons.
render_to_response('item/view.html', {'photo':photo, 'tag':tag, 'related_tags': related_tags, 'related_photos': related_photos, 'has_newer': has_newer, 'has_older': has_older}, context_instance=RequestContext(request))
Here's the view.py code for processing the buttons:
if 'newer' in request.GET:
if has_newer:
return HttpResponseRedirect('/gallery/view/%s/?tag=%s'%(newer[1].id, tag))
else:
return HttpResponseRedirect('/gallery/')
if 'older' in request.GET:
if has_older:
return HttpResponseRedirect('/gallery/view/%s/?tag=%s'%(older[1].id, tag))
else:
return HttpResponseRedirect('/gallery/')
<form action="/gallery/view/{{ photo.id }}/" method="get">
{% if has_newer %}
<input type="submit" name="newer" value="Newer">
{% endif %}
<!--This will append a tag parameter with given value to the querystring -->
<input type="hidden" name="tag" value="{{ tag }}">
<img src="{{ photo.photofile.url }}">
{% if has_older %}
<input type="submit" name="older" value="Older">
{% endif %}
</form>
Note that the query string is removed from action (as it won't be used) and the older and newer parameters will still be sent along.

django forms - getting django to build the form you want

class BaseForm(forms.Form):
def as_custom_table(self):
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
return self._html_output(
normal_row = u'<tr%(html_class_attr)s><td class="label_col">%(label)s</td><td class="field_col">%(field)s%(help_text)s</td></tr>',
error_row = u'<tr><td colspan="2" class="error">%s</td></tr>',
row_ender = u'</td></tr>',
help_text_html = u'<br />%s',
errors_on_separate_row = True)
I'm trying to see if I can get django to do the heavy lifting and render a form in the way I want it to render.
The problem here is, there might be one or two fields that need to render slightly differently. Such as a "please confirm you agree to our terms and conditions" check box, which would need to span two columns.
Also some other things, such as error placement, might need changing.
I could override the _html_output method to change error placement, but what about getting an individual field to use a different render method?
I think ultimately I need to revert to manually building the form html in the template, but I'm just wondering how much of it Django could do for me with some minor modifications.
The suggested method will be to use a template like this:
<form action="/contact/" method="post">
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endfor %}
<p><input type="submit" value="Send message" /></p>
</form>
You can conditionally override specific fields using {{ if field.my_property }}.