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.
Related
I am trying to understand how a very regularly used code-form in Django views.py actually works. I see the following (or a variation) used a lot, but I can’t find a line-by-line explanation of how the code works – which I need if I am to use it confidently and modify when needed.
Can you let me know if I have understood how Django processes these various components? If not, please indicate where I have misunderstood.
I will start with the model then introduce urls.py the view and the form. I will go through the relevant parts of the code. I will consider:
The model:
#models.py
class CC_Questions(models.Model):
# defining choices in Model : https://docs.djangoproject.com/en/4.0/ref/models/fields/
personality = [
('IF','IF'),
('IT','IT'),
('EF','EF'),
('ET','ET'),
]
q_text = models.CharField('Question text', max_length=200)
#C1_Type = models.CharField('Choice 1 Type', max_length=2)
C1_Type = models.CharField(choices=personality, max_length=2)
Choice1_text = models.CharField('Choice 1 text', max_length=100)
#C2_Type = models.CharField('Choice 2 Type', max_length=2)
C2_Type = models.CharField(choices=personality, max_length=2)
Choice2_text = models.CharField('Choice 2 text', max_length=100)
#
def __str__(self):
return self.q_text[:20]
The url
#
#urls.py
app_name = ‘polls’
urlpatterns = [
…..
# ex: /polls/p2create
path('p2create/', p2_views.p2create, name='p2create'),
…
The view:
#views.py
from.forms import Anyform
#
def p2create(request):
if request.method == 'POST':
form = AnyForm(request.POST)
if form.is_valid():
form.save()
return redirect('/polls/p2')
else:
form = AnyForm()
context = {'form' : form}
return render(request, 'pollapp2/create.html', context)
#
The form:
#forms.py
#
….
from django import forms
from .models import ….. CC_Questions …
…….
class AnyForm(forms.ModelForm):
class Meta:
model = CC_Questions
fields = ['q_text', 'Choice1_text', 'Choice2_text','C1_Type','C2_Type']
The template:
#
# Create.html
#
…..
{% load widget_tweaks %}
…..
<form method="POST">
{% csrf_token %}
…
<div class="row">
<div class="col-lg-5">
<div class="form-group">
<label for="Choice1_text ">Choice 1</label>
{% render_field form.Choice1_text class="form-control" %}
<label for="C1_type">Type 1</label>
{% render_field form.C1_Type class="form-control" %}
…….
Does the code operate as follows?
The user enters URL in browser: http://localhost:8000/polls/p2create/
The urls.py picks the view to execute
path('p2create/', p2_views.p2create, name='p2create'),
views.py runs the view:
def p2create(request):
Now, as no form has yet been "identified" or "loaded" (??) the following test fails:
if request.method == 'POST':
so the Else clause executes
else:
form = AnyForm()
that "sets" the variable form to "AnyForm()"
The following line creates a dictionary, named context, that creates a key 'form' that is linked with the value form (=Anyform)
context = {'form' : form}
The following line searches for create.html in the template directory, passing the directory context as a parameter
return render(request, 'pollapp2/create.html', context)
Then template, create.html, displays various input boxes (??) from :
<label for="Choice1_text ">Choice 1</label>
{% render_field form.Choice1_text class="form-control" %}
When the submit button is pressed on the displayed page, this "passes back" (??) the {% render_field .. %} values to the view (?????)
<form method="POST">
...
<div class="col-lg-4">
<button type="submit" class="btn btn-info">Submit</button>
</div>
...
</form>
the view is executed again (????) , but this time request method is set to "POST" because of the form method="POST" in the template (?????)
if request.method == 'POST':
Now the same form , AnyForm , is "reloaded" (????) but with the parameter value "POST"
if request.method == 'POST':
form = AnyForm(request.POST)
Now if the form is valid (I have no idea what a "valid" or "invalid" form is)
if form.is_valid():
then all the values "captured" in the template by (???)
<label for="Choice1_text ">Choice 1</label>
{% render_field form.Choice1_text class="form-control" %}
(??????)
are written by the view (?????)
form.save
to the corresponding fields in the ModelForm (?????)
class Meta:
model = CC_Questions
fields = ['q_text', 'Choice1_text', 'Choice2_text','C1_Type','C2_Type']
The view then redirects and loads the home page in the browser
return redirect('/polls/p2')
Ok, So with the help of the references (mentioned below) and the workflow suggested by you, let us first see, the Django MVT workflow and then address the various questions asked in between the post.
WebForms with Django MVT in brief:
Prepare data of several different types for display in a form.
Render data as HTML.
Edit, enter data using a convenient interface.
Validate and clean up the data.
Return data to the server.
Save, delete or pass on for further processing.
The URL:
When a user requests a page from your Django-powered site, this is the algorithm the system follows to determine which Python code to execute. Which is handled by our views.py. From the frontend, if the request is not 'POST', then it is a GET request, hence the else part of the handling function in the views.py executes. This you have mentioned already.
The View: - Form data sent back to a Django website is processed by a view, generally, the same view which published the form. This allows us to reuse some of the same logic. To handle the form we need to instantiate it in the view for the URL where we want it to be published.
If we use request.POST, as in this line:
form = AnyForm(request.POST)
It transforms the form into a bound form (Form bound with data). Read more about it here.
Questioned By You (QBY) - When the submit button is pressed on the displayed page, this "passes back" (??) the {% render_field .. %} values to the view (?????)
So, yes, If the action attribute is not mentioned in the form then the data will be passed to the view responsible for displaying the form.
QBY - the view is executed again (????), but this time request method is set to "POST" because of the form method="POST" in the template (?????)
The button type submit, submits the form and make the request a POST request. The Django template sends that submitted data in the request.POST.
QBY - Now the same form, AnyForm, is "reloaded" (????) but with the parameter value "POST"
Here, if the return method at the end of the POST condition is HttpResponseRedirect it will redirect the user to the mentioned URL page, but if the same HTML is used to be rendered then the form will be displayed as a bound form. (It depends upon the requirements)
QBY - Now if the form is valid (I have no idea what a "valid" or "invalid" form is)
Form.is_valid()
The primary task of a Form object is to validate data. With a bound Form instance, call the is_valid() method to run validation and return a boolean designating whether the data was valid. If yes, then the data is being saved in the model.
QBY - then all the values "captured" in the template by (???)
All the values are sent to views in the request.POST. We can check it by
print(request.POST)
QBY - are written by the view (?????), form.save to the corresponding fields in the ModelForm (?????)
Save method is called on the Django ModelForm instance in order to save the data to the database. Calling save would run the validation check. A ValueError will be raised if the data in the form doesn't validate.
This saved data can now be processed further.
References:
[https://docs.djangoproject.com/en/4.0/topics/forms/][2]
[https://www.tangowithdjango.com/book/chapters/models_templates.html][3]
[https://docs.djangoproject.com/en/4.0/ref/forms/api/][4]
In Django I'm trying to render a list of checkboxes with the choices being populated from values in a model's field. Here is what I have so far:
models
#Model that feeds the list of checkboxes
class Category_Level1(models.Model):
category_level1_name = models.CharField(max_length = 50)
forms
class ProductCategoryLevel1Form(forms.Form):
product_category_level1 = forms.MultipleChoiceField(label = "Product Category", choices = Category_Level1.objects.all(), widget = forms.CheckboxSelectMultiple)
views
def select_product(request):
product_category_level1 = ProductCategoryLevel1Form()
if request.method == 'POST':
product_category_level1_form = ProductCategoryLevel1Form(request.POST)
if product_category_level1_form.is_valid():
product_category_level1_list = product_category_level1_form.cleaned_data.get('product_category_level1', 'Unk')
# Saving the selected categories to another table
product_cat_obj = Product_Category_Level1(category_level1_name = product_category_level1_list)
product_cat_obj.save()
context = {
'product_category_level1' : product_category_level1
}
return render(request, 'select_product/select.html', context)
template
<div class="row">
<label for="id_prod_category"></label>
{% product_category_level1 %}
</div>
I get the following error:
Invalid block tag on line 23: 'product_category_level1', expected
'endblock'. Did you forget to register or load this tag?
What am I doing wrong?
Appreciate any suggestions.
Django uses two types of markup - {% ... %} for tags and {{ ... }} for variables.
In this code you are trying to render the product_category_level1 variable, therefore you should use {{ product_category_level1 }} instead of {% product_category_level1 %} in your template.
However, there are also some other bugs in this code.
You seem to be mixing product_category_level1 and product_category_level1_form in your view code. These should all be the same variable. You need to use the same variable so when binding the POST data and calling is_valid() the form you display to the user contains any validations errors. As you currently have it you always give the user a new instance of the form - if any data validations fail the form will be presented again with all the fields empty and no error messages.
You need to tell Django how you want your form to be rendered - as p tags, as li tags, etc. As such you'll need to add the method call as_p like this: {{ product_category_level1.as_p }}. More rendering options can be found here: https://docs.djangoproject.com/en/1.10/topics/forms/#working-with-form-templates
I am building my own projects based on the instructions from 'Django by Example' book.
However I got stuck on how does django initialize input form fields using URL parameters?
#models.py
class Image(models.Model):
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
url = models.URLField()
(...)
#views.py
def image_create(request):
if request.method == 'POST':
form = ImageCreateForm(data=request.POST)
(...)
else:
form = ImageCreateForm(data=request.GET)
return render(request,
'create.html',
{'form': form})
#forms.py
class ImageCreateForm(forms.ModelForm):
class Meta:
model = Image
fields = ('title', 'url', 'description')
widgets = {
'url': forms.HiddenInput,
}
#create.html
{% extends "base.html" %}
{% block content %}
<h1>Bookmark an image</h1>
<img src="{{ request.GET.url }}" class="image-preview">
<form action="." method="post">
{{ form.as_p }}
{% csrf_token %}
<input type="submit" value="Bookmark it!">
</form>
{% endblock content %}
I could not find any specific line of code which would explicitly tell to get request.GET parameters and assign each of them to corresponding input field.
I assume that all of this has to do with the form initialization on line: form = ImageCreateForm(data=request.GET) in views.py
Questions:
Can someone please explain me how does django use request.GET parameters to initialize input fields values(assigns them URL parameters' values)?
Does it simply match, for every request.GET key, the corresponding input field 'name' attribute, and then assigns to that particular input field the value corresponding to the key in request.GET?
Also can someone confirm if there is a relationship between the model/form(which one?) field names to the URL parameters?
What is the difference between these two(as both of them seems to work the same way, except the latter returns list in the input field values):
form = ImageCreateForm(data=request.GET)
form = ImageCreateForm(initial=request.GET)
1) Yes - request.GET is simply a fancy dictionary (technically a QueryDict), and the form "data" argument expects a dict with keys corresponding to the fields in the form. You could technically pass the form any dictionary if it had the right keys representing the fields.
2) Generally speaking, there is no relation between model/form fields and url parameters. However, if you are using certain Class Based Views (such as a DetailView) if you set a slug or id in your url, it will pass the value along to the view and map it to the objects slug or id. The get_object() method in the link below has an example.
https://ccbv.co.uk/projects/Django/1.9/django.views.generic.detail/DetailView/
3) The data attribute to a form is what is submitted (usually POSTed) to the form whereas the initial value is what is passed to the form upon first page load. For example, if you were editing an object, typically the existing object data would populate the initial value. Then, if you POSTed edits to the form, it would be passed in as data.
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.
I want to save my string in CharField and then display it as a value of input.
But when I save it, I got value in unicode characters. ex.
When I type "a" and I save it I got "(u'a',)"
and I want to get only "a"
In my html code I have form:
<form action="{% url 'edit'%}" method="post">
{% csrf_token %}
<input type="text" name="name" value="{{data.name}}" placeholder="{% trans 'Name' %}" required><br>
<button type="submit">{% trans 'Submit'%}</button>
</form>
My views:
#login_required
def edit(request):
user = MyUser.objects.get(pk=request.user.pk)
if request.method == "POST":
post = request.POST
editmodel = user.editmodel_set.all()[0]
editmodel.name = post.get('name')
shop.save()
And models:
class EditModel(model.Model):
name = models.CharField(max_length=100)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
That is not a good way of working with forms. Do not use data in request.POST directly to save in the database.
For more information refer working with forms in django documentation.
Either create a model form or simple form, check posted data is valid and then save it in DB with appropriate data object.
If you still want to do that update your code as
editmodel.name = post.getlist('name')[0]