django-crispy-forms bootstrap 4: How to display checkboxes horizontally? - django-forms

I can't seem to make my CheckboxSelectMultiple widget to display horizontally using django-crispy-forms/bootstrap4.
I've tried :
Specifying it on the form's widgets :
widgets = {'my_field': forms.CheckboxSelectMultiple(attrs={'class': 'form-check-inline'}),}
but the checkboxes still display vertically, and the template renders to this:
<div class="custom-control custom-checkbox"> <input type="checkbox" class="custom-control-input" name="my_field" id="id_my_field_2" value="2" class="form-check-inline"> <label class="custom-control-label" for="id_my_field_2">
This " class="form-check-inline" " is displayed in red.
Same thing happens if I use crispy-forms' helper.
self.helper.layout = Layout(Field('my_field', css_class="form-check-inline"))
Any clue why that is? Can someone suggest an alternative?
p.s. : Settings are : CRISPY_TEMPLATE_PACK = "bootstrap4"
Template is :
{% csrf_token %} {% load crispy_forms_tags %} {% crispy form form.helper %}
** Edit **
I managed to render them by using:
from django_crispy.bootstrap import InlineCheckboxes
self.helper.layout = Layout(InlineCheckboxes('my_field'))

from crispy_forms.bootstrap import InlineRadios
class DhcpForm(forms.ModelForm):
cargo = forms.ChoiceField(label='Cargo on Deck',
choices=[('true', 'Yes'),
('false', 'No')]
def __init__(self, *args, **kwargs):
self.helper.layout = Layout(
InlineRadios('cargo', id="radio_id"))

Related

Summernote using Django forms displaying with HTML tags

Hi So I have just set up summernote for the first time, however when I save all the HTML tags are displayed in another page where I am displaying the text. I want it to display the correct way without the HTML, thanks for any help :)
FORMS.PY
from django_summernote.widgets import SummernoteWidget, SummernoteInplaceWidget
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title','content']
widgets = {
'content': SummernoteWidget(),
}
VIEWS.PY
class PostCreateView(CreateView):
model = Post
form_class = PostForm
Form HTML
<div class ='content-section'>
<form method="POST">
{% csrf_token %}
<fieldset class ="form-group">
<legend class ="border-bottom mb-4">Post 1</legend>
{{form|safe}}
</fieldset>
<div class="form-group">
<button class = "btn" type="submit">Post</button>
</div>
</form>
</div>
display Post HTML
{% extends 'guide/base.html' %}
{%block content%}
{% if post.author == user %}
Edit
Delete
{% endif %}
<div class = 'content'>
<h2>{{post.title}}</h2>
<p></p>
<p>{{ post.content }}</p>
</div>
{% endblock content %}
I didn't include the safe in the html so
{{post.content|safe}}
did not include because I thought you only include the safe in the form

How to set each field as required for formset_factory in Django (how to validate blank forms in formsets)

I have the form in my forms.py:
class NameForm(forms.Form):
your_name = forms.CharField(label='Your name', max_length=100)
age = forms.CharField(label='Age', max_length=100)
sex = forms.CharField(label='Sex', max_length=100)
I created the formset_factory
NameFormSet = formset_factory(NameForm, extra=0)
In my views.py for the get context_data, I have:
...
def get_context_data(self, **kwargs):
context = super(APView, self).get_context_data(**kwargs)
if self.request.POST:
context['formset'] = NameFormSet()
else:
recommended = returnWebAttackResults(self.kwargs['webAttack'])
if recommended is None:
context['recommendedAP'] = False
else:
context['formset'] = NameFormSet(initial=recommended[0])
return context
....
In my template file, I have:
<form class="row" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ formset.management_form }}
{{ formset.non_form_errors }}
{% for forma in formset.forms %}
{{ forma }}
{% endfor %}
<input class="btn bg-success" type="submit" value="Update" />
</form>
My problem is that I don't get "This field is required" when I click the "Update" button. I tried setting the use_required_attribute to True, but it did not work.
Scenario:
The returnWebAttackResults function gets the initial data for the forms in the formset. So, if I have three forms, if one of the fields is blank (no user input) in any of the forms, then when I click the Update button, each field that is blank should be highlighted with the "This field is required". I can do this when I just render a regular form, but when I am using formsets, it does not work.
Is there a way to validate the forms in the formset before the request in sent?
I hit the same problem and found this in the documentation
https://docs.djangoproject.com/en/3.0/ref/forms/fields/#core-field-arguments
Widgets of required form fields have the required HTML attribute. Set the Form.use_required_attribute attribute to False to disable it. The required attribute isn’t included on forms of formsets because the browser validation may not be correct when adding and deleting formsets.
If you have an empty row (for adding an extra record) then you can't save the form until the empty row is filled in. So, if you want to say update one existing row without adding an extra row then you have a problem, you can't do it. The problem really comes from the fact that a formset is really in html terms just a single form. A workaround for that problem might be to use javascript to add rows to the formset only as needed, as in this example:
https://whoisnicoleharris.com/2015/01/06/implementing-django-formsets.html
There is probably furthermore a javascript solution to validate the form fields before you send the request, I think this may be such a solution but I haven't tried it:
https://jqueryvalidation.org/required-method/
It would make sense that a completely empty row should not be validated for required fields but just ignored altogether.
UPDATE: I got a simple solution working from the example in the above link. My template looks like this (note that no empty row is shown initially thanks to the if statement):
{% extends "base_generic.html" %}
{% load static %}
{% block content %}
<h1>Experiment Detail</h1>
<form name="test_form" method="post">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
{% if form.name.value %}
<div class="link-formset">
{{ form.name }}
</div>
{% endif %}
{% endfor %}
<input type="submit" value="Save" class="button"/>
</form>
<!-- Include formset plugin - including jQuery dependency -->
<!-- <script src="{% static 'path_to/jquery-3.4.1.js' %}"></script> -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="{% static 'js/jquery_formset.js' %}"></script>
<script>
$('.link-formset').formset({
addText: 'Add',
deleteText: 'Delete'
});
</script>
{% endblock %}
In my view I have this for loop to set the use_required_attribute:
for form in formSet:
form.use_required_attribute = True
The solution seems to work OK, when I click the Add button an empty row appears and the empty 'name' field has the expected red box round the empty field and when I try to click Save I get the message 'Please fill out this field.'
the solution is, add BaseFormSet in froms.py, like this:
forms.py
from django.form import BaseFormSet
class NameForm(forms.Form):
your_name = forms.CharField(label='Your name', max_length=100, blank=False)
age = forms.CharField(label='Age', max_length=100, blank=False)
sex = forms.CharField(label='Sex', max_length=100, blank=False)
class RequiredFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
super(RequiredFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
form.use_required_attribute = True
NameFormSet = formset_factory(NameForm, formset=RequiredFormSet)

Using django crispy form, how do you set extra_context in the Field method of the Layout object?

I am using crispy forms 1.6 with django 1.9 and python 3.4.
In using the Field class with a Layout , I am not able to get the extra_context to work. The values are not being injected into the context.
The template below fails to output the "threshold" value.
Here is my form code.
# project_edit_form.py
from django import forms
from django.contrib.auth.models import Group
from .models import Projects
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, HTML
class ProjectEditForm(forms.ModelForm):
class Meta:
model = Projects
fields = ('project_description', 'location', 'days_elapsed',)
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
# ...
self.helper.layout = Layout(
Fieldset(
'Project',
'project_description',
'location',
Field('days_pge_first_responded', template="days_elapsed_field.html", extra_context={"threshold":15})%}"),
),
)
super(ProjectEditForm, self).__init__(*args, **kwargs)
And here is the html template for the Field:
# days_elapsed_field.html
extra_context.threshold: {{ extra_context.threshold }}
threshold: {{ threshold }}
field.threshold: {{ field.threshold }}
<div id="div_id_{{ field_name }}" class="form-group">
<label for="{{ field.id_for_label }}" class="control-label"> {{ field.label }} </label>
<div >
<input
style="{% if field.value > threshold %} background-color:#FFE8E8; {% else %} background-color:#e3e3e3 {% endif %}"
class="textinput textInput form-control" id="{{ field.id}}" name="{{ field.name}}" readonly="True" type="text" value="{{ field.value }}" />
</div>
</div>
And here is the main html template:
{% load crispy_forms_tags %}
<!doctype html>
<html>
<head>...</head>
<body>
<form action="" method="post" class="form-horizontal" >
{% crispy form %}
</form>
</body>
</html>
I couldn't figure out a built-in way of doing it, so I just created a generic class and used that instead. You can pass extra_context as a kwarg to CustomCrispyField. extra_context is just a dictionary that I access in my custom template that I copied from crispy_forms.
from crispy_forms.layout import Field
from crispy_forms.utils import TEMPLATE_PACK
class CustomCrispyField(Field):
extra_context = {}
def __init__(self, *args, **kwargs):
self.extra_context = kwargs.pop('extra_context', self.extra_context)
super(CustomCrispyField, self).__init__(*args, **kwargs)
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, extra_context=None, **kwargs):
if self.extra_context:
extra_context = extra_context.update(self.extra_context) if extra_context else self.extra_context
return super(CustomCrispyField, self).render(form, form_style, context, template_pack, extra_context, **kwargs)
And I would use it like so in my form:
self.helper.layout=Div(CustomCrispyField('my_model_field', css_class="col-xs-3", template='general/custom.html', extra_context={'css_class_extra': 'value1', 'caption': 'value2'})
And my template would have code similar to the following:
{% crispy_field field %}
<button class="btn {{ css_class_extra }}">{{ caption }}</button>

Django - Allow User to Edit Profile and then Show Updated Profile fields

I have the form populating with the user profile information, but when I click save, it doesn't actually update.
Any clues/hints as to which part I need to modify is greatly appreciated.
Thanks in advance!
views.py
def profile_view(request):
user = request.user
form = EditProfileForm(initial={'first_name':user.first_name, 'last_name':user.last_name})
context = {
"form": form
}
return render(request, 'profile.html', context)
def edit_profile(request):
user = request.user
form = EditProfileForm(request.POST or None, initial={'first_name':user.first_name, 'last_name':user.last_name})
if request.method == 'POST':
if form.is_valid():
user.first_name = request.POST['first_name']
user.last_name = request.POST['last_name']
user.save()
return HttpResponseRedirect('%s'%(reverse('profile')))
context = {
"form": form
}
return render(request, "edit_profile.html", context)
forms.py
class EditProfileForm(forms.ModelForm):
first_name = forms.CharField(label='First Name')
last_name = forms.CharField(label='Last Name')
class Meta:
model = User
fields = ['first_name', 'last_name']
edit_profile.html
{% extends "base_site.html" %}
{% block content %}
<h1>Edit Profile</h1>
<form method="POST" action="/accounts/profile/" class="" />
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Save</button>
</form>
{% endblock %}
urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^register/$', 'accounts.views.registration_view', name='auth_register'),
url(r'^login/$', 'accounts.views.login_view', name='auth_login'),
url(r'^logout/$', 'accounts.views.logout_view', name='auth_logout'),
url(r'^profile/$', 'accounts.views.profile_view', name='profile'),
url(r'^profile/edit/$', 'accounts.views.edit_profile', name='edit_profile'),
]
The action in your form is POSTing to profile_view and not edit_profile and your forms are self closing so they aren't being POSTed correctly.
Change this:
<form method="POST" action="/accounts/profile/" class="" />
To this:
<form method="POST" action="/accounts/profile/edit" class="" >
Or even better, use the django url template tag:
<form method="POST" action="{% url 'edit_profile' %}" class="" >

Non Form Errors for a Formset not Rendering with Django Crispy Forms

I implement a custom clean method to validate my formset. I know there are error as I can print them to the console but these non_form_errors() are never rendered in my template. How can I render them?
template.html:
<form action="{% url 'databank:register' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="container">
<div class="row" style="margin-top: 30px">
<div class="col-md-10 col-md-offset-1">
{{ dataset_form.media }}
{% crispy dataset_form %}
<div class="authorFormset">
{% for form in formset %}
{% crispy form helper %}
{% endfor %}
</div>
{% crispy terms_form %}
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<input type="submit" class="btn btn-lg btn-success" value="Submit">
</div>
</div>
</div>
{{ formset.management_form }}
forms.py:
class BaseAuthorFormset(BaseFormSet):
def clean(self):
if any(self.errors):
return
roles = []
for form in self.forms:
contributor_role = form.cleaned_data['role']
for x in contributor_role:
if (x == "Depositor") and (x in roles):
raise forms.ValidationError("Only one Contributor may be marked Depositor.")
roles.append(x)
if "Depositor" not in roles:
raise forms.ValidationError("You must designate one Contributor as Depositor.")
def __init__(self, *args, **kwargs):
super(BaseAuthorFormset, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
class AuthorForm(forms.Form):
first_name = forms.CharField(
max_length=96,
required=True,
)
middle_initial = forms.CharField(
max_length=96,
required=False,
)
last_name = forms.CharField(
max_length = 96,
required = True,
)
email = forms.EmailField(
required = True
)
role = forms.ModelMultipleChoiceField(
queryset=AuthorRole.objects.filter(is_visible=True),
widget=forms.CheckboxSelectMultiple,
required = False,
)
affiliation = forms.CharField(
max_length=512,
required=True,
)
AuthorFormset = formset_factory(AuthorForm, formset=BaseAuthorFormset, extra=1)
class AuthorFormHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(AuthorFormHelper, self).__init__(*args, **kwargs)
self.helper = FormHelper()
FormHelper.form_tag = False
FormHelper.disable_csrf = True
FormHelper.formset_error_title = "Contributor Errors"
self.layout = Layout(
Div(
Div( HTML("<h4>Contributor</h4>"),
Div(
Div('first_name', css_class='col-xs-4'),
Div('middle_initial', css_class='col-xs-4'),
Div('last_name', css_class='col-xs-4'),
css_class='row'
),
Div(
Div('affiliation', css_class='col-xs-12 col-sm-6'),
Div('email', css_class='col-xs-12 col-sm-6'),
Div(
Field(
InlineCheckboxes('role'),
),
css_class='col-xs-12 col-sm-6'),
css_class='row'
),
),
css_class = 'add_author',
id="authorFormDiv"
),
)
After a lot of investigations, I found the solution for render non_form_errors of crispy BaseInlineFormSet in template by native django-crispy's filter. Maybe it would be helpful for someone (it works at least for django-crispy-forms==1.5.2, Django==1.6):
{% load crispy_forms_filters %}
{% if formset.non_form_errors %}
{{ formset|as_crispy_errors }}
{% endif %}
Thank you to #Brandon for helping me figure this out!
In the view, formset.__dict__ can be used to print out the object itself to figure out where the errors are being stored. Doing that showed that the formset errors are stored using the _non_form_errors key. I then passed those errors to the template!

Resources