Django Crispy Form with Toggle switch element - django

I am using Crispy Forms and layout helper to generate my Input form. I'd like to add a Toggle switch into the form.
desired result:
what I get:
forms.py:
class RequestForm(forms.ModelForm):
on_prem = forms.CharField(widget=forms.CheckboxInput(attrs={"class": "slider form-control"}))
class Meta:
model = Request
fields = ['on_prem']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.layout = Layout(
Div(
Div(Field('on_prem'), css_class='col-md-3', ),
css_class='row',
),
)
models.py:
class Request(models.Model):
on_prem = models.BooleanField(default=False)
form.html
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{% crispy form %}
<button type="submit" class="d-block btn mt-4 w-50 mx-auto">
Save Request
</button>
</form>
I have seen in the Bootstrap documentation that the toggle switch effect I desire is achieved with nested css classes and I am not sure how to achieve that via the form helper. Any help would be greatly appreciated.

Fewer lines of code just using "Field":
self.helper.layout = Layout(
Field('notify_faculty_field', css_class="form-check-input", wrapper_class="form-check form-switch"),
)

I managed to achieve this by using HTML blocks in the helper.
form.py
class RequestForm(forms.ModelForm):
on_prem = forms.CharField(widget=forms.CheckboxInput(attrs={"class": "slider form-control"}))
class Meta:
model = Request
fields = ['on_prem']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.layout = Layout(
Div(
Div(
HTML('<label class="form-check-label" for="on_prem">On Prem</label>'),
HTML('<div class="form-switch">'),
Field('on_prem'),
HTML('</div>'),css_class='col-md-3',),
css_class='row',
),
)

Related

Django Crispy forms - inline Formsets 'ManagementForm data' error with update view

Good Evening,
Im having trouble with a crispy forms inlineformset. I have followed guides as per:
https://github.com/timhughes/django-cbv-inline-formset/blob/master/music/views.py
https://django-crispy-forms.readthedocs.io/en/latest/crispy_tag_formsets.html#formsets
EDIT
I think the issue is something to do with the dual submit buttons. the devicemodel form has a button that when pressed produces this error. but there is also a save button as part of the resource helper, when that's submitted I get an empty model form error.
I've added screenshots of what happens when you action each button
and I must be missing something as am getting the error:
['ManagementForm data is missing or has been tampered with']
here is my update view:
class EditDeviceModel(PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
model = DeviceModel
form_class = DeviceModelForm
template_name = "app_settings/base_formset.html"
permission_required = 'config.change_devicemodel'
success_message = 'Device Type "%(model)s" saved successfully'
def get_success_url(self, **kwargs):
return '{}#device_models'.format(reverse("config:config_settings"))
def get_success_message(self, cleaned_data):
return self.success_message % dict(
cleaned_data,
model=self.object.model,
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title']='Edit Device Model'
if self.request.POST:
context['formset'] = DeviceFormSet(self.request.POST, instance=self.object)
else:
context['formset'] = DeviceFormSet(instance=self.object)
context['helper'] = DeviceFormSetHelper()
return context
def form_valid(self, form):
context = self.get_context_data()
formset = context['formset']
if formset.is_valid():
self.object = form.save()
formset.instance = self.object
formset.save()
return redirect(self.success_url)
else:
return self.render_to_response(self.get_context_data(form=form))
Here are my forms:
class MonitoredResourceForm(forms.ModelForm):
class Meta:
model = MonitoredResource
fields = ['resource','model']
def __init__(self, *args, **kwargs):
self.is_add = kwargs.pop("is_add", False)
super(MonitoredResourceForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_id = 'snmp_resource_form'
self.helper.form_method = 'POST'
self.helper.layout = Layout(
Div(
Div(
Field('model'),
Field('resource', placeholder="Resource"),
css_class='col-lg-3'
),
css_class='row'
),
Div(
Div(
HTML("""<input type="submit" name="submit" value="""),
HTML('"Add' if self.is_add else '"Update' ),
HTML(""" monitored resource" class="btn btn-primary"/>"""),
HTML("""Cancel"""),
HTML("""{% if object %}
<a href="{% url 'config:delete_monitoredresource' object.id %}"
class="btn btn-danger">
Delete <i class="fa fa-trash-o" aria-hidden="true"></i></a>
{% endif %}"""),
css_class='col-lg-12'
),
css_class='row'
),
)
class DeviceModelForm(forms.ModelForm):
class Meta:
model = DeviceModel
fields = ['model','vendor','device_type','ports','uplink_speed']
def __init__(self, *args, **kwargs):
self.is_add = kwargs.pop("is_add", False)
super(DeviceModelForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_id = 'device_type_form'
self.helper.form_method = 'POST'
self.helper.layout = Layout(
Div(
Div(
Field('model', placeholder="Model"),
Field('vendor',),
Field('device_type',),
Field('ports', placeholder="Ports"),
Field('uplink_speed', placeholder="Uplink Speed"),
css_class='col-lg-6'
),
css_class='row'
),
Div(
Div(
HTML("""<input type="submit" name="submit" value="""),
HTML('"Add' if self.is_add else '"Update' ),
HTML(""" Device Model" class="btn btn-primary"/>"""),
HTML("""Cancel"""),
HTML("""{% if object %}
<a href="{% url 'config:delete_device_model' object.id %}"
class="btn btn-danger">
Delete <i class="fa fa-trash-o" aria-hidden="true"></i></a>
{% endif %}"""),
css_class='col-lg-12'
),
css_class='row'
),
)
DeviceFormSet = inlineformset_factory(DeviceModel, MonitoredResource, form=MonitoredResourceForm, extra=1)
class DeviceFormSetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(DeviceFormSetHelper, self).__init__(*args, **kwargs)
self.form_method = 'post'
self.render_required_fields = True
self.form_id = 'snmp_resource_form'
self.form_method = 'POST'
self.add_input(Submit("submit", "Save"))
self.layout = Layout(
Div(
Div(
Field('model'),
Field('resource', placeholder="Resource"),
css_class='col-lg-6'
),
css_class='row'
),
)
and in the templates I render:
{% block content %}
{% include "home/form_errors.html" %}
<div class="col-lg-6">
{% crispy form %}
</div>
<div class="col-lg-6">
{% crispy formset helper %}
</div>
<!-- /.row -->
{% endblock %}
is anyone able to see what im missing?
I think you have to render management form in your template, explained here why you need that
Management Form is used by the formset to manage the collection of forms contained in the formset. If you don’t provide this management data, an exception will be raised
add this in view html
{{ DeviceFormSet.management_form }}
You are missing a tag and also {{format.management_form|crispy}}
I guess
Your problem is that each form in a formset has its own management_form. I haven't dealt with this specifically in crispy, but in the general formsets, that was the problem that I had. You have to manually spell out each piece of the formset, either by iteration or hardcoding, and make sure that each has its management_form.
I had the same problem and found the answer in the documentation:
{{ formset.management_form|crispy }}
{% for form in formset %}
{% crispy form %}
{% endfor %}

Cannot pass Helper to django Crispy Formset in template

I'm trying to set up crispy form on a form and a formset (Visit and VisitService).
I'm having trouble attaching the helper to the formset, no matter how I do it.
I added helper as an attribute to the Formset and added the helper to view and context but it keeps giving me the following error:
VariableDoesNotExist - Failed lookup for key [helper]
(also tried [helper_attribute])
here is what I'm using:
forms.py:
class VisitForm(forms.ModelForm):
class Meta:
model = models.Visit
fields = [
[...all visit fields go here...]
]
def __init__(self, *args, **kwargs):
super(VisitForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.layout = Layout(
[...fields go here...]
),
ButtonHolder(
Submit('submit', 'Submit', css_class='button white btn-wide')
)
)
class VisitServiceForm(forms.ModelForm):
class Meta:
model = models.VisitService
fields = [
'service',
'unit',
]
def __init__(self, *args, **kwargs):
super(VisitServiceForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
Row(
Div(Field('service'),css_class='col-sm-4'),
Div(Field('unit'),css_class='col-sm-4'),
)
VisitServiceFormSet = forms.modelformset_factory(
models.VisitService,
form=VisitServiceForm,
extra=2,
)
VisitServiceInlineFormSet = forms.inlineformset_factory(
models.Visit,
models.VisitService,
extra=5,
fields=('service', 'unit'),
formset=VisitServiceFormSet,
min_num=1,
)
views.py:
def create_visit(request, patient_pk):
patient = get_object_or_404(models.Patient, pk=patient_pk)
form_class = forms.VisitForm
form = form_class()
visitservice_forms = forms.VisitServiceInlineFormSet(
queryset=models.VisitService.objects.none()
)
helper = forms.VisitServiceForm()
if request.method == 'POST':
form = form_class(request.POST)
visitservice_forms = forms.VisitServiceInlineFormSet(
request.POST,
queryset=models.VisitService.objects.none()
)
if form.is_valid() and visitservice_forms.is_valid():
visit = form.save(commit=False)
visit.patient = patient
visit.save()
visitservices = visitservice_forms.save(commit=False)
for visitservice in visitservices:
visitservice.visit = visit
visitservice.save()
messages.success(request, "Added visit")
return HttpResponseRedirect(visit.get_absolute_url())
return render(request, 'fpform/visit_form.html', {
'patient': patient,
'form': form,
'formset': visitservice_forms,
'helper': helper,
})
template:
<div class="container">
<form method="POST" action="">
{% crispy form %}
{% crispy formset formset.form.helper_attribute %}
</div>
</form>
in my template I've also used each one of these separately with no luck:
{% crispy formset formset.form.helper %}
{% crispy formset helper_attribute %}
{% crispy formset helper %}
{% crispy formset form.helper_attribute %}
{% crispy formset form.helper %}
I looked at the crispy documentation but couldn't find the answer there.
A whole day spent on this but it hasn't gone anywhere. Also feel free to let me know if there is a better way to achieve this.
To define the layout of formsets you need to create an independent FormHelper class and pass it to the form.
http://django-crispy-forms.readthedocs.io/en/latest/crispy_tag_formsets.html
# forms.py
class VisitServiceFormHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(VisitServiceFormHelper, self).__init__(*args, **kwargs)
self.layout = Layout(
Div(Field('service'),css_class='col-sm-4'),
Div(Field('unit'),css_class='col-sm-4'),
)
#views.py
formset = VisitServiceInlineFormSet(queryset=VisitService.objects.none())
helper = VisitServiceFormHelper()
return render(request, 'template.html', {'formset': formset, 'helper': helper})
#template
{% crispy formset helper %}

crspy-forms layout not work for django inline formset

This is my forms and inlineformset
class EventForm(ModelForm):
def __init__(self, *args, **kwargs):
super(EventForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Field('name'),
Field('description'),
Field('tags'),
)
self.helper.layout.append(Submit('save', 'Save'))
class Meta:
model = Event
fields = ('name','description','tags', )
class GalleryForm(ModelForm):
def __init__(self, *args, **kwargs):
super(GalleryForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.form_class = 'form-inline'
self.helper.form.method = 'post'
self.helper.form.action = ''
self.helper.layout = Layout(
Div(
Div('title', css_class='col-md-4', ),
Div('image', css_class='col-md-4', ),
css_class='row',
),
FormActions(
Submit('submit', 'Submit'),
),
)
class Meta:
model= Gallery
fields = ('title', 'event', 'image')
GalleryFormSet = inlineformset_factory(Event, Gallery, extra=0, min_num=1, fields=('title', 'image' ))
My views:
class EventCreateView(FormsetMixin, CreateView):
template_name = 'member/event_and_gallery_form.html'
model = Event
form_class = EventForm
formset_class = GalleryFormSet
class EventUpdateView(FormsetMixin, UpdateView):
template_name = 'member/event_and_gallery_form.html'
is_update_view = True
model = Event
form_class = EventForm
formset_class = GalleryFormSet
my form.html:
{% block body %}
<form action="." method="post" enctype="multipart/form-data">
{{ formset.management_form }}
{% csrf_token %}
<legend>Event</legend>
<div class="event">
{{ form|crispy}}
</div>
<legend>
<div class="pull-right"><i class="icon-plus icon-white"></i> Add Photo</div>
Photo Gallery
</legend>
<div class="gallery form-inline">
{% for form in formset %}
{{form|crispy}}
{% endfor %}
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Save</button>
</div>
</form>
{% endblock %}
But it didn't give inline formset layout. How do I implement crispy form layout in my form? Help is needed.

Change <input> Types and Attributes for a Form Used in a CreateView and UpdateView

I'm experimenting with a Django 1.4.6 project for tracking sales leads. I want this to be mobile-friendly, so I'm using Twitter Bootstrap (still on version 2.3.2) with django-crispy-forms. According to this article, the best way to do e-mail fields is <input type="email" autocapitalize="off" autocorrect="off"> and the best way to do date fields is <input type="date">. None of these attributes are implemented by default with Django and I'm wondering how best to go about implementing them. Here is the relevant code (simplified):
models.py
from django.db import models
class Lead(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField(blank=True, null=True)
initial_contact_date = models.DateField()
class Meta:
ordering = ('name',)
def __unicode__(self):
return self.name
views.py
from django.core.urlresolvers import reverse
from django.views.generic import CreateView, ListView, UpdateView
from .models import Lead
class LeadAdd(CreateView):
model = Lead
def get_context_data(self, **kwargs):
context = super(LeadAdd, self).get_context_data(**kwargs)
context['title'] = 'Add a Lead'
return context
def get_success_url(self):
return reverse('lead_list')
class LeadEdit(LeadAdd, UpdateView):
def get_context_data(self, **kwargs):
context = super(LeadEdit, self).get_context_data(**kwargs)
context['title'] = 'Edit a Lead'
return context
class LeadList(ListView):
model = Lead
urls.py
from django.conf.urls import patterns, url
from .views import *
urlpatterns = patterns('',
url(r'^$', view=LeadList.as_view(), name='lead_list'),
url(r'^add/$', view=LeadAdd.as_view(), name='lead_add'),
url(r'^edit/(?P<pk>[\d]+)/$', view=LeadEdit.as_view(), name='lead_edit'),
)
lead_form.html
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="page-header">
<h1>{{ title }}</h1>
</div>
<form action="." method="post" class="form-horizontal">
{% csrf_token %}
{{ form|crispy}}
<div class="form-actions">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
{% endblock content %}
Use a field template to do this (see the docs on Field):
Field('email', template="custom-email.html")
OR create a custom widget. There is an abstract class you can use, but one of the existing predefined widgets should work:
# widgets.py
from django.forms.widgets import TextInput
class EmailInput(TextInput):
input_type = 'email'
So it might look like this in your view:
class LeadAdd(CreateView):
model = Lead
form_class = LeadAddForm
...
And then that LeadAddForm class would have your custom widget defined:
from . import widgets
LeadAddForm(forms.Form):
email = forms.CharField(
...
widget = widgets.EmailInput,
...
)
Or you can set the widget in the init:
class LeadAddForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(LeadAddForm, self).__init__(*args, **kwargs)
self.fields['email'].widget = widgets.EmailInput()
You should be able to set the extra attributes (autocapitalize="off" autocorrect="off") using the crispy form config:
Field('email', autocapitalize="off", autocorrect="off")
The simpler option:
forms.py:
class LeadForm(forms.ModelForm):
class Meta:
model = Lead
def __init__(self, *args, **kwargs):
super(LeadForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_class = 'form-horizontal'
self.helper.layout = Layout(
Fieldset('Enter the following information',
'name',
Field('email', type="email",
autocapitalize="off",
autocorrect="off"),
Field('initial_contact_date', type="date")),
FormActions(
Submit('save', 'Create Lead'),
Button('cancel', 'Cancel')
)
)
In your views.py:
class LeadAdd(CreateView):
model = Lead
success_url = reverse('lead_list')
form_class = LeadForm
def get_context_data(self, **kwargs):
context = super(LeadAdd, self).get_context_data(**kwargs)
context['title'] = 'Add a Lead'
return context
class LeadEdit(LeadAdd, UpdateView):
def get_context_data(self, **kwargs):
context = super(LeadEdit, self).get_context_data(**kwargs)
context['title'] = 'Edit a Lead'
return context
In your template:
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="page-header">
<h1>{{ title }}</h1>
</div>
{% crispy form %}
{% endblock content %}

Django: Using crispy styled formsets in a class based view

Hi Stackoverflow people,
I would like to style a formset with the crispy app, but it causes some grieve.
A very simple model should be presented four times.
class ItemPicture(models.Model):
item = models.ForeignKey('Item')
picture = ThumbnailerImageField(_('Image'),
upload_to='pictures/', null=True, blank=True,)
The form class is also straightforward:
class ItemPictureForm(forms.ModelForm):
class Meta:
model = ItemPicture
fields = ('picture',)
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Fieldset(_('Add pictures'),'picture', ),
ButtonHolder(
Submit('save', _('Add'), css_class='btn btn-primary '),
Reset('reset', _('Cancel'), css_class='btn')))
super(ItemPictureForm, self).__init__(*args, **kwargs)
In my views.py, I generate the formset:
class ItemUploadPictures(FormView):
ItemPictureFormSet = formset_factory(ItemPictureForm, extra=4)
form_class = ItemPictureFormSet
template_name = 'item_upload_pictures.html'
success_url = reverse_lazy('dashboard')
My trouble is that crispy expects {% crispy formset formset.form.helper %} in the template, but it seems that the passed-through variable is form.
{% crispy form %} works, but no helper attributes will be displayed. How can I pass the entire formset information to the template?
Thank you for your suggestions.
Have you tried
{% crispy formset form.form.helper %}
forms.py
class ItemPictureForm(forms.ModelForm):
class Meta:
model = ItemPicture
fields = ('picture',)
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Fieldset(_('Add pictures'),'picture', ),
# These need to be removed because they cant be prevented from duplicating
# ButtonHolder(
# Submit('save', _('Add'), css_class='btn btn-primary '),
# Reset('reset', _('Cancel'), css_class='btn')
))
super(ItemPictureForm, self).__init__(*args, **kwargs)
manage_pictures.html
{% load crispy_forms_tags i18n %}
<form action="" method="post">
{% csrf_token %}
{% crispy formset formset.form.helper %}
<div class="form-actions">
<input type="submit" name="save" value="{% trans "Add" %}" class="btn btn-primary" id="submit-id-save">
<input type="button" name="reset" value="{% trans "Cancel" %}" class="btn" id="button-id-cancel">
</div>
</form>
{% crispy formset formset.form.helper %}
Maybe this library serves your needs: https://github.com/runekaagaard/django-crispy-forms-fancy-formsets