Django - Submitting form with multiple inputs for same field - python

Forewarning: I'm very new to Django (and web development, in general).
I'm using Django to host a web-based UI that will take user input from a short survey, feed it through some analyses that I've developed in Python, and then present the visual output of these analyses in the UI.
My survey consists of 10 questions asking a user how much they agree with a a specific topic.
Example of UI for survey:
Example of UI input screen
For models.py, I have 2 fields: Question & Choice
class Question(models.Model):
question_text = models.CharField(max_length=200)
def __str__(self):
return self.question_text
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
I am wanting to have a user select their response to all 10 questions, and then click submit to submit all responses at once, but I'm having trouble with how that would be handled in Django.
Here is the html form that I'm using, but this code snippet places a "submit" button after each question, and only allows for a single submission at a time.
NOTE: The code below is creating a question-specific form for each iteration.
{% for question in latest_question_list %}
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<div class="row">
<div class="col-topic">
<label>{{ question.question_text }}</label>
</div>
{% for choice in question.choice_set.all %}
<div class="col-select">
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
</div>
{% endfor %}
</div>
<input type="submit" value="Vote" />
</form>
{% endfor %}
I'm interested in how I would take multiple inputs (all for Question/Choice) in a single submission and return that back to views.py
EDIT: ADDING VIEWS.PY
Currently, my views.py script handles a single question/choice pair. Once I figure out how to allow users to submit the form one time for all 10 question/choices, I will need to reflect this in views.py. This could sort of be part 2 of the question. First, how do I enable a user to submit all of their responses to all 10 questions with one "submit" button? Second, how do I setup views.py to accept more than 1 value at a time?
views.py
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/survey.html', {
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:analysis'))
Please let me know if additional context it needed.
Thanks in advance!
-C

Use getlist()
In your views.py
if method=="POST":
choices = request.POST.getlist('choice')
I feel you should change the input radio to checkbox. Radio won't allow multiple selection but Checkbox will.
Refer here: https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.QueryDict.getlist

Ideally, this should have been done with Django Forms. Django forms have widgets and RadioSelect is one of them. You can use that to render your form and get the answer to each question at once.
But that will need a lot of change in the way you are currently doing things.
So, what you can do is, on click on a submit button, collect all the question/choice pairs and send them at once with a POST request.
{% for question in latest_question_list %}
<form>
<div class="row">
<div class="col-topic">
<label>{{ question.question_text }}</label>
</div>
{% for choice in question.choice_set.all %}
<div class="col-select">
<input type="radio" name="choice" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
</div>
{% endfor %}
</div>
</form>
{% endfor %}
<input id="submit-btn" type="submit" value="Vote" />
<script>
$(document).on('click', '#submit-btn', function(event){
var response_data = []
var question_objs = $('.col-topic');
var choice_objs = $('.col-select');
for(i=0;i<question_objs.length;i++){
var question_text = $(question_objs[i]).find('label').text();
var choice_id = $(choice_objs[i]).find('input').val();
var choice_text = $(choice_objs[i]).find('label').text();
var question_choice = {
"question_text": question_text,
"choice_id": choice_id,
"choice_text": choice_text
}
response_data.push(question_choice);
}
$.ajax({
type: "POST",
url: "url_to_your_view",
data: response_data,
success: function(response){
alert("Success");
}
});
});
</script>
This is how your view should look like.
def question_choice_view(request):
if request.method == "POST":
question_choice_data = request.POST['data']
# further logic
Now, question_choice_data is a list of dictionaries. Each dict in the list will have the question_text, choice_text and choice id of user's response.

You just need to organize your template a bit differently in order to have multiple questions within the same form. Litteraly in HTML it would translate into multiple text inputs and then one submit input below, all within one single form:
<form action="{% url 'polls:vote' question.id %}" method="post">
{% for question in latest_question_list %}
{% csrf_token %}
<div class="row">
<div class="col-topic">
<label>{{ question.question_text }}</label>
</div>
{% for choice in question.choice_set.all %}
<div class="col-select">
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
</div>
{% endfor %}
</div>
{% endfor %}
<input type="submit" value="Vote" />
</form>
Is it working now ?

Related

Django - Updating value only for the last item of list

This is how my ToDoapp looks like
The date change works only for the last item in the list but for other items it throws the error:
ValidationError at /
['“” value has an invalid date format. It must be in YYYY-MM-DD format.']
I see that irrespective of "Update" button I press, it passes only the last item's id and date.
Find my code below:
Index.html
{% extends 'base.html' %}
{% block content %}
<h3 style = "margin-bottom: 20px"><strong>To Do List App</strong></h3>
<form method="POST">
{%csrf_token%}
<ul class="list-group">
{% for task in tasklist %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<input type='hidden' name = 'task_id' value='{{task.id}}'>{{task.tasks}}
<span class="badge bg-primary rounded-pill">{{task.duedate}}
<input type="date" name="datepick" />
<input type='submit' value ='Update'>
</span>
</li>
{% endfor %}
</form>
Views.py
def index(request):
if request.method == "POST":
task_id = request.POST.get('task_id')
task=Task.objects.get(id=task_id)
datepick = request.POST.get('datepick')
task.duedate = datepick
task.save()
tasklist = Task.objects.all()
return render(request, 'index.html', {'tasklist':tasklist})
Models.py
class Task(models.Model):
tasks = models.CharField(max_length=200)
duedate = models.DateField(blank=True)
I feel that mistake is in the HTML file and I'm not familiar with HTML.
Each task on the list should have its own form.
{% extends 'base.html' %}
{% block content %}
<h3 style = "margin-bottom: 20px"><strong>To Do List App</strong></h3>
<ul class="list-group">
{% for task in tasklist %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<form method="POST">
{%csrf_token%}
<input type='hidden' name = 'task_id' value='{{task.id}}'>{{task.tasks}}
<span class="badge bg-primary rounded-pill">{{task.duedate}}
<input type="date" name="datepick" />
<input type='submit' value ='Update'>
</span>
</form>
</li>
{% endfor %}
</ul>
Each row shall be in an independent form as currently the form has 3 elements with the same name
You should provide a unique name for each element in a form. As it is inside an iterator you can use {{ forloop.counter }} as name.
The link below would be helpful:
Django - iterate number in for loop of a template

Django how to retrieve values from a database for an existing post so that we can edit

I am displaying a form for a posting which has already been saved to the database. I am giving the user the option to view the values as they are in the database and give him the option to edit them. However, it is not working for dropdown. I have tried writing something but it does not display the correct values.
please help :(((
Here is the code snippet:
<div class="form-group col-md-6">
<label for="industry_type">Industry Type</label>
<select class="form-control" id="industry_type" name="industry_type" selected="
{{ internship.industry_type }}">
{% for ind_type in industry_types %}
<option value="{{ ind_type }}" {% if '{{ ind_type }}' == '{{
internship.industry_type }}' %}selected="selected" {% endif %}>
{{ind_type}}</option>
{% endfor %}
</select>
</div>
views.py
def edit_internship(request, pid):
internship = Internship.objects.get(id=pid)
skills = InternshipSkill.objects.filter(internship=internship)
emp_steps = InternshipEmpStep.objects.filter(internship=internship)
.order_by('emp_step_no')
industry_types = IndustryType.objects.all()
context = {
'industry_types': industry_types,
'internship': internship,
'skills': skills,
'emp_steps': emp_steps,
}
return render(request, 'edit_internship.html', context)
Okay this is just because don't need to re-add the curly brackets in django's templates if you already have them:
<div class="form-group col-md-6">
<label for="industry_type">Industry Type</label>
<select class="form-control" id="industry_type" name="industry_type" selected="{{ internship.industry_type.pk }}">
{% for ind_type in industry_types %}
<option value="{{ ind_type.pk }}" {% if ind_type.pk == internship.industry_type.pk %}selected="selected" {% endif %}>
{{ ind_type }}
</option>
{% endfor %}
</select>
</div>
Also, don't forget the .pk argument to ind_type when declaring the value.
For future reference, I really recommend you use Django's forms when creating these, it makes your like SO much easier:
views.py
class EditInternship(UpdateView):
model = Internship
template_name = 'edit_internship.html'
form_class = EditInternshipForm
def get_context_data(**kwargs):
ctx = super().get_context_data(**kwargs)
ctx.update(
skills=InternshipSkill.objects.filter(internship=internship),
emp_steps=InternshipEmpStep.objects.filter(internship=internship).order_by('emp_step_no')
)
# You'll also have `form` already in the context
return ctx
edit_internship = EditInternship.as_view()
forms.py
class EditInternshipForm(forms.ModelForm):
class Meta:
model = Internship
fields = 'industry_type', 'other_fields_on_the_model_you_want_to_edit'
edit_internship.html
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.industry_type }}
{{ form.other_fields_you_want_to_edit }}
<input type="submit" name="submit_button" value="Save" class="btn btn-primary btn-sm" id="submit_button">
</form>

posting Flask wtforms in loop and saving data

I have a Flask-WTF form fields in for loop. I want to post the new quantities for each item.
I am reading about field list. I still dont understand but i think they might be the answer.
#app.route('/checkout')
def checkout():
form = CartForm()
for item in current_user.cart:
product = Product.query.get(item.product_id)
cart_items.append({product: item.quantity})
return render_template('checkout.html',cart_items=cart_items,form=form)
{% for item in cart_items %}
{% for product, quantity in item.items() %}
{{product.name}}
{{product.price}}
{{form.quantity }}
{% endfor %}
{% endfor %}
Problem1: When looping over each Each Flask-WTF form field has the same name.
The output
<select id="quantity" name="quantity"><option value="1">1</option></select>
<select id="quantity" name="quantity"><option value="1">1</option></select>
problem2: how save in the backed if each form has a different name.
I have the same problem exactly! But I solve it without Flask-WTF, the solution below was based on my app. I have an album edit page, I need to loop a text input for each picture in album, then save the text for each picture.
I loop the input in HTML, notice I set the action value for another view function and use each photo's id as each text input's name:
<form action="{{ url_for('edit_photo', id=album.id) }}" method="POST">
<ul>
{% for photo in photos %}
<li>
<img class="img-responsive portrait" src="{{ photo.path }}" alt="Some description"/>
<textarea name="{{ photo.id }}" placeholder="add some description" rows="3">{% if photo.description %}{{ photo.description }}{% endif %}</textarea>
</li>
{% endfor %}
</ul>
<hr>
<input class="btn btn-success" type="submit" name="submit" value="submit">
Then I loop the picture and save the input data (get it's value by it's id):
#app.route('/edit-photo/<int:id>', methods=['GET', 'POST'])
#login_required
def edit_photo(id):
album = Album.query.get_or_404(id)
photos = album.photos.order_by(Photo.order.asc())
if request.method == 'POST':
for photo in photos:
photo.about = request.form[str(photo.id)]
db.session.add(photo)
return redirect(url_for('.album', id=id))
return render_template('edit_photo.html', album=album, photos=photos)

Get field type in Django's login template

I'm trying to create custom template for Django's build-in login view. At the moment it looks like (registration/login.html):
<form method="post">
{% csrf_token %}
{% for field in form %}
{% include 'registration/form_field.html' %}
{% endfor %}
<input class="btn btn-primary btn-block" type="submit" value="{% trans "Log in" %}">
</form>
And registration/form_field.html file is:
<div class="form-group {% if field.errors %}has-error{% endif %}">
<input class="form-control" name="{{ field.name }}" placeholder="{{ field.label }}" {% if field.data %}value="{{ field.data }}"{% endif %} />
{% if field.errors %}
<span class='text-danger'>{{ field.errors|join:'<br>' }}</span>
{% endif %}
</div>
Everything works as expected, only problem is that password is shown in clear text.
To solve this type="password" should be set for password field (and type="text" for username field).
Is it possible to implement this using field variable (i.e. something like {{ field.type }})?
You can implement a PasswordInput widget in your form definition, that will render as a password field with type="password".
class ExampleForm(forms.Form):
password = forms.CharField(label='Password',
widget=forms.PasswordInput())
In the templates,
{{form.password}}
will render this field, which is the cleanest solution.
You may also access the type of the field (as you wanted), like this:
{{form.fields.password.widget.input_type}}
Note that if you'd like further customization beyond simply rendering the form, there's nothing wrong with just writing your own html for the fields.

Is it possible to create Form in django that lets you change or add directly in the database?

Is it possible to create a form that modifies when information is already find in the database or so adds information if there is nothing in the database ?
So I have a template that allows me to display the value if there is one in the base given to the fields or answer then display a blank field for the answer.
My template for my formset :
<form method="POST" action="">
{{ formset.management_form }} {% csrf_token %}
<table>
<!-- <br>{{ formset.as_table }}<br> -->
{% for question in questions %}<hr>
<label for="question">{{ question }} [{{ question.id }}]</label>
<input type="hidden" id="id_form-{{ forloop.counter0 }}-question" name="form-{{ forloop.counter0 }}-question" value="{{ question.id }}"/>
</p>
{% if question.reply_set.all %}
{% for reply in question.reply_set.all %}
<p>
<label for="answer">Réponse :</label>
<input type="text" id="id_form-{{ forloop.parentloop.counter0 }}-answer" name="form-{{ forloop.parentloop.counter0 }}-answer" value="{{ reply.answer }}"/>
</p>
{% endfor %}
{% else %}
<p>
<label for="answer">Réponse :</label>
<input type="text" id="id_form-{{ forloop.counter0 }}-answer" name="form-{{ forloop.counter0 }}-answer" placeholder="answer here"/>
</p>
{% endif %}
{% endfor %}
<br><br><br><hr> <br><br><br>
</table><br>
<center><input type="submit" value="Submit" class="btn btn-success" />
Retour</center>
</form>
My problem is that I get a good add when there is nothing in the database but when I have Data if I change its value this create another answer as I would like it replaced the answer with the new!
My views.py
def access(request, instance):
questions = Question.objects.filter(page=instance)
pages = Page.objects.all()
numPages = Page.objects.get(pk=instance)
length_questions = len(questions)
logged_user = get_logged_user_from_request(request)
instance = {'instance':instance}
ReplyFormSet = modelformset_factory(model=Reply, form=ReplyForm, extra=length_questions)
ReplyFormSet.form = staticmethod(curry(ReplyForm, instance=numPages))
if logged_user:
if request.method == 'POST':
formset = ReplyFormSet(request.POST, request.FILES)
if formset.is_valid():
new_instances = formset.save(commit=False)
for new_instance in new_instances:
new_instance.user = logged_user
new_instance.save()
return render_to_response('polls/access.html', {
'formset': formset,
'questions':questions,
'logged_user':logged_user,
'numPages' : numPages
})
else:
messages.add_message(request, messages.INFO, "L'ajout à bien été effectué !")
return render_to_response('polls/access.html', {
'formset': formset,
'questions':questions,
'logged_user':logged_user,
'numPages' : numPages
})
else:
formset = ReplyFormSet(queryset = Reply.objects.none())
return render_to_response('polls/access.html', {
'formset': formset,
'questions':questions,
'logged_user':logged_user,
'numPages' : numPages
})
else:
return HttpResponseRedirect('/login/')
My model allows me to answer several questions at the same time ..
My models.py
class Page(models.Model):
title = models.CharField(max_length=30)
def __str__(self):
return self.title
class Question(models.Model):
label = models.CharField(max_length=30)
page = models.ManyToManyField(Page)
def __str__(self):
return self.label
class Reply(models.Model):
question = models.ForeignKey(Question)
user = models.ForeignKey(Personne)
answer = models.CharField(max_length=30)
creationDate = models.DateTimeField(default=django.utils.timezone.now)
def __str__(self):
return str(self.answer)
How can I change the values instead adding in once again when there is already in the database?
Edit : screenshot of the problem
This saves 2 other lines more when I submit ..
Although you can edit current replies using your current approach by adding a hidden id field in your reply forms like
{% for reply in question.reply_set.all %}
<p>
<label for="answer">Réponse :</label>
<input type="text" id="id_form-{{ forloop.parentloop.counter0 }}-answer" name="form-{{ forloop.parentloop.counter0 }}-answer" value="{{ reply.answer }}"/>
<input type="hidden" id="id_form-{{ forloop.parentloop.counter0 }}-id" name="form-{{ forloop.parentloop.counter0 }}-id" value="{{ reply.id }}"/>
</p>
{% endfor %}
this will tell your ReplyFormset that these records already exist, so instead of creating new records, edit existing records.
Yet, most of the work you are doing manually, can be done using Django's built in functionality, i.e. Django Formsets (which you are using but not using right). Django Inline Formsets are the Django way of doing it.
Django form will create a new entry in db if you are calling a form without an instance.
form = MyForm()
On the contrary, if you want to update the existing entry in tha db pass the instance to the form.
form = MyForm(instance=myinstance)

Categories