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 %}
Related
{{ form.firstname|as_crispy_field}}
I want to know how further I can add bootstrap classes to this Django form field and also I want to add a placeholder
thank you
Use FormHelper to add css classes and placeholders to your form. For example if you have the form:
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['pub_date', 'headline', 'content', 'reporter']
Add init function to it:
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Form
class ArticleForm(forms.ModelForm):
[...]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
Field('pub_date', css_class="Your css class" placeholder='Pub Date ...', ),
[...]
)
check Layouts for more informations.
I got two models:
Project:
class Project(Model):
name = CharField(max_length=50)
members = ManyToManyField("accounts.User", through='ProjectUser')
organization = ForeignKey(Organization, related_name="projects", on_delete=CASCADE)
def __str__(self):
return self.name
and Task:
class Task(Model):
task = CharField(max_length=100)
project = ForeignKey(Project, on_delete=CASCADE)
class Meta:
db_table = 'task'
I got a UpdateView class:
class ProjectUpdateView(UpdateView):
form_class = ProjectUpdateForm
template_name = 'projects/project_edit.html'
success_url = reverse_lazy('projects:list')
How can I allow a user to add tasks (through an inline formset) on the same page as where they'd edit a Project instance?
E.g one consolidated form where the user can edit the Project name, and add / remove Task instances, all in one place
Form/Formset:
First, create a form and a formset for your Task model
class TaskForm(ModelForm):
class Meta:
model = Task
fields = ['task']
def __init__(self, *args, **kwargs):
super(TaskForm, self).__init__(*args, **kwargs)
class TaskBaseFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(TaskBaseFormSet, self).__init__(*args, **kwargs)
TaskFormset = inlineformset_factory(
Project, # parent_model
Task, # model
form=TaskForm,
formset=TaskBaseFormSet
)
Or maybe all that you need to do to create a TaskFormset if you dont need a TaskForm class is this
TaskFormset = inlineformset_factory(Project, Task, fields=('task',))
View:
I see you're using a UpdateView class for your view, so you can do this to get a TaskFormset in your context_data, so now you can use the TaskFormset in the template that you declared in the 'template_name' property of your UpdateView class
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['task_formset'] = forms.TaskFormset(self.request.POST)
else:
context['task_formset'] = forms.TaskFormset()
return context
# In the form_valid method of your UpdateView class you can validate the data
# and assign the Project instance to all the tasks that were create by the formsets
def form_valid(self, form):
task_formset = context['task_formset']
# you can validate formset data like this
if not task_formset.is_valid():
return self.form_invalid(form)
project = form.save()
# Here you assign the Project instance to the Tasks
task_formset.instance = project
task_formset.save()
return super().form_valid(form)
Template:
Now all that you need to do is to print the management_form and each form from the formset using a loop as you can see in the code below
<form method="post">
<!-- Your ProjectUpdateForm form here... -->
{{ task_formset.management_form }}
<table>
{% for form in task_formset %}
{{ form }}
{% endfor %}
</table>
</form>
Hope this can help! There are some links to the official Django documentation that you may find useful:
https://docs.djangoproject.com/en/3.1/topics/forms/formsets/#using-a-formset-in-views-and-templates
https://docs.djangoproject.com/en/3.1/topics/forms/modelforms/#inline-formsets
https://docs.djangoproject.com/en/3.1/ref/forms/models/#inlineformset-factory
In our regular Django form we can render the form with something like this {{form.username}} and we specify the widget within the constructor of the form class like name, class, id, label, etc.
Now suppose that I have this form class
class LoginForm(forms.Form):
email = forms.EmailField(widget=forms.EmailInput())
password = forms.CharField(widget=forms.PasswordInput())
class Meta:
fields = ['email', 'password']
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
self.fields['email'].required = True
self.fields['password'].required = True
How can I render it with vuetify components in template?
# example
<v-text-field label="username"></v-text-field>
<v-select></v-select>
# something like that
Thanks you
Looking for the exact same thing, i don't know if you managed to achieve what you wanted or if anybody got some hint.
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.
I'm trying to conditionally set an initial value on a crispy forms InlineRadio field, and am getting ignored.
I set the initial value like so:
class CustomWizardForm3(ModelForm):
def __init__(self, user, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset("",
Div(ImageInlineRadios('sleeve_length'), css_class='span8 clearfix'),
Div(
CustomStyleDiv('armhole_edging_stitch', 'armhole_edging_height'),
css_class="row-fluid")
)
)
super(CustomWizardForm3, self).__init__(user, HELP_TEXT, *args, **kwargs)
if self.instance.is_vest():
self.fields['sleeve_length'].initial = DC.SLEEVE_VEST
class Meta:
model = IndividualDesign
fields = ('armhole_edging_stitch', 'armhole_edging_height', 'sleeve_length',)
In the model, sleeve_length is declared as:
sleeve_length= models.CharField(
max_length = 20,
choices = DC.SLEEVE_LENGTH_CHOICES,
null = True,
blank = True)
I've stepped through it in the debugger, and the initial attribute is set to what I expect. But when the radio buttons are generated, nothing is checked. What gives? Crispy and/or Django Forms is driving me mad.
ETA: ImageInlineRadios is a simple subclass of the standard crispy thing.
class ImageInlineRadios(InlineRadios):
def __init__(self, *args, **kwargs):
self.span_class = kwargs.pop('span_class', 2)
super(ImageInlineRadios, self).__init__(*args, **kwargs)
template = 'radio_buttons.html'
def render(self, form, form_style, context, template_pack='bootstrap'):
import pdb; pdb.set_trace()
context.update({'inline_class': 'inline',
'images': 'none',
'span_class':self.span_class})
return super(ImageInlineRadios, self).render(form, form_style, context)
In the radio_buttons.html template file, we have:
<input type="radio"
{% if choice.0|stringformat:"s" == field.value|stringformat:"s" %} checked="checked"{% endif %}
Does crispy not set field.value from initial? That part looks to me like what's in the radioselect.html template that comes with crispy, but if there's something else I should test, I'd love to know what it is.