The Docs says, to display the form fields in template, i need to do this:
<form action="/search_for_book/">{% csrf_token %}
{{form.as_p}}
<input type="submit" value="Suchen" />
</form>
i did this. and this is my views.
def addbook(request):
form = AddBook()
return render(request,'searchbook.html',{'form':form})
this is my form. defined in forms.py and imported into views.py
class AddBook(forms.Form):
titel = forms.TextInput()
author = forms.TextInput()
why dont Form fields show up in template? I did almost the 1:1 copy from docs. Template shows only the button Suchen and nothing else. Docs doesnot say anything about Urlconf, do i have to do also?
The reason why is that you're defining a widget, which is merely a representation of a HTML input element, instead of defining a field in your forms.py
Do the following:
class AddBook(forms.Form):
titel = forms.CharField(widget=forms.TextInput())
author = forms.CharField(widget=forms.TextInput())
PS: don't blame django's docs - they are really excellent!
For the referencce on widgets, read here: https://docs.djangoproject.com/en/dev/ref/forms/widgets/
For the reference on fields, read here: https://docs.djangoproject.com/en/dev/ref/forms/fields/
Related
I have a django template in which I'm dynamically rendering multiple fields (using ajax)
Below is a Django form (which has been rendered in a template) whose fields have same names. I want to use the cleaned_data method to clean form data in views.py before storing them in the database.
index.html
<div class="form-container">
<!-- ASSUMING I HAVE ALREADY ADDED FIELDS DYNAMICALLY -->
<form id = "orderForm" action="newPickupOrder/" method="post" name="processForm">
<input type='text' name='this_field'>
<input type='text' name='this_field'>
<button type="submit">Submit</button>
</form>
</div>
<form id="addItemForm">
{% csrf_token %}
<!-- BUTTON TO ADD MORE FIELDS DYNAMICALLY -->
<button id = "addItemButton">Add item</button>
</form>
<script>
var addItemButton = document.querySelector('#addItemButton');
addItemButton.onclick = function(){
$.ajax({
type: 'POST',
url: 'addItem/',
data: addItemForm.serialize(),
success: function (response) {
$("#orderForm").append(response);
console.log('Success');
},
error: function (response) {
console.log('Error = '+response);
}
});
};
</script>
forms.py
class ItemForm(forms.Form):
this_field = forms.CharField()
urls.py
urlpatterns = [
url(r'^newPickupOrder/$', views.pickup_order_view, name='new_pickup_order'),
]
views.py
def add_item(request):
if request.method == 'POST':
itemForm = ItemForm()
return HttpResponse(itemForm.as_p())
def pickup_order_view(request):
if request.method == 'POST':
form = ItemForm(request.POST)
same_name_fields = request.POST.getlist('this_field')
# WANT TO CLEAN DATA IN same_name_fields
if form.is_valid():
print(form.cleaned_data)
# ONLY PRINTS THE LAST FIELD's DATA
return HttpResponseRedirect('/viewPickupRequests')
The problem I'm facing is that if I use form.cleaned_data['this_field'], only the last field's data is fetched i.e. in this example, the field with value anotherTestValue is fetched and cleaned. If I fetch the data using request.POST.getlist('this_field'), all the fields' data is fetched and stored as a list, but, I don't know how to clean it using cleaned_data method. Is there a way to apply the cleaned_data method to the list of field data?
I'm sorry, I can't test if this works so this is not really an answer - but the comment system is not suitable for larger code chunks so I'm posting here.
Django forms lack a field type that renders to multiple text inputs with the same name. The proper thing to do would be to write a new form field class and a new widget. Since you are not rendering the form in the template (you are using it only for validation) I will omit the widget part.
class AcceptAnythingMultipleChoiceField(forms.MultipleChoiceField):
def validate(self, value):
if self.required and not value:
raise ValidationError(
self.error_messages['required'],
code='required'
)
Then use this field class instead of forms.CharField() (you may need to pass an empty choices parameter).
[update]
So essentially what you're saying is that I need to create new form field class and then render it to the template each time the user wants to add a new field? What if user has to add 15 fields, I'll need to create 15 classes then! I think this method won't be suitable in scenarios where number of fields required to be generated is large. I feel there should be some elegant way to do this which i'm not aware of – The OP
No, it is not what I'm saying. You probably want to subclass something like MultipleHiddenInput and set AcceptAnythingMultipleChoiceField.widget to it. You will have to create a new template based on the template for MultipleHiddenInput and replace input type="hidden" for type="text" (the original template is django/forms/widgets/multiple_hidden.html).
class AcceptAnythingWidget(MultipleHiddenInput):
template_name = 'django/forms/widgets/multiple_visible.html'
class AcceptAnythingMultipleChoiceField(forms.MultipleChoiceField):
widget = AcceptAnythingWidget
def validate(self, value):
if self.required and not value:
raise ValidationError(
self.error_messages['required'],
code='required'
)
This should render as many <input name='this_field'> as needed for instantiated forms at the frontend if you use:
{{ form.this_field }}
in the template, but will not add/remove them dynamically.
In order to do that you must plug in the JavaScript required to add/remove inputs dynamically in the widget but I will left this as an exercise for you. Look at Form Assets (the Media class) in the docs in order to figure out how to do that.
I think that what you are looking for is formsets. https://docs.djangoproject.com/en/2.0/topics/forms/formsets/
from django.forms import formset_factory
ItemFormSet = formset_factory(ItemForm, extra=2)
You can the essentialy use ItemFormSet in the way you would use a normal form except that this objects is iterable.
You will also have to change your jquery if you want to dynamically add items. There are many examples online on how to do this. In short what you do is
clone one of the forms in the formset
clear all the values from the copied form
update the input's (prefixes of) id's
Using Formsets doesn't solve the problem of fetching and validating
fields with same name. The issue still remains
It does however generate the end result you wanted (see below). My question would be why you need to have inputs with the same name? If there is some jquery stuff that uses these names I dont see any reason why you wouldn't be able to use name like... or assign a class to the inputs instead.
def pickup_order_view(request):
if request.method == 'GET':
ItemFormSet = formset_factory(ItemForm, extra=5)
item_formset = ItemFormSet()
template = "some_template.html"
template_context = {'item_formset': item_formset}
return render(request, template, template_context)
if request.method == 'POST':
ItemFormSet = formset_factory(ItemForm)
item_formset = ItemFormSet(request.POST)
same_name_fields=[]
if item_formset.is_valid():
for item_form in item_formset:
same_name_fields.append(item_form.cleaned_data['this_field'])
print(same_name_fields)
Template
<form id = "orderForm" action="newPickupOrder/" method="post" name="processForm">
{% csrf_token %}
{{ item_formset.management_form }}
{{ for item_form in item_formset }}
{{ item_form.as_p }}
{{ endfor }}
<input type='submit' value='submit'>
</form>
Go to newPickupOrder/ , fill in the 5 fields, hit submit, and watch it print your list.
How does one correctly override the styling of a single item in Django's model form {{form}} call. I cant seem to just overide the defualt behaviour and add my necessary style.
Specifically, I would like to add a simple slider that passes through some text to the view.
I have added an extra field to my form as follows:
class CreateTestForm(forms.ModelForm):
difficulty = forms.CharField()
class Meta:
model = Test
And the following in my html directly:
<form method="POST" action=""> {% csrf_token %}
{{ form|crispy }}
<strong>Difficulty</strong>
<input id="difficulty" type="text" value="" class="slider form-control" data-slider-min="0" data-slider-max="10"
data-slider-step="1" data-slider-value="[0,10]" data-slider-orientation="horizontal"
data-slider-selection="before" data-slider-tooltip="show" data-slider-id="blue">
</br>
<input type='submit' value='Create' class='btn'>
</form>
However, when I render the view, I get two difficulties inputs (one slider and one box). I understand that Django is creating the text box for me, but I assumed, giving my slider the same id would simply override it?
From my belief, I would also have to have the slider defined in the forms.py class for this form, else it is not accessible in cleaned_data in the view. Any ideas?
I'm not sure why you would assume that. But why not define the relevant attributes in the form in the first place, so that they get output automatically?
difficulty = forms.CharField(widget=forms.TextInput(attrs={"class": "slider form-control", "data-slider-min": "0"...}))
Or even better, use the Crispy API to let you declare those attributes (I don't use Crispy myself, but I know it gives you a lot of extra control.)
You can override the default widget, adding your class for styling like so:
from django import forms
class CreateTestform(forms.ModelForm):
class Meta:
model = Test
fields = ['difficulty'] # you don't need to define difficulty again,
# just get it from your model like in this example.
widget = {
'difficulty': forms.CharField(attrs={
'class': 'name-of-your-class',
'other-attribute': 'other-value',
}),
}
You can check for more here.
If you have too much styling to apply to your form, I suggest to render it manually and I know you use cripsy to avoid that, but it's a easier way, without limits, django is supposed to be used for backend, not frontend.
You can even render like this (supposing you have to render difficulty):
<input class="whatever-your-class-is" name="{{ form.difficulty.html_name }}" id="{{ form.difficulty.id_for_label }}">
If you look carefully, you found information about .html_name and .id_for_label, even .value here.
I have an UpdateView which contains a form and an InlineFormetSet that is related to the form model (I simplified the code below):
#models.py
class Note(Model):
content = models.TextField()
class Dialog(Model):
message = models.TextField()
note = modes.ForeignKey(Note)
#views.py
class NoteUpdateView(UpdateView):
model = Note
form_class = NoteForm
def get_context_data(self, **kwargs):
context = super(NoteUpdateView ,self).get_context_data(**kwargs)
self.object = self.get_object()
dialogFormset = inlineformset_factory(Note,
Dialog,
fields='__all__',
extra=0)
dialog = dialogFormset(instance=self.object)
context['dialog'] = dialog
return context
def post(self, request, *args, **kwargs):
form = self.get_form(self.get_form_class())
dialog_form = DialogFormset(self.request.POST, instance=Note.objects.get(id=self.kwargs['pk']))
if (form.is_valid() and dialog_form.is_valid()):
return self.form_valid(form, result_form, dialog_form)
else:
return self.form_invalid(form, result_form, dialog_form)
def form_valid(self, form, result_form, dialog_form):
self.object, created = Note.objects.update_or_create(pk=self.kwargs['pk'], defaults=form.cleaned_data)
dialog_form.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, result_form, dialog_form):
return self.render_to_response(self.get_context_data(form=form,
result_form=result_form,
dialog_form=dialog_form))
The purpose of NoteUpdateView is to render existing Note and Dialog when a GET request is made tonote/11. A user may delete an existing Dialog, which is not handled by the code above.
To handle delete, I can do the following on POST:
1) fetch all of the dialog records related to the requested Note:
dialogs = Note.objects.filter(pk=self.kwargs['pk'])
2) loop through self.request.POST and see if the formsets contained in the submitted data also exist in the dialogs created above.
3) If a record is dialogs but not submitted via POST, then that dialog is considered to be removed by the user. Thus, preform delete operation.
I am sure I can implement these steps. But since I am not very familiar with Django's formset. I wonder if there is any built-in classes or methods that I should use to automate these steps. What is the Django way of doing what I just described?
Ok, I figured out what the problem was. The problem that I run into is due to the use of django-crispy-forms. Let me explain what happened:
When Django renders InlineFormSet, it's can_delete attribute is set to True automatically. When this attribute is set to True, a hidden input field is inserted into the rendered HTML:
<input type="hidden" name="dialog_set-0-DELETE" id="id_dialog_set-0-DELETE">
I used django-crispy-forms to render my forms so that they are styled with bootstrap3. When rendering inlineformset using crispy-forms, a FormHelper needs to be defined.
This is because when you have multiple inlineformset forms on the page, you will only want one <form> tag surrounds them instead of giving each inlineformset form it's own <form> tag. To do that, I had to define the FormHelper like this:
#models.py
class Dialog(Model):
info1 = models.TextField()
info2 = models.TextField()
#forms.py
class DialogFormSetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(DialogFormSetHelper, self).__init__(*args, **kwargs)
self.form_tag = False # This line removes the '<form>' tag
self.disable_csrf = True # No need to insert the CSRF string with each inlineform
self.layout = Layout(
Field('info1', rows='3'), # make sure the name of the field matches the names defined in the corresponding model
Field('info2', rows='3')
)
I need django-crispy-forms to set the row number of a textarea tag to be 3. Thus, I had to specifically redefine how my textarea fields look like under Layout. What I didn't realize when using the Layout is that anything that you didn't define in it will not be rendered in the HTML.
From the look of the code, I didn't miss any fields defined in the Dialog model. But, what I didn't realize is that the hidden fields that come with InlineFormSet are not rendered in the HTML unless I specifically define them in the Layout object and in the template. To get formset & inlineformset working properly, you will need the following hidden inputs:
formset.manageform. They look like this in the HTML:
<input id="id_dialog_set-TOTAL_FORMS" name="dialog_set-TOTAL_FORMS" type="hidden" value="1">
<input id="id_dialog_set-INITIAL_FORMS" name="dialog_set-INITIAL_FORMS" type="hidden" value="1">
<input id="id_dialog_set-MIN_NUM_FORMS" name="dialog_set-MIN_NUM_FORMS" type="hidden" value="0">
<input id="id_dialog_set-MAX_NUM_FORMS" name="dialog_set-MAX_NUM_FORMS" type="hidden" value="1000">
The primary key that is associated with each inlineformset form, and a foreign key that the inlineformset refers to. They look like this in HTML:
<input id="id_dialog_set-0-note" name="dialog_set-0-note" type="hidden" value="11"> <!-- This line identifies the foreign key`s id -->
<input id="id_dialog_set-0-id" name="dialog_set-0-id" type="hidden" value="4"> <!-- This line identifies the inlineformset form`s id -->
[A DELETE hidden field when can_delete is set to True] (https://docs.djangoproject.com/en/1.9/topics/forms/formsets/#can-delete). It looks like this in the HTML:
<input type="hidden" name="dialog_set-0-DELETE" id="id_dialog_set-0-DELETE">
In my template, I had the first two covered:
<form method="post" action="{{ action }}" enctype="multipart/form-data" id="note_form">
{% crispy form %}
{# the management_form is covered here #}
{{ dialog.management_form }}
{% for form in dialog %}
<div class="formset-container">
<div class="dialog-title">
{% crispy form dialogHelper %}
</div>
{# the hidden fields are covered here #}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
</div>
{% endfor %}
</form>
What I didn't have is the DELETE hidden input. To add it to the HTML, I had to add it this way in the Layout:
#forms.py
class DialogFormSetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(DialogFormSetHelper, self).__init__(*args, **kwargs)
self.form_tag = False
self.disable_csrf = True
self.layout = Layout(
Field('info1', rows='3'),
Field('info2', rows='3'),
Field('DELETE') # <- ADD THIS LINE
)
Finally, everything works properly now
The Django way is to check if someone has made a library that handles this for you :-).
So take a look at the exellent django-extra-views and it's InlineFormSetView. I've used it a lot and it works really well. In your case your view becomes something like this:
from extra_views import InlineFormSetView
class NoteUpdateView(InlineFormSetView):
model = Note
inline_model = Dialog
form_class = NoteForm
extra = 0
def get_context_data(self, **kwargs):
context = super(NoteUpdateView ,self).get_context_data(**kwargs)
context['dialog'] = context['formset']
return context
You could skip .get_context_data method as well if you update your template to refer to the formset as "formset" instead.
Hello and thank you in advance. I have a django form that is not rendering on a template. The "submit" button renders, but the form doesn't. I have been staring at this code for about 6 hours and it is probably something very simple, I probably need another pair of eyes on it.
My Model:
#models.py
from django.db import models
from django.forms import ModelForm
class DraftInput(models.Model):
player_id = models.IntegerField(help_text = 'Input Player ID Number', max_length = 5)
def __unicode__(self):
return self.player_id
class DraftForm(ModelForm):
class Meta:
model = DraftInput
My views:
#views.py
from django.shortcuts import render_to_response
from simple.models import DraftInput
from simple.models import DraftForm
from django.template import RequestContext
#...
def draft_view(request):
if request.method == 'POST':
form = DraftForm(request.POST)
if form.is_valid():
form.save()
else:
form = DraftForm()
return render_to_response('simple/draft_input.html', {'form': form} )
My template:
#draft_input.html
#...
<h1>Draft Input</h1>
<h2>Draft</h2><br /><br />
<form method="POST" action="">
<table>{{ form }}</table>
<input type="submit" value="Draft Player" />
</form><br /><br />
#...
Thank you for your help.
Reading the answers thread, it seems you're still out of luck.
Try performing these commands in a django interactive shell (python manage.py shell). It's possible that an exception is thrown when you try to format the form. When an exception is thrown during the form generation in the template, you won't see much in the HTML; but a django shell usually unveils the culprit.
python manage.py shell
In [1]: from simple.models import DraftForm
In [2]: form = DraftForm()
In [3]: form.as_p()
You can either see the HTML generated here, or would catch an exception. This will tell you more about what you need to fix.
Either use {{ form.as_table }} or remove the <table></table> from around your form tag.
I think that it is a sign to move to generics classes :) Just try CreateView class
In your views.py file:
class CreateDraftInputView(CreateView):
model = DraftInput
template_name = "test/draftinput_form.html"
success_url = "/test/listdraft/"
Simple create template:
<form method="POST" action=".">
<table>
{% csrf_token %}
{{ form.as_table }}
<input type="submit" />
</table>
</form>
And very simple list of records in your urls.py:
, (r"^listdraft/$", ListView.as_view(model = DraftInput, template_name = "draftinput_list.html"))
Try {{ form.as_p }} for example. There are other shortcuts. You can also manually create the form. Have a look into the Django Docs : https://docs.djangoproject.com/en/dev/topics/forms/?from=olddocs
Try "form.as_p" or "form.as_table" instead of just "form" in your html file.
EDIT: check out the documentation and use one of their examples as a model: https://docs.djangoproject.com/en/dev/topics/forms/modelforms/
In your example, you define form parameters in your model, which may be incorrect. You want to leave form specific modifications, like help_text in the DraftForm class, now in the DraftInput model.
This is a simple issue, you should just replicate something from the docs and then work towards what you want.
Hope this helps.
Could not quickly find the root cause, but the fields in my form only show up when the variable declaration follows the conventional module.Class form. The issue might be somewhere in classloading order.
from simple import models
# ...
form = models.DraftForm()
I want to learn how can I add to template to my ModelForm i'm newbie. Below you can see my models.py, url.py and views.py:
My model.py looks like that:
from django.db import models
from django.forms import ModelForm
from django.contrib.auth.models import User
class Yazilar(models.Model):
yazi = models.CharField(max_length=200)
temsilci = models.ForeignKey(User)
class YaziForm(ModelForm):
class Meta:
model = Yazilar
My views.py function is below:
#login_required
def yazi_ekle(request):
yazim = YaziForm
return render_to_response('yazi/save.html', {'YaziForm': YaziForm})
My url.conf looks like below:
(r'^yazi/save/$', 'tryout.yazi.views.yazi_ekle'),
My question is about creating a form and what is that forms "action" parameter?
It seems to me that your problem is in the view, you should be doing something like this:
#login_required
def yazi_ekle(request):
yazim = YaziForm() # Look at the (), they are needed for instantiation
return render_to_response('yazi/save.html', {'YaziForm': yazim}) # Sending the form instance to the context, not the form class
Now, you have a variable named YaziForm in your template context. Django forms autorender to a bunch of table rows with the widgets as default, so in your file yazi/save.html, do this
<form method="post" action="">
{% csrf_token %}
<table>
{{YaziForm}}
</table>
<input type="submit" value="Submit Form"/>
</form>
That will render your form as a table automatically, though you have to add the logic for the form under POST.
You could in fact use <form action=""> since the url you want to post to is the same as the page you are on.
If you don't like that then as long as you have 'django.core.context_processors.request' in your TEMPLATE_CONTEXT_PROCESSORS in settings.py I think you could also do:
<form action="{{ request.path }}">
As always, see the docs :)
http://docs.djangoproject.com/en/1.1/ref/request-response/#django.http.HttpRequest.path
EDIT
In case, in the intervening year since this question was posted, the poster still hasn't tried to read the ModelForm docs... http://docs.djangoproject.com/en/1.2/topics/forms/modelforms/
Yes the view is wrong, you have instantiate the form. You also want some logic to handle the post data. If it's an edit view you probably also want the view to take an item id in the view args and have some logic to load that model instance.
eg:
#login_required
def yazi_ekle(request, id=None):
form_args = {}
if id is not None:
# edit an existing Yazilar
try:
yazilar = Yazilar.objects.get(pk=id)
except Yazilar.DoesNotExist:
return Http404('Yazilar not found')
form_args['instance'] = yazilar
# else create new Yazilar...
if request.POST:
form_args['data'] = request.POST
yazi_form = YaziForm(**form_args)
if yazi_form.is_valid():
yazilar = yazi_form.save(commit=True)
else:
yazi_form = YaziForm(**form_args)
return render_to_response('yazi/save.html',
{
'yazi_form': yazi_form
},
context_instance=RequestContext(request)
)
then in your urls.py something like:
(r'^yazi/ekle/(?P<id>\d+)?$', 'tryout.yazi.views.yazi_ekle'),
and in the template:
<form method="post" action="">
{% csrf_token %}<!-- required since Django 1.2 or later -->
<ul>
{{ yazi_form.as_ul }}
</ul>
<input type="submit" value="Submit Form"/>
</form>