I'm trying to imeplement an edit formset. Then, i'm instancing the objects in the formset using modelformset_factory. When the request isn't POST, the formset loads perfectly, but, if the request is POST, the formset constructor raises a MultiValueDictKeyError.
This is my code.
forms.py
class SchoolSectionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Div(
'name',
css_class='name',
),
)
super(SchoolSectionForm, self).__init__(*args, **kwargs)
class Meta:
model = SchoolSection
exclude = [
'school',
'created_by',
]
class SectionBreakFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
super(SectionBreakFormSet, self).__init__(*args, **kwargs)
class SectionBreakForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Div(
Div(
'start_time',
css_class='start_time',
),
Div(
'end_time',
css_class='end_time',
),
css_class='formset_element',
)
)
super(SectionBreakForm, self).__init__(*args, **kwargs)
class Meta:
model = SectionBreak
exclude = [
'school_section',
'created_by',
]
views.py
def school_section_edit(request, section_id):
section = get_object_or_404(
SchoolSection,
id=section_id,
)
current_school = request.user.current_school
school_sections_list = current_school.schoolsection_set.all()
section_break_formset = modelformset_factory(
SectionBreak,
max_num=3,
extra=0,
form=SectionBreakForm,
)
formset_qset = SectionBreak.objects.filter(school_section=section)
formset = section_break_formset(queryset=formset_qset)
school_section_form = SchoolSectionForm(instance=section)
if request.method == 'POST':
school_section_form = SchoolSectionForm(request.POST)
# Bug raises in this line
formset = section_break_formset(request.POST, queryset=formset_qset)
# Bug raises in this line
if school_section_form.is_valid() and formset.is_valid():
school_section_form.save()
formset.save()
messages.success(
request,
u'xxx',
)
return HttpResponseRedirect(reverse('school:school_section_add'))
else:
messages.error(
request,
u'xxx',
)
return render(request, 'school/schoolsection_add.html', {
'school_section_form': school_section_form,
'formset': formset,
'school_sections_list': school_sections_list,
})
template
<form class="new_section_form" method="post" action="">
<div class="school_section_form">
{% crispy school_section_form %}
</div>
<h3>Horarios de descanso:</h3>
<div class="section_break_formset">
{% crispy formset formset.form.helper %}
</div>
<button class="button color">guardar</button>
</form>
When i post the form... crashh.... i have this error
thanks for help...
Exception Type: MultiValueDictKeyError at /administrador/ciclo-educativo/editar/34/
Exception Value: "Key u'form-0-id' not found in <>QueryDict: {u'name': [u'Primaria'], u'form-MAX_NUM_FORMS': [u'3'], u'form-TOTAL_FORMS': [u'1'], u'form-0-start_time': [u'07:00:00'], u'form-0-end_time': [u'12:00:00'], u'form-INITIAL_FORMS': [u'1'], u'csrfmiddlewaretoken': [u'aZkZPJ6tlzJeCd1kjC0EpkjPuFbWe6IB', u'aZkZPJ6tlzJeCd1kjC0EpkjPuFbWe6IB']}<>"
You might need to add the form id {{ form.id }} e.g.
{% crispy formset formset.form.id %}
Form id is hidden field but if you not include it in form in your template then it will through the above error.
if include it in your template you will get rid of above error
Ex:
{{form.title}} #this field you want to display
{{ form.id }} # this is hidden field, this will not get display in form. but at the time of form-set submission this is required.
Related
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
I have a simple form witch ModelChoiceField. This is a part of my view.py file:
def premium(request, id):
context = {}
try:
site = Site.objects.get(id=id)
except Site.DoesNotExist:
raise Http404("Nie ma takiej strony")
if request.method == 'POST':
premium_form = PremiumForm(request.POST)
if premium_form.is_valid():
# group = Group.objects.get(id=request.POST["kod"])
print('OK')
else:
print('NOT OK')
else:
premium_form = PremiumForm(site)
premium_form.fields['group'].queryset =
premium_form.fields['group'].queryset.exclude(group_name=site.group)
context['site'] = site
context['form'] = premium_form
context['category'] = site.category
context['subcategory'] = site.subcategory
return render(request, 'mainapp/premium.html', context)
This is my form:
class PremiumForm(forms.Form):
def __init__(self, site, *args, **kwargs):
super(PremiumForm, self).__init__(*args, **kwargs)
self.fields['group'].initial = 2
self.fields['group'].empty_label = None
group = forms.ModelChoiceField(
queryset=Group.objects.filter(is_active=True),
help_text="<div id='group'></div>",
label="Some text",
required=False)
My premium.html file:
<form method="post" action="" class="form-horizontal">
{% csrf_token %}
{% bootstrap_form form layout='horizontal'%} <br>
{% bootstrap_button "Submit" size='large' button_type="submit" button_class="btn-primary btn-main-add" %}
</form>
When I press "Submit" button I get "NOT OK". I can't resolve this problem. I don't have any idea how to validate forms.ModelChoiceField. Thanks for any help.
Form should be initialised with kwargs:
premium_form = PremiumForm(site=site)
And inside init:
def __init__(self, *args, **kwargs):
site = kwargs['site']
However, site is not used inside form initialization so you can just remove it and it will solve the issue.
I'd like to display the image from an ImageField. I'm using Django crispy forms. It seems I need to use the HTML layout helper, but I'm not sure how to access a template variable here.
The following renders a blank image tag:
HTML('<img src="{{ logo.url }}" />')
Am I referencing this wrong, or is there a better method in crispy forms?
Here is the full code so you can get a better idea:
forms.py
class CompanyForm(forms.Form):
name = forms.CharField(required=False, label="Company name")
logo = forms.ImageField(required=False, label="Logo")
# form layout
helper = FormHelper()
helper.form_class = 'form-horizontal'
helper.layout = Layout(
Div(
'name',
HTML('<img src="{{ logo.url }}" />'),
css_class='span4',
),
FormActions(
Submit('submit', 'Save', css_class="btn-primary pull-right"),
)
)
models.py
class Company(models.Model):
name = models.CharField(max_length=100)
logo = models.ImageField(upload_to = 'logos/', null=True)
views.py
I'm setting the initial values of the form in my view:
...
profile = Company.objects.get(name="foo")
form = CompanyForm(initial={'name': profile.name, 'logo': profile.logo, })
context = {
'profile_form' : form,
}
return render_to_response('dashboard/company-profile.html', context_instance=RequestContext(request, context))
company-profile.html
{% load crispy_forms_tags %}
<div class="row">
{% crispy profile_form %}
</div>
Funny, I was just working on the same issue today on a Inline image upload formset, I ended up with this implementation:
class ImageModelForm(forms.ModelForm):
class Meta:
model = ImageModel
def __init__(self, *args, **kwargs):
super(ImageModelForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_tag = False
self.helper.disable_csrf = True
self.helper.layout = Layout(
'title',
'description',
'imagefile',
HTML("""{% if form.imagefile.value %}<img class="img-responsive" src="{{ MEDIA_URL }}{{ form.imagefile.value }}">{% endif %}""", ),
'flag_featured',
)
People who are seeing this from-after 2020, the this is fixed since maybe v1.10, v.1.9 or nearby.
I've had same issue with v.1.8.
further to #petkostas answer, specifically on the image reload comment from #Banjer:
- i also had this problem with a form and managed to solve this by introducing a conditional statement into views.py (note, context statements not included below):
if request.method == "POST":
if form.is_valid():
#do stuff
return HttpResponseRedirect(reverse('your_named_url'))
elif request.method =="GET":
return render(request, 'path/to/template.html', context)
what this does is allows the page to reload with the new content if it's updated (so you can use the form.imagefile.value attribute mentioned previously)
! this is quite a specific use case, but i hope it helps someone save a bit of time
I want to use the same model for two forms and change the labels of field how can i change the label?
this is my one form:
class jobpostForm(forms.ModelForm):
class Meta:
model = jobpost
fields = ('job_type','title','company_name','location','country','description','start_date','end_date','how_to_apply')
widgets = {
'job_type':RadioSelect(),
'location':TextInput(attrs={'size':'70','cols': 10, 'rows': 20}),
'description': TinyMCE(attrs={'cols':'100', 'row': '80'}),
'start_date':AdminDateWidget(attrs={'readonly':'readonly'}),
'end_date':AdminDateWidget(attrs={'readonly':'readonly'}),
'how_to_apply':RadioSelect(),
}
def __init__(self, *args, **kwargs):
super(jobpostForm, self).__init__(*args, **kwargs)
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'))
super(jobpostForm, self).__init__(*args, **kwargs)
like i want to change 'location' to 'Job Location'..how can i do this?
This problem is not really specific for Django Crispy Forms.
One option is to set the label in the init() method of your JobPostForm.
def __init__(self, *args, **kwargs):
super(JobPostForm, self).__init__(*args, **kwargs)
self.fields['location'].label = "Job Location"
A good read when dealing with these kind of questions is Overloading Django Form Fields.
There is an easier way to do this. See my example below:
class CreateAPIKey(forms.ModelForm):
class Meta:
model = APIKey
fields = ["client_id"]
labels = {
"client_id": "Nome da Key",
}
help_texts = {
"client_id": _(
"Um identificador exclusivo de formato livre da chave. 50 caracteres no máximo"
),
}
widgets = {
"client_id": forms.TextInput(
attrs={
"id": "key_id",
"required": True,
"placeholder": "Entre um nome único para a chave",
"label": "dasdasdsdasd",
}
),
}
Then, then rendering the form on the template:
<form class="" method="POST" action="#">
{% csrf_token %}
<div class="col-md-4 col-sm-12 form-group" data-children-count="1">
{% for field in form %}
{{ field|as_crispy_field }}
{% endfor %}
</div>
</form>
RESULT:
In my case calling the __init__ on my forms.py file worked
CODE FORMAT:
def __init__(self, *args, **kwargs):
super(ChangeFormNameHere, self).__init__(*args, **kwargs)
self.fields['model_filed_default_name'].label = "Your_new_label_name"