I'm new with s3 and trying to upload some files but the I'm getting The system cannot find the file specified: <hashed_file_name>.jpg I understand the issue. When the file is saved at the root, everything is fine. But I don't want to save the file. I want to upload it directly after the action at the form.
def upload_to_s3(file_to_upload, s3_upload_folder):
s3 = boto3.resource('s3',
aws_access_key_id=app.config['ACCESS_KEY_ID'],
aws_secret_access_key=app.config['SECRET_ACCESS_KEY'])
s3.meta.client.upload_file(file_to_upload, app.config['BUCKET_NAME'], s3_upload_folder)
def _user_img_folder(form, file_name):
username = session['name']
vacation_name = slugify(form.test_name.data)
directory = os.path.join(username, test_name)
directory = os.path.join(UPLOAD_FOLDER, directory)
return directory + '/' + file_name
#app.route('/post', methods=['GET', 'POST'])
def test():
if _is_image():
uploaded_images = request.files.getlist('photo')
for image in uploaded_images:
processed_image_name = _hash_image_name(image) # Returns hashed filename with extension
directory = _user_img_folder(form, processed_image_name)
upload_to_s3(str(processed_image_name), str(directory))
return render_template('test.html', form=form, error=error)
Thank you for your help.
EDIT 1:
{# Heavily edited #}
{% extends '_base.html' %}
{% block content %}
<form class="logVacation" enctype=multipart/form-data role="form" method="post" action="/post">
{{ form.csrf_token }}
{{ form.vacation_name(placeholder="Name Your Vacation")}}
<br>
{{ form.location(placeholder="Where was it?") }}
<br>
{{ form.with_who(placeholder='Who was with you') }}
<br><br>
{{ form.description(placeholder="Tell us about your vacation... or not.") }}<br>
{{ form.when(class="datepicker", placeholder="when?") }}
<br><br>
{{ form.photo(multiple="multiple") }}
<br>
<button class="btn btn-sm btn-success" value="upload" type="submit">Done</button>
</form>
{% endblock %}
Found the answer:
I've changed the
s3.meta.client.upload_file(file_to_upload, app.config['BUCKET_NAME'], s3_upload_folder)
to:
s3.Object(app.config['BUCKET_NAME'], s3_upload_folder).put(Body=image)
so the trick is, you must either have the file on disk and provide filepath with file_to_upload OR provide the file itself as I demonstrated in this answer.
Related
I am getting the following error from my terminal:
{{ form.hidden_tag() }} File "/home/gitpod/.pyenv/versions/3.8.13/lib/python3.8/site-packages/jinja2/environment.py", line 485, in getattr return getattr(obj, attribute) jinja2.exceptions.UndefinedError: 'form' is undefined
here is the related code:
(html template)
<form action="#" method="post" enctype="multipart/form-data">
{{ form.hidden_tag() }}
{{ form.file }}
{{ form.organs }}
{{ form.upload }}
</form>
python code:
class UploadImage(FlaskForm):
file = FileField(validators=[FileRequired(), FileAllowed(['png', 'jpeg','jpg'], 'Images only!')]) #allow only files with the correct extension to be submitted
organs = RadioField('Label', choices=[('leaf','leaf'),('flower','flower'),('fruit','fruit'),('bark','bark/stem')])
upload = SubmitField("Upload")
##login_required
def view_plants():
#check if the file the client wants to upload matches the specified requirements
form = UploadImage()
if form.validate_on_submit():
filename = secure_filename(form.file.data.filename)
form.file.data.save('static/user_uploads/' + filename) #grab the file and save it in the uploads directory
return render_template("your_plants.html")
return render_template("your_plants.html")```
You aren't passing form to the template
Change your render_templates calls to this:
return render_template("your_plants.html", form=form)
I am trying to implement dropzone in my project to upload multiple images. But apparently, when I call dropzone.create(action=''), I keep getting the error that the url is not build. Below are the implementation details I did.
This is my css code filename: template.html.j2
{% block style %}
{{ dropzone.load_css() }}
{{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }}
{% endblock %}
This is the import of dropzone:
{% block scripts %}
<script src="{{url_for('static', filename='jsfiles/dropzone.js')}}"></script>
{% endblock %}
The Body part:
{{ dropzone.create(action=url_for('folder1.upload, id=user.id'))}}
{{ dropzone.load_js() }}
{{ dropzone.config() }}
This is my function : File name:user.py
#inspection.route('/users/<int:user__id>', methods=['GET', 'POST'])
def upload(id):
//Some Code
if form.validate_on_submit() and 'photo' in request.files:
for f in request.files.getlist('photo'):
filename = secure_filename(f.filename)
print(filename)
return render_template('folder/user_details.html.j2')
The folder structure is as below:
Folder1
user.py
Templates
template.html.j2
app.py
It would be great if someone can point out why I keep getting this error.
Cannot build url for endpoint folder1.upload
Try this:
Body part
{{ dropzone.create(action=url_for('folder1.user.upload, pk=user.id'))}}
{{ dropzone.load_js() }}
{{ dropzone.config() }}
user.py
#inspection.route('/users/<int:pk>', methods=['GET', 'POST'])
def upload(pk):
//Some Code
if form.validate_on_submit() and 'photo' in request.files:
for f in request.files.getlist('photo'):
filename = secure_filename(f.filename)
print(filename)
return render_template('folder/user_details.html.j2')
So, I am trying to open an .csv file in Python using Flask. I copies the code from the Python library, but I go from one error message to the other and I don't know what I am doing wrong. The latest error code I get on the below code is: TypeError: invalid file:
Any ideas what I am doing wrong?
My Python code/Flash route is as follows:
#app.route("/admin", methods=["GET", "POST"])
#login_required
def admin():
"""Configure Admin Screen"""
# if user reached route via POST (as by submitting a form via POST)
if request.method == "POST":
# load csv file with portfolio data
with open(request.files["portfolios"]) as csvfile:
portfolios = csv.DictReader(csvfile)
# load csv file in dictionary
for row in portfolios:
print(row['first_name'], row['last_name'])
else:
return render_template("admin.html")
My html/Flask code is:
{% extends "layout.html" %}
{% block title %}
Admin
{% endblock %}
{% block main %}
<h2>Admin Console</h2>
<h3> Upload Portfolio Data</h2>
<form action="{{ url_for('admin') }}" method="post" enctype=multipart/form-data>
<fieldset>
<label class="control-label">Select Portfolio Upload File</label>
<input id="input-1" type="file" class="file" name="portfolios">
<h3>Upload Security Lists</h2>
<label class="control-label">Select Security Upload File</label>
<input id="input-1" type="file" class="file" name="securities">
<div class="form-group">
<button class="btn btn-default" type="submit" value = "upload">Upload</button>
</div>
</fieldset>
</form>
{% endblock %}
The file is already open. open takes a string filename and created an open file object, but you don't need to do that because objects in request.files are already open file-like objects.
portfolios = csv.DictReader(request.files['portfolios'])
I am trying to upload a csv file into Mysql with choosing the correspanding columns but the problem is that once I change the route, the file is closed.
So I tried to render 2 templates in the same route: the first to load the file and the second to choose the columns. I can access only the first template.
I am testing the second form with env.is_submitted() but even when I am not submitting it prints "submitted"
#app.route('/upload', methods=['GET', 'POST'])
def upload():
form = UploadForm()
global columnscsv, salessource
if form.validate_on_submit():
try:
filename = secure_filename(form.csv.data.filename)
file = form.csv.data
if file and allowed_file(filename):
print 'file_path'
salessource = CSVSource(file, delimiter=',')
columnscsv = salessource.fieldnames
print columnscsv
finally:
return render(salessource)
return render_template('upload.html', form=form)
def render(salessource):
env = envForm()
if env.is_submitted():
print "submitted"
return render_template('form.html',columnscsv = columnscsv ,env =env)
upload.html
<html>
<head>
<title>Upload</title>
</head>
<body>
<form method="post" enctype="multipart/form-data" >
{{ form.hidden_tag() }}
{{ form.csv }}
<input type="submit">
</form></body>
</html>
form.html
{% block body %}
<form name = "mapping" method="POST" enctype="multipart/form-data" >
{{ env.hidden_tag() }}
<table>
{% for csv in columnscsv %}
<tr> <td> {{ csv }}</td>
<td><select name = "{{ csv }}" >
<option >year </option>
<option >month</option>
<option >day</option>
<option>reference</option>
<option>designation</option>
</select></td>
</tr>
{% endfor %}
</table>
<input type="submit" value="Submit" name = "submit" >
</form>
{% endblock %}
Your form.html can only be rendered when you submit a form (your render(salessource) was inside the check of submit form), so I cant find anyway it does not print "Submitted" in this way.
If you want to render 2 templates, I find a work arround like this:
Add session['fileName'] = filename as a temp to know if a file was submitted
Redirect back to itself after submit
Check if session['fileName'] exist to choose what template to render
#app.route('/upload', methods=['GET', 'POST'])
def upload():
form = UploadForm()
global columnscsv, salessource
if form.validate_on_submit():
try:
filename = secure_filename(form.csv.data.filename)
file = form.csv.data
session['fileName'] = filename
if file and allowed_file(filename):
print 'file_path'
salessource = CSVSource(file, delimiter=',')
columnscsv = salessource.fieldnames
print columnscsv
redirect(url_for('upload'))
except:
raise
if session.get('fileName') != None:
render_template('form.html',columnscsv = columnscsv ,env=env)
else:
return render_template('upload.html', form=form)
I'm building a website using Flask in which I use WTForms. In a Form I now want to use a FieldList of FormFields as follows:
class LocationForm(Form):
location_id = StringField('location_id')
city = StringField('city')
class CompanyForm(Form):
company_name = StringField('company_name')
locations = FieldList(FormField(LocationForm))
so to give people the ability to enter a company with two locations (dynamic adding of locations comes later) I do this on the front side:
<form action="" method="post" role="form">
{{ companyForm.hidden_tag() }}
{{ companyForm.company_name() }}
{{ locationForm.location_id() }}
{{ locationForm.city() }}
{{ locationForm.location_id() }}
{{ locationForm.city() }}
<input type="submit" value="Submit!" />
</form>
So on submit I print the locations:
print companyForm.locations.data
but I get
[{'location_id': u'', 'city': u''}]
I can print the values of the first location using the locationForm (see below), but I still don't know how to get the data of the second location.
print locationForm.location_id.data
print locationForm.city.data
So the list of locations does have one dict with empty values, but:
Why does the list of locations have only one, and not two dicts?
And why are the values in the location dict empty?
Does anybody know what I'm doing wrong here? All tips are welcome!
For starters, there's an argument for the FieldList called min_entries, that will make space for your data:
class CompanyForm(Form):
company_name = StringField('company_name')
locations = FieldList(FormField(LocationForm), min_entries=2)
This will setup the list the way you need. Next you should render the fields directly from the locations property, so names are generated correctly:
<form action="" method="post" role="form">
{{ companyForm.hidden_tag() }}
{{ companyForm.company_name() }}
{{ companyForm.locations() }}
<input type="submit" value="Submit!" />
</form>
Look at the rendered html, the inputs should have names like locations-0-city, this way WTForms will know which is which.
Alternatively, for custom rendering of elements do
{% for l in companyForms.locations %}
{{ l.form.city }}
{% endfor %}
(in wtforms alone l.city is shorthand for l.form.city. However, that syntax seems to clash with Jinja, and there it is necessary to use the explicit l.form.city in the template.)
Now to ready the submitted data, just create the CompanyForm and iterate over the locations:
for entry in form.locations.entries:
print entry.data['location_id']
print entry.data['city']
This is an old question, but still a good one.
I'd like to add a working Flask based example of a toy database (just a list of strings) with focus on the Python part - how to initialize the form with variable number of subforms and how to process the posted data.
This is the example.py file:
import flask
import wtforms
import flask_wtf
app = flask.Flask(__name__)
app.secret_key = 'fixme!'
# not subclassing from flask_wtf.FlaskForm
# in order to avoid CSRF on subforms
class EntryForm(wtforms.Form):
city = wtforms.fields.StringField('city name:')
delete = wtforms.fields.BooleanField('delete?')
class MainForm(flask_wtf.FlaskForm):
entries = wtforms.fields.FieldList(wtforms.fields.FormField(EntryForm))
submit = wtforms.fields.SubmitField('SUBMIT')
city_db = "Graz Poprad Brno Basel Rosenheim Torino".split() # initial value
#app.route("/", methods=['POST'])
def demo_view_function_post():
global city_db
form = MainForm()
if form.validate_on_submit():
city_db = [
entry['city'] for entry in form.entries.data
if entry['city'] and not entry['delete']]
return flask.redirect(flask.url_for('demo_view_function_get'))
# handle the validation error, i.e. flash a warning
return flask.render_template('demo.html', form=form)
#app.route("/")
def demo_view_function_get():
form = MainForm()
entries_data = [{'city': city, 'delete': False} for city in city_db]
entries_data.append({'city': '', 'delete': False}) # "add new" placeholder
form.process(data={'entries': entries_data})
return flask.render_template('demo.html', form=form)
This is the demo.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Demo</title>
</head>
<body>
<h1>Subform demo</h1>
<p>Edit names / mark for deletion / add new</p>
<form method="post">
{{ form.csrf_token() }}
{% for entry in form.entries %}
{% if loop.last %}
<div>Add new:</div>
{% endif %}
<div>
{{ entry.city.label }} {{ entry.city() }}
{{ entry.delete() }} {{ entry.delete.label }}
</div>
{% endfor %}
{{ form.submit() }}
</form>
</body>
Run with: FLASK_APP=example flask run