This has really stumped me. I can write forms OK in django but want to use crispy-forms bootstrap3. I can get the forms to render using this form code:
class NewGuestForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(NewGuestForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_id = 'id-newGuestForm'
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-lg-2'
self.helper.field_class = 'col-lg-6'
self.helper.form_method = 'post'
self.helper.form_action = 'guest-list'
self.helper.layout = Layout(
Fieldset (
'New Guest',
'first_name',
'last_name',
'num_child',
'landline',
'mobile',
'email'
),
FormActions(
Submit('save', 'Save changes',css_class='btn-primary'),
Button('cancel', 'Cancel')
)
)
class Meta:
model = Guest
class BookingForm(forms.Form):
class Meta:
model = Booking
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_method = 'POST'
self.helper.form_id = 'add-booking'
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-lg-2'
self.helper.field_class = 'col-lg-6'
self.helper.layout = Layout(
Fieldset(
'',
'guest',
'guest_status',
'start_date',
'end_date',
'dep_recd',
'bkd_child',
'bkd_adult',
'bal_recd',
'sec_recd',
'keys_sent',
'sec_retn',
'notes'
),
FormActions(
Submit('submit', "Save changes"),
Submit('cancel', "Cancel"),
)
)
super(BookingForm, self).__init__(*args, **kwargs)
This renders the form OK, but when I click 'submit' the browser goes white. The return (form_action) is correctly shown in the address bar, but isn't loaded. The data is not written to the database. The form renders with only the fields I need.
My view is:
class NewGuestView(CreateView):
model = Guest
template_name = 'new_guest.html'
form_class = NewGuestForm
If I change 'form_class' to 'form' the form renders with all fields and ignores the bootstrap column instructions. Also, the 'submit' and 'cancel' buttons do not appear.
I must be doing something wrong, but can't for the life of me see what. Any suggestions gratefully received.
This is probably a bit late for you, but I've been looking at django-crispy-forms for the first time today and ran into the same problem as you. If I have the form_action defined, then upon submission of the form the browser is redirected to the correct url, but the page is blank - even upon refreshing. This also happens whether the form is valid or not, so clearly this is a pretty fundamental problem and there must be something we're both doing wrong.
I got around this by using the success_url attribute of the view. So you could have tried:
from django.core.urlresolvers import reverse_lazy
class NewGuestView(CreateView):
...
success_url = reverse_lazy("guest-list")
As for the buttons, I haven't yet gotten to defining the layout and have used this approach:
self.helper.add_input(Submit('submit', 'Submit'))
self.helper.add_input(Button('cancel', 'Cancel'))
Although it's worth noting that the 'Cancel' button doesn't actually do anything at this stage, I'll need to look into that further.
Did you manage to get this working or find another way around?
Update:
The redirect fails with a 405 Method Not Allowed error. I tried defining the post() method in my view as per this SO question and this solves the HTTP error, but doesn't process the data (a new record isn't saved and validation errors are not caught). So I'm sticking with the success_url method until I can find out what I'm doing wrong.
Resolved now. I had not realised that I needed to add the fields to the class Meta as well as listing them in Layout. I added the following
class Meta:
model = Guest
fields = ['first_name', 'last_name', 'email', 'landline', 'mobile']
Now the form saves OK.
Related
I am using a modelformset_factory in Django to have a user fill out an unknown number of fields.
I have made the fields required but in the HTML rendering Django does not add required to the HTML element, looking around online this seems to be a common issue but I have not seen any valid answers that apply for what I want and I feel like this should be simple.
How do I make Django add the required tag to appropriate HTML elements for a Formset?
class ItemForm(forms.ModelForm):
class Media:
js = (formset_js_path,)
class Meta:
model = PurchaseOrderItems
fields = ['name', 'vendor', 'quantity', 'price', 'description']
labels = {
'name': 'Item',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# self.fields['description'] .widget.attrs['required'] = 'required'
self.empty_permitted = False
self.fields['description'] = forms.CharField(
required=False,
label='Description',
)
def clean(self):
"""
:return:
"""
cleaned_data = super().clean()
# print(cleaned_data)
ItemFormSet = modelformset_factory(
PurchaseOrderItems,
form=ItemForm,
extra=0,
min_num=1,
validate_min=True,
can_delete=True,
)
Here is the HTML rendered for the name field, no required yet in my model it certainly is, so if this form is submitted I get DB errors because of empty values:
<input type="text" name="form-0-name" maxlength="150" class="form-control" id="id_form-0-name">
According to the release notes:
Required form fields now have the required HTML attribute. Set the new
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.
So if you want to disable, from your view you must submit the form in this way. This will affect all your fields.
form = ItemForm(use_required_attribute=False)
However, if you only want to affect some you must do the previous step and also in your form add this
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({'required': ''})
self.fields['vendor'].widget.attrs.update({'required': ''})
self.fields['quantity'].widget.attrs.update({'required': ''})
self.fields['price'].widget.attrs.update({'required': ''})
self.fields['description'].widget.attrs.update({'required': 'False'})
On the other hand I see that you are not using widgets in your form you should also use them to make it work.
widgets = {
'name': forms.TextInput(),
'vendor': forms.TextInput(),
'quantity': forms.TextInput(),
'price': forms.TextInput(),
'description': forms.TextInput(),
}
I put all the fields as TextInput, but look for what is indicated according to the type of data here Field types.
I have a contact form on my Django site and when submitted it goes to the success url but the email is not sent, and the logging set up in the form_valid function is never called.
Here is the code for the view:
class ContactView(FormView):
form_class = ContactForm
template_name = "contact.html"
success_url = "/contact-sent/"
def form_valid(self, form):
message = "{name} / {email} said: ".format(
name=form.cleaned_data.get('name'),
email=form.cleaned_data.get('email'))
message += "\n\n{0}".format(form.cleaned_data.get('message'))
recipients = [recipient for recipient in settings.LIST_OF_EMAIL_RECIPIENTS]
try:
send_mail(
subject=form.cleaned_data.get('subject').strip(),
message=message,
from_email='XXX#XXX.com'
recipient_list=recipients,
)
logger = logging.getLogger(__name__)
logger.info("Contact Email sent successfully")
except Exception as e:
logger = logging.getLogger(__name__)
logger.warning("Contact Email failed to send\nInfo: %s" % e)
return super(ContactView, self).form_valid(form)
and the form, which is a model form using floppyforms and crispyforms:
class ContactForm(ffuture.ModelForm):
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'id-contactForm'
self.helper.form_class = 'contact-form'
self.helper.form_method = 'post'
self.helper.form_action = 'submit-feedback'
self.helper.form_tag = True
self.helper.layout = Layout(
Fieldset(
_('Contact Us'),
Field('name', placeholder=_('Name'), css_class='input-medium'),
Field('email', placeholder=_('Email'), css_class='input-xlarge'),
Field('subject', placeholder=_('Subject'), css_class='input-xlarge'),
Field('message', placeholder=_('Add a message'), rows='5', css_class='input-xlarge'),
),
)
self.helper.add_input(Submit('submit', _('Submit')))
class Meta:
model = Feedback
fields = ('name', 'email', 'subject', 'message')
and the model:
#python_2_unicode_compatible
class Feedback(models.Model):
subject = models.CharField(max_length=100)
message = models.TextField(max_length=500)
name = models.CharField(max_length=100)
email = models.EmailField()
creation_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.topic
class Meta:
verbose_name_plural = _("Feedback")
verbose_name = _("Feedback")
The emails are never sent, and the Feedback model is never updated in the admin.
Anyone have any ideas as to why this would be happening? I've been pouring over the code and looking at other examples and none of them seem much different from what I have. I am stumped as to why it is not sending the emails, nor calling any of the logging in the form_valid method.
Ideally I want it to send the contact email to the recipients, and also save the information entered into the Feedback model.
Two other things that may be relevant:
The site is currently running on Apache and for the from_email set in the view I never configured any credentials for it. I am unsure of where to do this. But even if that's the reason the email is not being sent, I don't see why the Feedback model would not be updated.
Thanks for any help you guys can provide, I've been stuck on this for a bit now.
EDIT:
I was thinking it could be the send_mail function that is the issue, but I added logging above the try block and that wasn't called either, so I am now sure that the form_valid method is never being called.
As for the Feedback model not being saved, I realized this is probably because I am never actually saving the form.
I am a bit confused here, because I am using a model form for the contact so the user submitting the form is not logged in. The objective was to both send the email, and store the results in the database. But I can't seem to figure out how I should go about saving the modelform without a valid user.
Would it be enough to just do
feedback = form.save()
inside my form_valid method in the ContactView? Or do I want a save method inside my model form?
The solution was to just call
form.save()
and store the model. The user being logged in did not matter as the fields on the model don't reference a logged in user at all.
I am currently working on a Django project and am using cripsy-forms to format and display the HTML forms in conjunction with Bootstrap. If I allow Crispy-Forms to use the default layout order, it will (for the most part) format the correct HTML layout to utilize inline forms with Bootstrap.
In order to rearrange the order of the fields on the form, I am attempting to utilize the Layout helper. When I add the Layout helper, Crispy-Forms loses it's inline labeling ability and all labels are displayed above their field counterparts.
How can I get Crispy Forms to re-order the layout and still retain Bootstrap inline formatting?
class MyEntryForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyEntryForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_id = 'id-MyEntryForm'
self.helper.help_text_inline = True
self.helper.error_text_inline = True
self.form_show_labels = True
self.helper.form_method = 'post'
self.helper.form_action = 'submit'
self.helper.form_tag = False
self.helper.add_input(Submit('submit', 'Submit'))
self.helper.form_class = 'form-inline'
self.helper.field_template = 'bootstrap3/layout/inline_field.html'
self.helper.layout = Layout(
'field1',
'field3',
'field2',
)
Have you tried using the fields attribute? The generated Form class will have a form field in the order specified in the fields attribute.
class MyEntryForm(ModelForm):
class Meta:
model = "mymodel"
fields = ['field1', 'field3', 'field2']
I have a modelform with 2 buttons and i want to perform different functionality on them.
My modelform:
class jobpostForm(ModelForm):
class Meta:
model = jobpost
fields = ('job_title','job_type','job_location','job_description','start_date','end_date','country','how_to_apply')
widgets = {
'job_type':RadioSelect(),
'job_location':TextInput(attrs={'size':'70'}),
'job_description':Textarea(attrs={'cols':200, 'rows':10}),
'start_date':TextInput(attrs={
'class': 'datepicker',
'data-date-format': 'mm/dd/yyyy',
}),
'end_date':TextInput(attrs={
'class': 'datepicker',
'data-date-format': 'mm/dd/yyyy',
}),
}
def __init__(self, *args, **kwargs):
#super(jobpostForm, self).__init__(*args, **kwargs)
#self.fields['start_date'].widget.attrs['class'] = 'datepicker'
super(jobpostForm, self).__init__(*args, **kwargs)
#self.fields['ref_id'].widget = forms.HiddenInput()
self.helper = FormHelper()
self.helper.form_class = 'horizontal-form'
self.helper.form_id = 'id-jobpostform'
self.helper.form_class = 'blueForms'
self.helper.form_method = 'post'
self.helper.form_action = '/portal/next/post/'
self.helper.add_input(Submit('submit_addcontent', 'Preview'))
self.helper.add_input(Submit('submit_addcontent', 'Submit'))
super(jobpostForm, self).__init__(*args, **kwargs)
I want to perform different functionality on submit and preview.How can i access them in my view?
A django form really handles two things:
Displaying the intitial form on a GET request
Processing POST requests with data
You can approach your situation in multiple ways. One way would be to have both buttons submit your form. The preview button would fill in a hidden field named preview. Your form would process the submitted data. If the data included a value in the POST field named preview it would render a preview. Otherwise, it would process the form normally.
I've been running into crispy form, and it seems to do exactly what I want: render forms with bootstrap layout.
Now, the example talk about using forms.Form. This is ok, I can create mine by writing the code like this:
class TemplateCreateForm(forms.Form):
title = forms.CharField(label=(u'Task name'))
description = forms.CharField(label=(u'Task description'))
url_start = forms.CharField(label=(u'Start page url'))
url_end = forms.CharField(label=(u'Final page url'))
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.add_input(Submit('submit', 'Submit'))
super(TemplateCreateForm, self).__init__(*args, **kwargs)
But, how to do the update? because if I put this in the view:
form = TemplateCreateForm(request.POST or None, instance=template)
it does not work because instance is only for ModelForm.
Now, can I substitute the forms.Form with ModelForm and use crispy form for ModelForm?
I did this
class TemplateCreateForm(ModelForm):
title = forms.CharField(label=(u'Task name'))
description = forms.CharField(label=(u'Task description'))
url_start = forms.CharField(label=(u'Start page url'))
url_end = forms.CharField(label=(u'Final page url'))
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.add_input(Submit('submit', 'Submit'))
super(TemplateCreateForm, self).__init__(*args, **kwargs)
class Meta:
model = Template
exclude = ('user')
Here I added the Meta class.
Now: it works, but is it correct to use it like this?
The update works as well in this way.
What's the correct way to use forms for doing the update?
I'm the lead developer of django-crispy-forms. I'm not sure I follow your question as it's a bit poorly formatted. What exactly are you trying to do?
django-crispy-forms does work with ModelForms, the same way as with simple forms. It sits on top of Django, so it doesn't mess with it. It only controls your form rendering, but doesn't change how validation works, how to create form instances and so on.
EDIT:
I'm adding an example on how to do a ModelForm with crispy-forms.
class ExampleModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ExampleModelForm, self).__init__(*args, **kwargs)
# If you pass FormHelper constructor a form instance
# It builds a default layout with all its fields
self.helper = FormHelper(self)
# You can dynamically adjust your layout
self.helper.layout.append(Submit('save', 'save'))
class Meta:
model = ExampleModel
I believe your first problem is that you were subclassing forms.Form instead of forms.ModelForm. That's why I said that your problem was Django related, not crispy-forms related.
Later in your view:
form = ExampleModelForm()
In your template:
{% load crispy_forms_tags %}
{% crispy form %}