Flask - Edit Prefilled Table Value and Resubmit - python

Overview
I am currently building a Flask application where a user will a upload a book cover and the OCR machine learning model will attempt to read the title and author of the book from the cover. The results are then shown in a html table. If the model is incorrect I want the user to be able to edit the data in the table and send it back to the flask app to be stored in the database.
I have tried a few solution such as placeholders and other solutions provided from stack overflow but none seem to allow me edit the data and submit the updated table. below are the solutions I have tried
Flask get and edit values
Send table data via form to Flask method
HTML
<link rel="stylesheet" href="{{ url_for('static', filename = 'styles/styles.css') }}">
<h1>Book Details</h1>
<body>
<img src="{{ image }}" alt="User Image" width="100%" height="100%" class="imagecenter">
</body>
&nbsp
<form action="{{ url_for('book_details') }}" method="post">
<table id = "bookDetails">
<tr>
<th> Title </th>
<td name="booktitle" contenteditable='true'>{{bookDetails['Title']}}</td>
<input type="hidden" name="booktitle" type="text" value="{{bookDetails['Title']}}"td>
</tr>
</table>
<p><input type="submit" value="Save to Database" class="uploadButton"></p>
</form>
Python
#app.route('/bookDetails', methods=["POST", 'GET'])
def book_details():
if request.method == "POST":
bookid = request.form["booktitle"]
print(bookid)
return render_template('book_details.html', bookDetails=exampleBookDetails, image = 'static/why_nations_fail.jpg')
Output
I am either getting the same value as originally given or the name of the field
Why Nations Fail
or
booktitle
When it needs to be "Why Nations Fail extra text".

Related

Python Flask - Make uploaded "file chosen" name stick on the HTML page

I have two input buttons. One is for uploading a file, and the other one is a submit button that adds the uploaded file to the database.
My problem is, after I submit the file, the first button that's used for uploading goes back to "No file chosen" next to the button. However, I want the uploaded file name to "stick" to the UI/html page as the file chosen.
Here is my File class:
class Files(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True, nullable=False)
data = db.Column(db.LargeBinary)
Here is my HTML code:
<td>
<form class="" action="{{url_for('main.upload_file')}}" method="post" enctype="multipart/form-data">
<input type="hidden" name="id" value="{{row.id}}">
<input style="margin-bottom: 5px;" type="file" accept=".csv" name="csvfile" id="upload" value ="{{row.name}}"> <br>
<input style="margin-bottom: 10px;" type="submit" name="" value="Submit"> <br>
</form>
<a href={{ url_for('main.files') }}>Go to Downloads Page</a>
<br>
</td>
I've tried making the value attribute equal to the file name getting passed in ex. value ="{{row.name}}" for the file type <input> above, but that doesn't keep the file chosen name on the page after submission either. I can't find any videos or posts that deal with this problem, so I would really appreciate any guidance. TIA!
I think setting a default value for an input field of type file is forbidden for security reasons.
However, if you want to keep the name of the selected file, you can aim for a transfer with AJAX. Here you can suppress the standard behavior of the form. The page is not reloaded and the form is not reset.
The example below shows you how it works.
Flask (app.py)
from flask import Flask
from flask import (
render_template,
request,
)
app = Flask(__name__)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/upload-file', methods=['POST'])
def upload_file():
if 'csvfile' in request.files:
file = request.files['csvfile']
if file.filename != '':
# handle file here!
return '', 200
return '', 400
HTML (templates/index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<form name="my-form" method="post">
<input type="file" name="csvfile" />
<input type="submit" />
</form>
<script type="text/javascript">
((uri) => {
const elem = document.querySelector('form[name="my-form"]');
elem.addEventListener('submit', evt => {
evt.preventDefault();
const formData = new FormData(evt.target);
fetch(uri, {
method: 'post',
body: formData
}).then(resp => {
if (resp.status === 200) {
console.log('Submit successful.');
}
});
})
})({{ url_for('.upload_file') | tojson }});
</script>
</body>
</html>
I don't know if it is possible to default the value the file-input field shows but what you could do is just have a row above the input field showing the currently uploaded/chosen file (if there is any). So something like this:
<td>
<form class="" action="{{url_for('main.upload_file')}}" method="post" enctype="multipart/form-data">
<input type="hidden" name="id" value="{{row.id}}">
{% if row.name %}
<p>Currently chosen file: {{row.name}}</p>
<p>You can select a new file below</p>
{% endif %}
<input style="margin-bottom: 5px;" type="file" accept=".csv" name="csvfile" id="upload"> <br>
<input style="margin-bottom: 10px;" type="submit" name="" value="Submit"> <br>
</form>
<a href={{ url_for('main.files') }}>Go to Downloads Page</a>
<br>
</td>

How can I ask user to upload image to an html page, insert this image to an sqlite3 table using flask, and then display this image using jinja?

so far this is what I came up with
#html ask user to input information including an image
<div class="form-group">
<input autocomplete="off" autofocus class="form-control" name="name" placeholder="name" type="text">
</div>
<div class="form-group">
<input class="form-control" name="subject" placeholder="subject" type="text">
</div>
<div class="form-group">
<input class="form-control" name="experience" placeholder="experience" type="text">
</div>
<div class="form-group">
<input class="form-control" name="phone" placeholder="puone-number" type="number">
</div>
<div class="form-group">
<input type="file" name="pic" id="pic">
</div>
<button class="btn btn-primary" type="submit">Register</button>
</form>
flask
#app.route("/register", methods=["GET", "POST"])
def register():
"""Show teacher registering menu"""
if request.method == "GET":
return render_template("register.html")
else:
# get the user input
name = request.form.get("name")
sub = request.form.get("subject")
exp = request.form.get("experience")
phone = request.form.get("phone")
f = request.files['pic']
pic = f.save(secure_filename(f.filename))
if not name or not sub or not exp or not phone:
return "404"
# insert in the database
sql = "INSERT INTO teachers (name, sub, exp, phone, pic) VALUES (?, ?, ?, ?, ?)"
db.execute(sql, name, sub, exp, phone, pic)
# inform the user for the success of the process
return render_template("success.html")
showing the results on html
<div>
{% for i in query %}
<div class="media bg-primary text-white">
<img class="align-self-end mr-3" src={{ i['pic'] }} alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0">Mr. {{ i['name'] }}</h5>
<ul class="list-group list-group-flush text-dark">
<li class="list-group-item">subject: {{ i['sub'] }},</li>
<li class="list-group-item">experience: {{ i['exp'] }},</li>
<li class="list-group-item">Contact number: {{ i['phone'] }}</li>
</ul>
</div>
</div>
<br>
{% endfor %}
</div>
but right now every time I try it I find the value of the image column in my sql table to be NULL.
How can I fix that
pic = f.save(secure_filename(f.filename))
The save method returns None, so here pic will be None.
I think you intended to write its filename to the database, so perhaps change this to:
pic = secure_filename(f.filename)
f.save(pic)
Now pic is the filename on your server, so you just need to reconstruct this wherever the file is viewed.
Of course be aware that this uses the filename of the file which was uploaded by the user. You may wish to avoid this, incase of duplicates or just for cleanliness. See this other answer I wrote regarding that.
EDIT: Regarding your template.
When it comes to loading the picture in the template, let's assume the filename came through as image.jpg, and you use your exisiting code:
<img src={{ i['pic'] }}>
You could view the source of the rendered page, and see:
<img src=image.jpg>
Two problems with this:
that attribute should have quotes (<img src="image.jpg">)
that's trying to load the file from whichever path is rendered in the browser, so if the URL was http://example.com/subdir/ it's looking for the image at http://example.com/subdir/image.jpg. This can also be verified in the Network tab of your browsers dev tools.
The solution, build the URL with flask's url_for function:
<img src="{{ url_for('static', filename=i['pic']) }}">
This, of course, assumes that you've saved the file to the static directory on your server. You may wish to ensure this in the python code:
import os
# ...
pic = secure_filename(f.filename)
f.save(os.path.join('static', pic))

How to return flask output to the same html page

I have an html file which reads like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Robots Uploader</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<section id="content">
<section class="module w100P">
<div id="error_bar" style = "display:none" class="message-bar error">
<p><span class="icon">Error:</span> Uh-oh, something broke! Close</p>
</div>
<div id="success_bar" style="display:none" class="message-bar success">
<p><span class="icon">Success:</span> Your changes have been made. Close</p>
</div>
<div class="module-inner">
<h3>DailyHunt Robots Uploader</h3>
<div class="module-content frm">
<form action="http://localhost:5000/uploadFile" method="post" enctype="multipart/form-data">
<table>
<tr>
<td>
<select name ="domain">
<option selected>Select Domain</option>
<option value="m">m</option>
<option value="www">www/option>
</select>
</td>
<td>
<input type="file" name="robots" accept='robots.txt'>
<button type="submit">Upload</button>
</td>
</tr>
</table>
</form>
<form action="http://localhost:5000/uploadApk" method="post" enctype="multipart/form-data">
<table>
<tr>
<td>
Enter APK you want to upload:
</td>
<td>
<input type="file" name="apk">
<button type="submit">Upload</button>
</td>
</table>
</form>
</div>
</div>
</section>
</section>
</section>
</body>
</html>
on hitting submit, it hits the flask api engine, where the 2 functions to be hit are defined as
#app.route('/uploadFile', methods=['POST'])
def upload_robots():
domain = request.form.get('domain')
if not domain:
return "Domain does not exist"
f = request.files[ROBOTS_IDENTIFIER]
if f.filename!=ROBOTS_FILE_NAME:
return "Incorrect file name. File name has to be robots.txt"
if domain == 'm':
robots_file_path = ROBOTS_MOBILE_FILE_PATH
elif domain == 'www':
robots_file_path = ROBOTS_WEB_FILE_PATH
else:
return "Domain not recognized"
filename = secure_filename(f.filename)
if os.path.isfile(robots_file_path + ROBOTS_FILE_NAME):
folder_name = datetime.utcfromtimestamp(int(os.path.getmtime(robots_file_path + ROBOTS_FILE_NAME))).strftime('%Y-%m-%d %H:%M:%S')
os.makedirs(robots_file_path + folder_name)
shutil.move(robots_file_path + ROBOTS_FILE_NAME, robots_file_path + folder_name +'/' + ROBOTS_FILE_NAME)
f.save(os.path.join(robots_file_path, ROBOTS_FILE_NAME))
return "file uploaded successfully, This will reflect in prod after the next cron cycle"
#app.route('/uploadApk', methods=['POST'])
def upload_apk():
f = request.files[APK_IDENTIFIER]
if f.filename.split('.')[-1] != 'apk':
return "upload file type must be apk"
filename = secure_filename(f.filename)
fname = '.'.join(f.filename.split('.')[0:-1])
rename = False
while os.path.isfile(APK_FILE_PATH + fname + '.apk'):
rename = True
fname += '_'
if rename:
shutil.move(APK_FILE_PATH + f.filename, APK_FILE_PATH + fname + '.apk')
f.save(os.path.join(APK_FILE_PATH, filename))
return "APK uploaded successfully"
Now when I hit submit the api returns some texts and it gets directed to a new page with just the text rendered. I would like this to remain in the same page and display the error_bar or success_bar divs in the html rather than it being redirected to a new page. Is it possible to achieve this without rendering a template or creating a new static html page?
Let's assume that your current page is: index.html.
I thought about two ways for resolving.
The first way,
After making request to your API, just render_template index.html again, including extra data (error=True/False, message=...) and you have update your index.html to check condition when receive extra data to display the error/success message.
=> By this way, you should modify the template and use Flask's render_template.
I prefer this way because of having control of the template (index.html) it just needs small update.
The second way, make request by using AJAX (XHR), when click submit button, you prevent the default form submit and use AJAX to request, then receive response and display the message.
The AJAX script can stay in index.html or another *.js where your index.html can locate.
=> By this way, you are working in non-Flask dependent way, by using Ajax you make request and modify the document (index.html) by using a little Javascript.

Getting multiple html fields with same name using getlist with flask in python

I have a form with dynamically added rows that have the same name. I try to use getlist but I only get the first value.
Here is my HTML form code:
<html>
<div>
<form method=POST>
</div>
<table id="linkTable">
<tbody><tr class="tr_clone" id="addrow">
<td>First: </td>
<td><input class="from-field" id="from" type="text" name="first"></td>
<td>Last: </td>
<td><input class="to-field" id="to" type="text" name="last"></td>
<td>Email: </td>
<td><input class="port-field" id="ports" type="text" name="email"></td>
</tr>
</tbody></table>
<script>
$("#addbtn").click(function(){
$("#addrow").clone().find("input:text").val("").end().prependTo("#linkTable");
})
</script>
</form>
</html>
Here is my python code:
from flask import Flask, request, render_template
for arg in request.form:
print arg, request.form.getlist(arg)
Can anyone explain that even if I use getlist, I only get the first value?
Thanks in advance
It's convenient to use zip() every time you want to parse same name attribute in html. Have a look at this:
#app.route('/creation', methods=['GET', 'POST'])
def create():
try:
if request.method == 'POST':
for first, last, email in zip(request.form.getlist('first'),
request.form.getlist('last'),
request.form.getlist('email')):
print(first, last, email)
else:
return render_template('create.html')
except Exception as e:
print(str(e))
After looking for a long time, was able to answer my own question by looking on the web.
I had the following:
<html>
<!-- note how the form has been moved out of the div so that the navigator does not close the div -->
<form method=POST>
<table id="linkTable">
<tbody><tr class="tr_clone" id="addrow">
<td>First: </td>
<td><input class="from-field" id="from" type="text" name="first"></td>
<td>Last: </td>
<td><input class="to-field" id="to" type="text" name="last"></td>
<td>Email: </td>
<td><input class="port-field" id="ports" type="text" name="email"></td>
</tr>
</tbody></table>
<script>
$("#addbtn").click(function(){
$("#addrow").clone().find("input:text").val("").end().prependTo("#linkTable");
})
</script>
</form>
</html>
It turns out that closing a div will close a form tag so rows were being dynamically added outside the form. By moving the form directive outside of the div, I started getting multiple values when the form was submitted
I also eventually found a more detailed answer at Form Issue (closing itself early in table)

How to render field data into a template file using django?

I am trying to render data into a template file using the following Code. The error I encounter is something like :
This page contains the following errors:
error on line 13 at column 16: AttValue: " or ' expected
Below is a rendering of the page up to the first error.
Name,Author,Status
Code
def editbook(request):
if request.method == 'GET':
name = request.GET.get('name',False)
Details = bookInfo.objects.all().filter(Name=name)
id = Details.values_list('id',flat=True)
Name = Details.values_list('Name',flat=True)
Author = Details.values_list('Author',flat=True)
Status = Details.values_list('Status',flat=True)
return render(request, 'app/add.html', {'Name' : Name, 'Author' : Author, 'Status' : Status}, content_type="application/xhtml+xml")
Template Code
<html>
<head>
<title>Add</title>
</head>
<body>
<form action="add/" method="post">
{% csrf_token %}
<p style="font-family:Courier New;color:teal">Name <input type="text" placeholder="Name of the book" name="name"></input></p>
<p style="font-family:Courier New;color:teal">Author <input type="text" placeholder="Author of the book" name="author"></input></p>
<p style="font-family:Courier New; color:teal"> Status
<select name="status">
<option value=1>Read</option>
<option value=1>Unread</option>
</select>
</p>
<input type="submit" id="booksubmit" value="Add/Edit Book"></input>
</form>
</body>
</html>
I searched through Google and I found that this somewhat like XML parsing error (Please correct me if I am wrong). Now I am stuck at this position. Please help.
EDIT Here the form for adding the book has a different method for saving the field data into database.
Your HTML is malformed for whatever type is is, html and html5.
And to be picky, your python code should be refactored as well.
Normally we define variables with lowercase letter instead of a capital so
the variables Details, Name, Author, Status should be details, name, author, status.
Further more, is your class name bookInfo spelled like that?
Classes in python should start with a capital letter so bookInfo should be BookInfo.
The proper HTML5 is this:
<html>
<head>
<title>Add</title>
</head>
<body>
<form action="add/" method="post">
{% csrf_token %}
<p style="font-family:Courier New;color:teal;">Name <input type="text" placeholder="Name of the book" name="name" /></p>
<p style="font-family:Courier New;color:teal;">Author <input type="text" placeholder="Author of the book" name="author" /></p>
<p style="font-family:Courier New; color:teal;"> Status
<select name="status">
<option value=1>Read</option>
<option value=1>Unread</option>
</select>
</p>
<input type="submit" id="booksubmit" value="Add/Edit Book" />
</form>
</body>
</html>
If you're not using HTML5 and that depends on the Doctype you've defined.
You can't use placeholders in your input fields.
The inputs need to get closed with a /> and not a </input>.
The inline styles you've provided in your html is incomplete:
style="font-family:Courier New; color:teal"
should be
style="font-family:Courier New; color:teal;"
The return you're using doesn't need a content_type you can drop that.
You're not using your template variables anywhere so that's not it but if you would like to start using them the syntax for the template language is {{ variable_name }} and in your case that would be (until you refactor)
{{ Name }}, {{ Status }} for example.
Also you're seeing this error because of the content_type since you're actively telling the browser to parse the document as xhtml+xml and that's XHTML with rules, which you're effectively breaking.
For adding the value from your Django app to your input fields do this, (without a Django Form)
<input type="text" value="{{ Name }}" />
But I would recommend using a Django Form instead.
I think you're getting this error because you're template is not 'well formed'. My guess is that there's an error in the xml that's being output.
You've declared the content type of your http response to be application/xhtml+xml. xhtml requires you to put quotes around all your attributes. You're probably missing/adding a quotation mark somewhere. I can't see from your template where that is.
Either check what your template variables ({{ Name }}, {{ Author }} and {{ Status }}) are outputting to see if they're adding in a stray quotation mark, or try changing the content type (maybe just drop the content_type parameter you're passing to the render function).

Categories