Intro:
I'm making an app with many records that allows you to have a ranking/upvote system
Pic attached:
The problem: I face is that each time I press 'Upvote +1', the page gets refreshed - and I want to solve it.
Here is my Flask app:
def form():
form = LoginForm()
value = 0
records = Row.query.order_by(Row.id.desc())
if form.validate_on_submit(): #if we submit something -> pick the ID of the item
for i in dict(request.form):
id_of_record_to_change = i
item = Row.query.filter_by(id=id_of_record_to_change) #filter item by the ID
new_value = item.first().rating + 1 #add +1 to the rating value
item.first().rating = new_value
db.session.commit() #record to database
return render_template('form.html', records=records, form=form, value=value)
Here is my html:
{{form.csrf_token }}
{% for item in records %}
{{ item.rating }}
<input type="hidden" id="intval" name="intval" value="{{ item.rating }}"> <!-- rating we want to increase -->
<input type="hidden" id="id" name="id" value="{{ item.id }}"> <!-- ID we want to pass -->
<input type="submit" id="target" name = "{{ item.id }}" value="Upvote +1"> <!-- submit action -->
{% endfor %}
</form>
Thank you!
I managed to solve my problem thanks to #RaniSharim who posted a link to the proper tutorial
Here is how my code changed:
In Flask app I added the following route to 'get/send/process' the data:
######## Data fetch ############
#app.route('/getdata/<index_no>', methods=['GET','POST'])
def data_get(index_no):
if request.method == 'GET': # POST request
item = Row.query.filter_by(id=index_no) #filter item by the ID
new_value = item.first().rating + 1 #add +1 to the rating value
item.first().rating = new_value
db.session.commit() #record to database
return '%s'%(new_value)
#################################
And my html file now looks like this:
{% for item in records %}
<hr>
<button id ="{{item.id}}" onclick="SomeFunc()">👍 {{ item.rating }}</button>
<!--- ################################ -->
<script >
function SomeFunc() {
var button = event.target;
var element_id = button.id;
index = element_id
fetch(`/getdata/${index}`)
.then(function(response) {
return response.text();
}).then(function(text) {
document.getElementById(index).innerText = '👍 ' + text;
});
} </script>
<!--- ################################ -->
{% endfor %}
In this way we can avoid page reload + we imitate the ranking change with JS
Thanks!
Related
In my page I have two different forms. I want to read the information from the first form whenever I press a button in the second form. Is this possible?
First form:
<form id="loadData" method="post" action="/loadData">
{% if day %}
Day: <input id="day" name="day" size="5px" value={{day}}>
Month: <input id="month" name="month" size="5px" value={{month}}>
Year: <input id="year" name="year" size="5px" value={{year}}>
{% else %}
Day: <input id="day" name="day" size="5px">
Month: <input id="month" name="month" size="5px">
Year: <input id="year" name="year" size="5px">
{% endif %}
.
.
.
</form>
Second form:
<form id="createFile" method="post" action="/createFile">
<button type="submit">Create</button>
</form>
By clicking the button in the second form I want to read the information in the first one to create a file containing all those information.
I tried something like
#app.route("/createFile", methods=["GET", "POST"])
def createFile():
if request.method == "POST":
day = request.form["day"]
month = request.form["month"]
year = request.form["year"]
return redirect('/')
but I can't manage to read those variable properly.
Despite corresponding in the comments i'm not entirely sure this is your end goal, but let's give it a go?
basically all i did was copy stuff from the links attached in the comment.
a.html:
<form id="form_id" action="/loadData" method="POST">
<input type="text" name="q" value="abcd">
<button type="submit">loadData</button>
</form>
<button id="createFile"> createFile </button>
<script>
function post(path, params, method = 'post') {
// The rest of this code assumes you are not using a library.
// It can be made less verbose if you use one.
const form = document.createElement('form');
form.method = method;
form.action = path;
for (const key in params) {
if (params.hasOwnProperty(key)) {
const hiddenField = document.createElement('input');
hiddenField.type = 'hidden';
hiddenField.name = key;
hiddenField.value = params[key];
form.appendChild(hiddenField);
}
}
document.body.appendChild(form);
form.submit();
}
var form_1 = document.querySelector('#form_id')
document.querySelector('#createFile').addEventListener('click', (e) => {
var data = Object.fromEntries(new FormData(form_1).entries());
post("/createFile",data)
});
</script>
app.py:
from crypt import methods
from flask import Flask, request
app = Flask(__name__)
#app.route("/loadData", methods=["POST"])
def loadData():
data = request.get_data()
return f"<h1 style='color:blue'>loadData data: {data}</h1>"
#app.route("/createFile", methods=["POST"])
def createFile():
data = request.get_data()
return f"<h1 style='color:red'>createFile data: {data}</h1>"
if __name__ == "__main__":
app.run(host='0.0.0.0')
page looks like this:
clicking on loadData:
clicking on createFile:
this whole setup is pretty convoluted and unnecessarily complex. what are you trying to achieve?
My website lets a teacher create multiple questions for an assignment. Once the assignment is created, a student can come and write an answer for each question.
My problem is that only one answer is saved for each entry. For example, there are two questions in an assignment. The answer for question 1 is "asdf", the answer for question 2 is "fdsa". The content for both answers will be saved as "asdf", when they should be unique.
I have tried printing request.form and it looks like this (excluding the csrf_tokens):
('code_content', 'asdf'), ('code_content', 'fdsa'), ('submit', 'Submit Assignment')]
So I know that fdsa is still in there somewhere, but I'm not able to access it. If this is important, there were two csrf_tokens that were the exact same when printing request.form.
To get that data, I created a 'GetQuestionContent()' form for as many questions in the assignment. Like this:
questions = []
question_content_forms = []
for question in Question.query.all():
if int(question.owner) == int(assignment_id):
questions.append(question)
question_content_forms = [GetQuestionContent() for item in range(0, len(questions))]
Then, in the HTML, I write the form like this:
<form method="POST">
{% for question in questions %}
<div class="accordion-item">
<h2 class="accordion-header" id="heading{{ question.id }}">
<button class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse{{ question.id }}"
aria-expanded="false"
aria-controls="collapse{{ question.id }}">
{{ question.title }}
</button>
</h2>
<div id="collapse{{ question.id }}"
class="accordion-collapse collapse"
aria-labelledby="heading{{ question.id }}"
data-bs-parent="#questionsAccordion">
<p>
<small>
<strong>Question Description</strong>
<br>
{% if question.description != "" %}
{{ question.description|safe }}
{% else %}
<em>The assignment creator did not provide a description for this question.</em>
{% endif %}
</small>
</p>
{{ question_content_forms[loop.index - 1].hidden_tag() }}
{% if question.type == "code" %}
<div class="code-box">
{{ question_content_forms[loop.index - 1].code_content(id = "editor") }}
</div>
{% endif %}
{% if question.type == "text" %}
{{ question_content_forms[loop.index - 1].text_content|safe }}
{% endif %}
</div>
</div>
{% endfor %}
{% if current_user.account_type == "Student" %}
{{ submit_button.submit(class="btn btn-outline-success mt-3", value="Submit Assignment")}}
{% endif %}
</form>
When the user presses submit, I want to get every answer and put it into their own entry for the StudentQuestionSubmission table in my database. This is what that code looks like:
if request.method == "POST" and submit_button.data:
print(request.form)
index = 0
for question in questions:
question_to_submit = StudentQuestionSubmission(question_id = int(question.id),
student_id = int(current_user.id))
if question.type == "code":
question_to_submit.question_content = question_content_forms[index].code_content.data
elif question.type == "text":
question_to_submit.question_content = question_content_forms[index].text_content.data
print(f"\n\n{ question_content_forms[index].code_content.data } \n \
{ question_content_forms[index].text_content.data } \n\n")
index += 1
db.session.add(question_to_submit)
assignment_to_submit = StudentAssignmentSubmission(assignment_id = int(assignment_id),
student_id = int(current_user.id),
has_submitted = True,
submission_date = date.today())
db.session.add(assignment_to_submit)
db.session.commit()
flash(f"'{assignment.name}' has been succesfully submitted.")
return redirect(url_for('classroom_assignments_list', class_id = class_id, paper_id = paper_id))
You can see that I print the data of the textboxes. It will output 'asdf' on both iterations even if I wrote something entirely different for question 2.
I appreciate any help. Thank you.
EDIT: 'hackily' getting the content from multiple instances of the same form using request.form.to_dict(flat=False)['your_form_field']
Here's the new code:
if request.method == "POST" and submit_button.data:
code_content = request.form.to_dict(flat=False)['code_content']
text_content = request.form.to_dict(flat=False)['text_content']
code_content_index = 0
text_content_index = 0
for question in questions:
question_to_submit = StudentQuestionSubmission(question_id = int(question.id),
student_id = int(current_user.id))
if question.type == "code":
question_to_submit.question_content = code_content[code_content_index]
code_content_index += 1
elif question.type == "text":
question_to_submit.question_content = text_content[text_content_index]
text_content_index += 1
print(f"\n\n{ question_to_submit.question_content } \n\n")
db.session.add(question_to_submit)
assignment_to_submit = StudentAssignmentSubmission(assignment_id = int(assignment_id),
student_id = int(current_user.id),
has_submitted = True,
submission_date = date.today())
db.session.add(assignment_to_submit)
db.session.commit()
flash(f"'{assignment.name}' has been succesfully submitted.")
return redirect(url_for('classroom_assignments_list', class_id = class_id, paper_id = paper_id))
My suggestion is not to create multiple forms, but to dynamically create one form for all the necessary questions.
In this case it is possible to assign a unique id to each field, which indicates that it belongs to the question.
The following example shows you a possible implementation.
An answer field is added to the form for each question, which has the id of the question in its name. Thus, the respective question can be assigned when rendering and querying the input data.
Flask
from flask import (
Flask,
render_template,
request
)
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import TextAreaField, SubmitField
from wtforms.validators import DataRequired
import random
app = Flask(__name__)
app.secret_key = 'your secret here'
db = SQLAlchemy(app)
class Question(db.Model):
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String)
owner = db.Column(db.Integer)
title = db.Column(db.String)
description = db.Column(db.Text)
with app.app_context():
db.drop_all()
db.create_all()
qs = [Question(
type=random.choice(['code', 'text']),
owner=1,
title=f'Question {i+1}',
description=f'Your description here.'
) for i in range(3)]
db.session.add_all(qs)
db.session.commit()
def form_factory(qs):
class F(FlaskForm):
submit = SubmitField('Submit')
for q in qs:
field = TextAreaField(
q.type.title(),
validators=[
# DataRequired()
],
)
setattr(F, f'q-{q.id}', field)
return F
#app.route('/', methods=['GET', 'POST'])
def index():
questions = Question.query.filter_by(owner=1).all()
form = form_factory(questions)(request.form)
if form.validate_on_submit():
for q in questions:
field = getattr(form, f'q-{q.id}')
print(f'Question-{q.id}\n{field.data}\n')
return render_template('index.html', **locals())
HTML (templates/index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Index</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
</head>
<body>
<div class="container my-3">
<form method="post">
{{ form.hidden_tag() }}
<div class="accordion mb-3" id="accordionExample">
{% for q in questions -%}
<div class="accordion-item">
<h2 class="accordion-header" id="heading-{{loop.index}}">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse-{{loop.index}}"
aria-expanded="false"
aria-controls="collapse-{{loop.index}}"
>
{{ q.title }}
</button>
</h2>
<div
id="collapse-{{loop.index}}"
class="accordion-collapse collapse"
aria-labelledby="heading-{{loop.index}}"
data-bs-parent="#accordionExample"
>
<div class="accordion-body">
<div class="mb-3">
{{ q.description|safe }}
</div>
{% set field = form|attr('q-{}'.format(q.id)) -%}
<div>
{{ field.label(class_='form-label') }}
{{ field(class_='form-control' + ('', ' editor')[q.type=='code']) }}
</div>
</div>
</div>
</div>
{% endfor -%}
</div>
<div class="d-grid gap-2">
{{ form.submit(class_='btn btn-primary') }}
</div>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
</body>
</html>
Currently every time I click on a product link on the website the entire database column "views" increases by 1 where as I need it so that just the row increases by 1. The link opens in an external website. The link is opened by clicking on the product image. Any suggestions would be greatly appreciated
Models.py
class T_shirt(models.Model):
Images = models.ImageField()
Titles = models.CharField(max_length=250, primary_key=True)
Prices = models.CharField(max_length=250)
Link = models.CharField(max_length=250)
Website = models.CharField(max_length=250)
Brand = models.CharField(max_length=250)
views = models.IntegerField(default=0)
views.py
def t_detail(request):
if request.method == 'POST':
T_shirt.objects.update(views=F('views') + 1)
if 'q' in request.GET:
q = request.GET['q']
owner_obj = T_shirt.objects.filter(Titles__icontains=q)
else:
owner_obj = T_shirt.objects.all()
p = Paginator(owner_obj, 20)
page_num = request.GET.get('page', 1)
try:
page = p.page(page_num)
except EmptyPage:
page = p.page(1)
return render(request, 'tshirtshtml.html', {"owner_obj": page})
html
<ul class="products">
{% for v in owner_obj %}
<div class="container">
<form method="POST" type="submit">
<button style="border: none;">
{% csrf_token %}
<a href="{{ v.Link }}" rel="noopener noreferrer">
<img src="{{ v.Images }}" width="150" height="150">
</a>
</button>
</form>
<figcaption> {{ v.Titles }} </figcaption>
<figcaption> <b>{{ v.Prices }}</b></figcaption>
</div>
{% endfor %}
Updated Code
def t_detail(request):
if request.method == 'POST':
T_shirt.objects.filter(Titles=request.POST.get('tshirt_id')).update(views=F('views') + 1)
if 'q' in request.GET:
q = request.GET['q']
owner_obj = T_shirt.objects.filter(Titles__icontains=q)
else:
owner_obj = T_shirt.objects.all()
p = Paginator(owner_obj, 20)
page_num = request.GET.get('page', 1)
try:
page = p.page(page_num)
except EmptyPage:
page = p.page(1)
return render(request, 'tshirtshtml.html', {"owner_obj": page})
you need some identifier to fetch that particular row from db, right now you are updating all the rows with this logic:
if request.method == 'POST':
T_shirt.objects.update(views=F('views') + 1)
for the current implementation, since you are using forms, you can add a hidden element inside the forms like:
<form method="POST" type="submit">
<button style="border: none;">
{% csrf_token %}
<input type="hidden" name="tshirt_id" value="{{ v.id }}" />
<a href="{{ v.Link }}" rel="noopener noreferrer">
<img src="{{ v.Images }}" width="150" height="150">
</a>
</button>
</form>
then change your view to get tshirt_id from request and update that row
if request.method == 'POST':
T_shirt.objects.filter(id=request.POST.get('tshirt_id')).update(views=F('views') + 1)
I am trying to have an input field in the template that the user enters a query and that query goes to the views.py
and from there i m taking the query and pass it as argument to the bash script.
This is what i have for now.
views.py
def home(request):
if request.method == 'POST':
try:
query = request.POST['query']
test = subprocess.check_call(['home/.../bash.sh',
query])
return render(request, 'base.html', {'input': test})
except KeyError:
return HttpResponse("Nothing was submitted!")
base.html
<form action="/" method="post">
{% csrf_token %}
<input type="hidden" name="query" value="{{ input }}">
<input type="submit" value="Submit">
</form>
I am stuck right here..i don't know if i shout request.POST or something else much simpler...cause i don't want to use a form.
I figure it out by creating a script in the html template.
<script>
$(".opener").click(function () {
var thead = $("#mytable").find("thead");
thead.find('th').last().remove();
thead = thead.html();
var row = $(this).parents('tr');
row.find('td').last().remove();
row = row.html();
var table = $(document.createElement('table'));
table.append('<thead>' + thead + '</thead>');
table.append('<tbody><tr>' + row + '</tr></tbody>')
$(".modal").html("").append(table);
$(".modal").dialog({width: 'auto', position: 'top'});
});
</script>
I have the following view,template and form through which i am implementing the formset. I intend to implement multiple formsets in a single view/template but my number of formsets is dynamic based on user input. How can i have multiple dynamic number of formsets in this code?
Can i do this with dictionary element i.e by creating a dictionary of formsets??
My view is as follows:
def show (request):
b = request.session["s1"] # count of no of period ids
c = request.session["s2"] # account number inserted by user
d = request.session["s3"] # year inserted by customer
a = account_period.objects.filter(year=d).values('id')
e = account_period.objects.filter(year=d).values('month')
f = account_period.objects.filter(id = a).values('year')
butt = formset_factory(bu, extra=b)
if request.method == 'POST'
formset = butt(request.POST)
if formset.is_valid():
z = account_tab.objects.get(account_number=c)
pr = date.today()
i=0
for form in formset.forms:
x = form.cleaned_data['value']
y = account_period.objects.get(id=a[i:(i+1)])
try:
uip = budget.objects.get(account_no = c,account_period = a[i:(i+1)])
if uip.budget_amount != x
uip.budget_amount = x
uip.save()
except budget.DoesNotExist:
w = budget(account_no = z, account_period = y, budget_amount = x, created_by_login = 'me')
w.save()
i=i+1
pass
return HttpResponse('thanks')
form is
class bu(forms.Form):
value = forms.CharField()
template is
<html>
<head>
<title>BUDGET</title>
</head>
<body>
<p>BUDGET MANAGEMENTS</p>
<p>Your Account Number is : {{ account_number }}.</p> <p>You Chose {{ period }} {{month}} as period<p>
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<form action="." method="post">{% csrf_token %}
{{ formset.management_form }}
<table>
{% for form in formset.forms %}
{{ form }}
{% endfor %}
</table>
<input type="submit" value="Submit">
</form>
</body>
</html>
#
#rohan
my GET method return has many variables which has to be passed into template so i tried to pass the dictionary formsetlist (after appying the changes u suggested) in following 2ways
1)
formset = butt( return render_to_response('budgetfinalform.html', {'account_number': c,'period':d,'month':e,'year':f,'formset': formset},context_instance=RequestContext(request))
2)
ctx ={'formsetlist': formset}
formset = butt( return render_to_response('budgetfinalform.html', {'account_number': c,'period':d,'month':e,'year':f,ctx,context_instance=RequestContext(request))
but obtained "unboundlocalerror : local variable 'formset' referenced before assignment"
I would do something like:
def show (request):
#initial view processing
# fomset_count taken as parameter or user input
formsetlist = []
#create formset list
for i in range(0, formset_count):
formsetlist.append(formset_factory(bu, extra=b))
# other view related code
# for GET method
ctx = { 'formsetlist': formset }
return render_to_response('your_template_name', ctx,
context_instance = RequestContext(request)
)
In template:
<form action="." method="post">{% csrf_token %}
{% for formset in formsetlist %}
{{ formset.management_form }}
<table>
{% for form in formset.forms %}
{{ form }}
{% endfor %}
</table>
{%endfor%}
<input type="submit" value="Submit">