posting Flask wtforms in loop and saving data - python

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)

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

Retrieving data from Django HTML form and passing it to a MySQL database

I have a database with multiple user names and phone numbers attached to each user. On the Django template that I created the user is presented with a list of names, and the user can click on a name, or multiple names, and the database is to respond with the phone number assigned to the name. However, I am using a for loop within the Django template to iterate over the names in the database to display for the user, as the count can change. It works correctly when I select one name, however, if I select multiple name, it takes the last name selected versus displaying all names. This error is due to my for loop solution which has the same "name" assigned to all inputs. Anyone have an idea on how I can approach this?
My View form:
def select_contact(request):
alldata = identity_log.objects.values_list("first_name", flat=True)
#https://docs.djangoproject.com/en/4.0/ref/models/querysets/
checkform = contact_form(request.POST or None)
context = {'alldata': alldata}
print(checkform)
display_type = request.POST.get("contact_option", None)
if display_type in alldata:
print(display_type)
return render(request, 'message_me/select_contact.html', context)
My template:
{% extends "base.html" %}
{% load static %}
{% block body %}
<p>Please select your favorite Web language:</p>
{% for x in alldata %}
<form id="contact_option" role="form" action="" method="POST">
{% csrf_token %}
  <input type="checkbox" id="contact_option" name="contact_option" value="{{x}}">
  <label for="contact_option">{{x}}</label><br>
{% endfor %}
<div class="row">
<div class="col-md-12"> <input type="submit" name="submit" class="btn btn-success btn-send pt-2 btn-block " value="Continue"> </div>
</div>
</form>
{% endblock %}
image of the HTML doc
Replace :
<input type="checkbox" id="contact_option" name="contact_option" value="{{x}}">
with
<input type="checkbox" id="contact_option{{x}}" name="contact_option{{x}}" value="{{x}}">
Now you have a unique id for each user.

Getting data from the session[] Python, Flask

I'm trying to make simple cart system to the online shop using Python and framework - Flask.
I'm getting data from form in products.html then writing it into the session. I have a problem with getting this data and returning it to the cart.html - I'm get just clear page instead of names of the products i added to the cart.
products.html
{% for el in products %}
<p>Name {{ el.product_name }}</p>
<p>Description {{ el.product_description }}</p>
<p>Image </p> <img width="200" height="200" src="data:;base64,{{ el.product_img }}">
<p>Cost: {{ el.product_cost }} тенге.</p>
<form method="post" action="/cart">
<input type="hidden" name="cart_prod_name" value="{{ el.product_name }}">
<input type="submit" value="Add to cart">
</form>
{% endfor %}
Python function cart():
#app.route('/cart', methods=['POST', 'GET'])
def cart():
if 'cart' not in session:
session['cart'] = []
if request.method == 'POST':
cart_prod_name = request.form['cart_prod_name']
session['cart'] += cart_prod_name
return redirect('/cart')
if request.method == 'GET':
cart_products = session['cart']
return render_template('cart.html', cart_products=cart_products)
cart.html:
{% for cart_product in cart_products %}
<p>{{ cart_product.order_prod_name }}</p>
{% endfor %}
From flask.session docs:
Be advised that modifications on mutable structures are not picked up
automatically, in that situation you have to explicitly set the
attribute to True yourself.
your carts holder object is a mutable structure == list so you have to set after changes
session.modified = True
I sloved that problem. Exactly I needed to iterate cart_product in cart_roducts and output cart_product and don't call it to output order_prod_name.
Fixed cart.html:
{% for cart_product in cart_products %}
<p>{{ cart_product }}</p>
{% endfor %}

Django getting right return value for post.request

I am trying to get the product id using request.post in Django. I am currently testing using the console, but the only product_id value I am returned is 1.
This is the particular function in the view:
def test_view(request):
cart_obj, new_obj = Cart.objects.new_or_get(request)
my_carts_current_entries = Entry.objects.filter(cart=cart_obj)
products = Product.objects.all()
if request.POST:
product_id = request.POST.get('product_id')
entry_quantity = request.POST.get('entry_quantity')
product_obj = Product.objects.get(id=product_id)
print(product_id)
# print(entry_quantity)
# Entry.objects.create(cart=cart_obj, product=product_obj, quantity=product_quantity)
return render(request, 'carts/test.html', {'cart_obj': cart_obj, 'my_carts_current_entries': my_carts_current_entries,
'products': products})
This is the html on the template.
<form method="POST">
<br>
{% csrf_token %}
{% for product in products %}
{{ product.name }} <br>
<button>Add to Basket</button>
{{ product.id }}
<input type="hidden" name='product_id' value='{{ product.id }}'>
<br>
{% endfor %}
</form>
Your problem is that you have as many <input> tags within 1 <form> as many products you have displayed. They all have the same name, so you always get the value of the first one.
I'd recommend getting rid of <input> and attaching the value of product.id to the button itself (or <input type="submit"> to be exact). Here's the more descriptive explanation:
How can I build multiple submit buttons django form?
An alternative would be to change your code to have multiple forms, like that:
{% for product in products %}
<form method="POST">
{% csrf_token %}
{{ product.name }}
<br/>
<button>Add to Basket</button>
{{ product.id }}
<input type="hidden" name='product_id' value='{{ product.id }}'>
</form>
<br/>
{% endfor %}

Django - Submitting form with multiple inputs for same field

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 ?

Categories