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.
Related
I'm using Csripy Forms to render a formset and I'd like to use a helper to modify the forms themselves.
How do I pass both the form helper and the formset helper to the crispy tag?
My form
class ModelForm(ModelForm):
class Meta:
model = MyModel
exclude = ['key']
labels = {
'name': 'Stage',
}
The formset
class ModelFormSetHelper(FormHelper):
# see https://django-crispy-forms.readthedocs.io/en/latest/layouts.html
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_tag = False
self.layout = Layout(
Row(
Column('name', css_class='col'),
Column('conversion_rate', css_class='col-md-3 col-4'),
css_class='formset-row'
)
)
self.disable_csrf = True
My tag
{% crispy mymodel_formset formset_helper %}
I'm having an issue using several forms with django-crispy-forms for Django.
From the documentation, we must set self.helper.form_tag = False in out Form. Documentation here
Then wrapp the forms with a Form tag in the HTML.
class SearchForm(forms.Form):
X = forms.IntegerField(label='X', min_value=0, max_value=10)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper
self.helper.form_method = 'post'
self.helper.form_tag = False
self.helper.layout = Layout(
'X',
Submit('submit', 'Submit', css_class='btn-success')
)
class PredictForm(forms.Form):
Y = forms.IntegerField(label='X', min_value=0, max_value=10)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper
self.helper.form_method = 'post'
self.helper.form_tag = False
self.helper.layout = Layout(
'Y',
Submit('submit', 'Submit', css_class='btn-success')
)
Then in the HTML file I have:
<form action="my_action" class="uniForm" method="post">
<div id="box" class="box">{% crispy formSearch formSearch.helper %}</div>
<div id="box2" class="box2">{% crispy formPrediction formPrediction.helper %}</div>
</form>
In my View.py:
def index(request):
if request.method == 'POST':
formSearch = SearchForm(request.POST)
formPrediction = PredictionForm(request.POST)
# Do stuff
else
formSearch = SearchForm()
formSearch.fields['X'].initial = 5
formPrediction = PredictionForm()
formSearch.fields['Y'].initial = 5
return render(request, 'index.html', {'formSearch': formSearch}, 'formPrediction': formPrediction)
What I get is 2 crispy forms that are displayed on my web page, but the first forms tries to load the fields of the seconds forms. It seems like my second form is duplicated.
I get this error:
KeyError: "Key 'Y' not found in 'SearchForm'. Choices are: X."
It is trying to get data from PredictForm but is aware that only X is in SearchForm. I thought it was I typo somewhere, but I can't find my mistake.
Here is the answer:
A collision war made between the 2 forms, why? Because I did not initiate properly each form functions.
I have changed
self.helper = FormHelper
to
self.helper = FormHelper(self)
I want to be able to vary the placeholder like so:
<input placeholder=" {{ input.placeholder }}">
Where input is a model with the "placeholder" field. The placeholder field will vary since I'll be using a formset, and each placeholder will vary.
Here's my modelForm
class Value(forms.ModelForm):
class Meta:
model = ValueModel
fields = ['value_text']
widgets = {
'value_text': forms.TextInput(attrs={'class': 'form-control'})
and my modelformset
values_formset = modelformset_factory(model=ValueModel, extra=0, form=Value)
I've tried
class Value(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(Value, self).__init__(*args, **kwargs)
self.fields['value_text'].widget.attrs['placeholder'] = self.fields['placeholder']
class Meta:
model = ValueModel
fields = ['value_text', 'placeholder']
widgets = {
'value_text': forms.TextInput(attrs={'class': 'form-control'})
And other attempts at trying to modify the self.fields with no success.
Edit: The relevant part of my views.py:
def page_view(request, values_id):
values_form = values_formset(queryset=ValueModel.objects.filter(
values_id=values_id))
context = {'value': values_form}
return render(request, 'view.html', context)
My template view:
{{ value.management_form }}
{% for form in value %}
{{ form.id }}
{{ form.value_text }}
{% endfor %}
self.fields['placeholder'] refers to a form field object, not a value; you couldn't use it as a placeholder. But it seems like what you want is to use the value of the model instance.
def __init__(self, *args, **kwargs):
super(Value, self).__init__(*args, **kwargs)
self.fields['value_text'].widget.attrs['placeholder'] = self.instance.placeholder
When I read the documentation about modelforms and widgets it looks like you can use any widget on any modelform but that there are certain default widgets used for form fields corresponding to modelform fields. I'm able to render a radio input using a form field but not with a modelform field.
I have tried many different things, but I just can't seem to render a RadioSelect widget of a modelform field which comes from a model field. Is this even possible?
Btw, my goal is to let the initial value of the radio input correspond with the current value of the model field(A boolean).
Attempt 1:
# views.py
class SettingsView(FormView):
template_name = 'settings.html'
success_url = 'settings/saved/'
form_class = NicknameForm
def post(self, request, *args, **kwargs):
profile = request.user.get_profile()
if request.POST['show_nickname'] == 'False':
profile.show_nickname = False
profile.save()
elif request.POST['show_nickname'] == 'True':
profile.show_nickname = True
profile.save()
return super(NicknameFormView, self).post(request, *args, **kwargs)
def get_context_data(self, **kwargs):
"""
To be able to use 'show_nickname_form' instead of plain 'form' in the template.
"""
context = super(NicknameFormView, self).get_context_data(**kwargs)
context["show_nickname_form"] = context.get('form')
return context
# models.py
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User,
unique=True,
verbose_name='user',
related_name='profile')
show_nickname = models.BooleanField(default=True)
# forms.py
from django import forms
from models import Profile
CHOICES = (('shows_nickname', 'Yes'), ('hides_nickname', 'No'))
class NicknameForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('show_nickname',)
widgets = {
'show_nickname': forms.RadioSelect(attrs={'choices': CHOICES}),
}
Part from my template:
<form action='' method="post">
{{ show_nickname_form.as_ul }} {% csrf_token %}
<input type="submit" value="Save setttings">
</form>
The form that is rendered from {{ show_nickname_form.as_ul }}:
<li><label for="id_show_nickname_0">show nickname:</label>
<ul></ul>
</li>
<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='1BqD6HJbP5e01NVwLtmFBqhhu3Y1fiOw' /></div>`
Attempt 2:
# forms.py
from django import forms
from models import Profile
class NicknameForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('show_nickname',)
widgets = {
'show_nickname': forms.RadioSelect(),
}
Attempt 3
# forms.py
CHOICES = ((True, 'On',),
(False, 'Off',))
class NicknameForm(ModelForm):
show_nickname = ChoiceField(widget=RadioSelect, choices=CHOICES, initial=True , label='')
class Meta:
model = Profile
fields = ('show_nickname',)
This renders the radio input fine but I need it to take the initial value of the corresponding model field show_nickname instead of the constant True.
I am using Django 1.4 btw.
You need to make it into a ChoiceField instead of a BooleanField with a choice for each and a RadioWidget in order for it to display radio buttons.
https://docs.djangoproject.com/en/dev/ref/forms/fields/#django.forms.ChoiceField
If you want to keep the boolean field, you will most likely have to do some hacking to create your own field/widget.
# views.py
class SettingsView(FormView):
template_name = 'settings.html'
success_url = 'settings/saved/'
form_class = NicknameForm
def get_form(self, form_class):
"""
Returns an instance of the form to be used in this view.
"""
form = super(SettingsView, self).get_form(form_class)
if 'show_nickname' in form.fields:
profile = self.request.user.get_profile()
form.fields['show_nickname'].initial = profile.show_nickname
return form
def post(self, request, *args, **kwargs):
profile = request.user.get_profile()
if request.POST['show_nickname'] == 'False':
profile.show_nickname = False
profile.save()
elif request.POST['show_nickname'] == 'True':
profile.show_nickname = True
profile.save()
return super(NicknameFormView, self).post(request, *args, **kwargs)
def get_context_data(self, **kwargs):
"""
To be able to use 'show_nickname_form' instead of plain 'form' in the template.
"""
context = super(NicknameFormView, self).get_context_data(**kwargs)
context["show_nickname_form"] = context.get('form')
return context
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 %}