I have a very simple application that creates, removes, and updates records in a sqlite3 database using Python and Flask. I am currently working on the remove function and have hit a road block. Below is my code for my view that shows the records in the table:
<!doctype html>
<html>
<body>
<table border = 1>
<thead>
<td>Name</td>
<td>Address>/td<
<td>city</td>
<td>Pincode</td>
</thead>
{% for row in rows %}
<tr>
<td>{{row["name"]}}</td>
<td>{{row["addr"]}}</td>
<td> {{ row["city"]}}</td>
<td>{{row['pin']}}</td>
<td><form action = "/foo" method = "POST">
<button id ="w3-btn">delete</button>
</form> </td>
</tr>
{% endfor %}
</table>
Go back to home page
</body>
</html>
As can be seen from the code, I have a delete button for each record displayed. How can I make it such that if the user clicks on the delete button on any given row, that row will be deleted from the table? I would like to know how I can specify the selected row, and how I can send that information/data to 'foo' in my app.py, and how the function in app.py would take that data as an input argument.
If you have row["pin"]
then you can use hidden field in form with method="POST"
<form action="/foo" method="POST">
<input type="hidden" value="{{ row["pin"] }}"/>
<button id="w3-btn">delete</button>
</form>
or using method="GET" and ?id=some_id in url
<form action="/foo?id={{ row["pin"] }}" method="GET">
<button id="w3-btn">delete</button>
</form>
Or even as normal link (which you can style as button using CSS)
delete
In view you will have
# POST
#app.route('/foo', methods=['POST'])
def foo():
pin = requests.form.get('id')
print(pin)
# GET or normal link
#app.route('/foo')
def foo():
pin = requests.args.get('id')
print(pin)
Read doc: http://flask.pocoo.org/docs/0.11/
If you use url without ?id= but "/foo/some_id"
action="/foo/{{ row["pin"] }}"
delete
or using url_for()
action="{{ url_for('foo', pin=row["pin"]) }}"
TEST
then you will need
#app.route('/foo/<pin>')
def foo(pin):
print(pin)
Create a delete view that takes an id to delete.
#app.route('/<int:id>/delete', methods=['POST'])
def delete(id):
r = Record.query.get_or_404(id)
db.session.delete(r)
db.session.commit()
return redirect(url_for('index'))
The delete form's action is the generated url to the delete view.
<form method=post action="{{ url_for('delete', id=r.id) }}">
This assumes that each row has an id attribute. For example:
from flask import Flask, redirect, url_for, render_template_string
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
class Item(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
db.create_all()
db.session.add_all((
Item(name='abc'),
Item(name='def'),
Item(name='ghi'),
))
db.session.commit()
#app.route('/')
def index():
items = Item.query.all()
return render_template_string('''<ul>{% for item in items %}
<li>{{ item.name }} -
<form method=post action="{{ url_for('delete', id=item.id) }}">
<button type=submit>delete</button>
</form></li>
{% endfor %}</ul>''', items=items)
#app.route('/<int:id>/delete')
def delete(id):
item = Item.query.get_or_404(id)
db.session.delete(id)
db.session.commit()
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True)
Related
I have a FieldList form that allows users to enter in an origin and destination for routes they have travelled.
I am trying to add Google's autocomplete API to make it easier for users to enter in addresses into the fields.
forms.py
from flask_wtf import Form
from wtforms import (
StringField,
FormField,
FieldList
)
from wtforms.validators import (
Length,
Optional
)
class RouteForm(Form):
origin = StringField([Optional(), Length(1, 256)])
destination = StringField([Optional(), Length(1, 256)])
class JourneysForm(Form):
ids = []
journeys = FieldList(FormField(RouteForm))
edit.html
{% import 'macros/form.html' as f with context %}
<tbody>
<tr>
{% for journey, route in zip(form.journeys, routes) %}
<td>
{% call f.location_search(journey.origin,
css_class='sm-margin-bottom hide-label') %}
{% endcall %}
</td>
</tr>
{% endfor %}
</tbody>
<div class="col-md-6">
<button type="submit" class="btn btn-primary btn-block">
'Save'
</button>
</div>
macros/forms.html
<head>
<script src="https://maps.googleapis.com/maps/api/js?libraries=places&key=<KEY>&callback=initMap" async defer></script>
<script>
function initMap() {
var input = document.getElementById('searchInput');
var autocomplete = new google.maps.places.Autocomplete(input);
}
</script>
</head>
{# Render a form for searching a location. #}
{%- macro location_search(form, css_class='') -%}
<input type="text" class="form-control"
id="searchInput" placeholder="Enter location">
{{ form(class=css_class, **kwargs) }}
{{ caller () }}
{%- endmacro -%}
routes.py
#route('/edit', methods=['GET', 'POST'])
def routes_edit():
routes = get_routes()
journeys_form = JourneysForm()
if journeys_form.validate_on_submit():
for i, entry in enumerate(journeys_form.journeys.entries):
origin = entry.data['origin']
However, this renders two fields. One which contains the Google autocomplete input, but does not submit the value (top). And another field which does not have the Google autocomplete input but submits the value to the db via routes.py (bottom).
Is it possible to combine this into a single field that both contains the Google autocomplete and submits the input value to the db?
WTForms actually renders an input class itself:
<input class="form-control" id="journeys-0-origin" name="journeys-0-origin" type="text" value="" placeholder="Enter a location" autocomplete="off">
Therefore I was unnecessarily duplicating the input element. In order to get Google autocomplete to work I simply just had to pass the input id into my js function:
function initMap() {
var input = document.getElementById('journeys-0-origin');
var autocomplete = new google.maps.places.Autocomplete(input);
}
I am very much struggling with a concept and hope someone can offer a code implementation suggestion. I have the following route embedded within my flask app. After working on this for a few days I think I now see the nature of the issue. I have modified my code based on suggestion below in comments.
In the web app now, the names of the data frame columns populate a drop down menu and the user can choose one of those variables and click "Show". That variable name then prints to the screen just so I know my POST from the form is communicating with my function in the flask app.
What I would like to do is create a temp version of the file uploaded so that it exists as long as the web session is open (or is overwritten if a new file upload occurs). Then, the user chooses a variable from the drop down menu and the mean of that variable is computed (that part is commented out in the code below for now but you can see what I have tried).
If the user reads in a new file, then the same process would occur on that new file.
Thanks for any suggestions or supports.
from flask import Flask, render_template, request
import numpy as np
from scipy.stats import binom
from scipy.optimize import minimize
from scipy.stats import norm
from scipy import optimize
from pyodbc import connect
import pandas as pd
import os
import tempfile
app = Flask(__name__)
#app.route('/dataTools', methods=['POST', 'GET'])
def data_tools_upload():
tempfile_path = tempfile.NamedTemporaryFile().name
if request.files:
df = pd.read_csv(request.files.get('file'))
#tempfile_path = tempfile.NamedTemporaryFile().name
df.to_csv(tempfile_path)
if os.path.exists(tempfile_path):
orig_df = pd.read_csv(tempfile_path)
vars = list(orig_df.columns)
var2use = request.form.get("var2use")
#indx = vars.index(var2use)
#df[vars[indx]].mean()
mean = orig_df[vars[4]].mean()
dims = orig_df.shape
message = 'You have data! There are %s rows and %s columns and the variable %s has mean %s' % (dims[0],dims[1],vars[4],round(mean,3))
table = orig_df.head(10).to_html(classes='data', header = "true")
return render_template('upload.html', tables = [table], message = message, vars = vars, var_name = var2use)
var2use = request.form.get("var2use")
return render_template('upload.html', var_name = var2use, message = "What happened?")
if __name__ == '__main__':
app.run(debug=True)
And then the following HTML bit (upload.html):
{% extends "layout.html" %}
{% block body %}
<h3> Read in a data file </h3>
<br>
<form method=post enctype=multipart/form-data>
<input type=file name=file class = "btn btn-outline-secondary">
<input type=submit value=Upload class = "btn btn-outline-secondary">
</form>
<br>
<form class="form-inline" action = "{{url_for('data_tools_upload')}}" method = "POST">
<select type = "text" name="var2use" class="custom-select mr-sm-1">
{% for var in vars %}
<option value= "{{ var }}" SELECTED>{{ var }}</option>"
{% endfor %}
</select>
<button class = "btn btn-primary"> Show </button>
</form>
<center>
<h1>
{{message}}
{{name}}
</h1>
<br>
<small>
{% for table in tables %}
{{ table|safe }}
{% endfor %}
{% endblock %}
</small>
</center>
you need to save file to work with it (the code works perfectly in my app):
f = request.files['file']
tmp_filename = '/tmp/my_tmp_file.csv'
f.save(tmp_filename)
pd.read_csv(tmp_filename)
Here is the detailed documentation on Flask file uploads:
https://flask.palletsprojects.com/en/1.1.x/patterns/fileuploads/
Full example for your case is below. I think you might have problems with the following three items:
Uploading the file because of form enctype (should be "multipart/form-data")
Uploading because of file size (try my sample)
Processing the form with empty file overwriting yours (see my example for "csv_file_uploaded")
Python (csv_test.py):
from flask import Flask, render_template, request
import pandas as pd
import os
app = Flask(__name__, static_url_path='')
#app.route('/', methods=['GET', 'POST'])
def index():
data = {}
tmp_filename = '/tmp/csv_test.csv'
if request.files:
csv_file_uploaded = request.files.get('file')
if csv_file_uploaded:
f = request.files['file']
f.save(tmp_filename)
if os.path.exists(tmp_filename):
df = pd.read_csv(tmp_filename)
data['columns'] = ', '.join(df.columns)
col_selected = request.form.get('col_selected')
print(col_selected)
data['col_selected'] = col_selected
if col_selected:
mean = df.get(col_selected).mean()
data['mean'] = mean
return render_template('csv_test.tpl', data=data)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
Flask template (csv_test.tpl):
<html><head><title>CSV TEST</title></head>
<body><h1>CSV TEST</h1>
{% if data %}
<div>Columns of the uploaded file: <strong>{{ data.columns }}</strong></div>
<div>Selected column: <strong>{{ data.col_selected }}</strong></div>
<div>Mean of the selected = <strong>{{ data.mean }}</strong></div>
{% endif %}
<fieldset>
<form enctype="multipart/form-data" action="" method="post">
<h2>Provide some CSV with first column in numbers and/or column name</h2>
<div>File{% if data.columns %} (UPLOADED){% endif %}: <input id="file" type="file" name="file"/></div>
<div>Column: <input id="col_selected" type="text" name="col_selected" value="{{ data.col_selected }}"/></div>
<div><input id="submit" type="submit" value="SUBMIT"/></div>
</form>
</fieldset>
</body></html>
CSV file (123.csv) - use it to test the form:
col1,col2
1,2
2,3
3,4
5,6
I have done a lot of research through multiple posts about this issue, and and no matter what I try I am getting issues. Essentially what I am trying to do it write something in an input field, press a submit button and post the string from the input field and display it on a separate page/separate route in using flask. I think I am on the right track with what I have below, however it returns a value of None rather than what I write in my input field in index.html.
from flask import Flask, render_template, request, jsonify, Response,
redirect, url_for,session
from flask_bootstrap import Bootstrap
app = Flask(__name__)
Bootstrap(app)
app.secret_key = 'dljsaklqk24e21cjn!Ew##dsa5'
#app.route('/', methods=['GET', 'POST'])
def hello():
if request.method == 'POST':
nme = request.form['name']
session['name'] = nme
return url_for(deliver)
return render_template('index.html')
#app.route('/delivery', methods=['GET', 'POST'])
def deliver():
name = session.get('name')
return render_template('delivery.html', name=name)
index.html is
<form action = "{{ url_for('deliver')}}" method = "POST">
<p>Name <input type = text class="form-control" name = "name" /></p>
<p>Address <input type = text class="form-control" name = "Address" /></p>
<input type=submit name='submit'>
</form>
and delivery.html is
<div class="card-deck mx-auto" style="width: 75rem;">
<div class="card text-white bg-dark p-3" style="width: 45rem;">
<h5 class="card-title text-center"></h5>
<img class="card-img-top mx-auto" src="/static/img/hands.png" alt="Vibration
Image" style="width:20%">
<div class="card-body">
<h2 class="card-title text-center">Delivery Information</h2>
<h5> {{name}} </h5>
This code:
<form action = "{{ url_for('deliver')}}" method = "POST">
means that submitting the form will POST to /deliver, but you really want to POST to the index page to trigger the session['name'] = nme code. So just remove the action.
You also need to change
return url_for(deliver)
to
return redirect(url_for('deliver'))
I am able to add a new entry to my database using WTForms and Flask and I can edit too, the problem is that I need to display the already existing information in the database in an edit form.
I have the following code:
A Class For the Edit Post Form
class editPostForm(Form):
postTitle = TextField('postTitle', validators.Required()])
postSubtitle = TextField('postSubtitle', validators.Required()])
A Route for the Edit Post Template
#app.route('/editpost/<postId>', methods = ['GET','POST'])
def editpost_page(postId):
try:
form = editPostForm(form)
if request.method == "POST" and form.validate():
postTitle = form.postTitle.data
postSubtitle = form.postSubtitle.data
c, conn = connection()
query = c.execute("SELECT * FROM posts WHERE post_id = (%s)",
[noinjection(postId)])
c.execute("UPDATE posts SET post_title=%s, post_subtitle=%s WHERE post_id = %s",
[
noinjection(postTitle),
noinjection(postSubtitle),
noinjection(postId)
])
conn.commit()
flash("Post Edited", 'success')
c.close()
conn.close()
gc.collect()
return redirect(url_for('posts'))
return render_template("editpost.html", form = form, POST_ID = postId)
except Exception as e:
return(str(e))
The Edit Post Template {jinja}
{% extends "header.html" %}
{% block body %}
<body>
<div class="container-fluid">
<br />
<h4>Edit Post</h4>
<br />
{% from "_formhelpers.html" import render_field %}
<form action="/editpost/{{ POST_ID }}" class="form-horizontal" method="post">
{% from "_formhelpers.html" import render_field %}
<form action="/editpost/" method="post">
<div class="form-group">
<label for="postTitle">Post Title</label>
<input type="text" class="form-control" id="postTitle" name="postTitle" placeholder="Post Title" value="{{request.form.postTitle}}">
</div>
<div class="form-group">
<label for="postSubtitle">Post Subtitle</label>
<input type="text" class="form-control" id="postSubtitle" name="postSubtitle" placeholder="Post Subtitle" value="{{request.form.postSubtitle}}">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
{% if error %}
<p class="error"><strong>Error: </strong>{{error}}</p>
{% endif %}
</form>
{% if error %}
<p class="error"><strong>Error: </strong>{{error}}</p>
{% endif %}
</div>
</body>
{% endblock %}
With the following code, I am getting a selected post to update in the database, but the editpost template is not showing the values which are already in the database and all the fields are blank.
How can I pre-populate the form before editing?
You can populate each field separately like this:
form = editPostForm(form)
form.postTitle.data = postTitle_from_database
form.postSubtitle.data = postSubtitle_from_database
or you can populate your form fields from a given object using process method:
process(formdata=None, obj=None, **kwargs)
Take form, object data, and keyword arg input and have the fields
process them.
Parameters:
formdata – Used to pass data coming from the enduser, usually request.POST or equivalent.
obj – If formdata has no data for a field, the form will try to get it from the passed object.
**kwargs – If neither formdata or obj contains a value for a field, the form will assign the value of a matching keyword argument
to the field, if provided.
Since BaseForm does not take its data at instantiation, you must call
this to provide form data to the enclosed fields. Accessing the
field’s data before calling process is not recommended.
I was able to pre-populate HTML input and textarea fields from a SQL database with Python and Jinja as follows:
1. Store relevant data from database in a variable:
name = db.execute("""SELECT name FROM users WHERE id = :id""", id=session["user_id"])
about = db.execute("""SELECT about FROM users WHERE id = :id""", id=session["user_id"])
2. Render template (with render_template function) and pass in the relevant variables:
return render_template("edit.html", name = name, about = about)
3. Pass variables via jinja to html input and textarea elements. Index into the object that has been passed as follows:
For an input tag use the value attribute as below:
<input type="text" class="form-control" name="name" value="{{ name[0]["name"] }}">
For a textarea element:
<textarea class="form-control" name="about">{{ about[0]["about"] }}</textarea>
i am using django with google app engine . i am trying to upload images.
i made a form
<form enctype="multipart/form-data" action="addImage" method="post">
<p>Title of the Image:
<input type="text" name="title" /></p>
<p>Please select image to upload:
<input type="file" name="img" required="True"/></p>
<p><input type="submit" value="Upload" /></p>
</form>
mapping it to this view
def addImage(request):
image = Image()
image.title = request.POST.get("title")
img = images.resize(request.POST.get('img'),50,50)
image.blob = db.Blob(img)
image.put()
return HttpResponse('<html><head><meta HTTP-EQUIV="REFRESH" content="2; url=/"></head><body>One item added successfuly </body></html>')
its giving me this error in the debugging session
Exception Type: NotImageError
Exception Value:Empty image data.
WHY?????
I haven't used Google App Engine, but this is how I would do it on a pure Django 1.3 installation:
forms.py:
from django import forms
from django.forms import fields
class UploadImageForm(forms.Form):
image_file = fields.ImageField()
views.py:
from django.shortcuts import render_to_response
from django.template import RequestContext
from NAME_OF_YOUR_APP.forms import UploadImageForm
def addImage(request):
if request.method == 'POST':
upload_image_form = UploadImageForm(data=request.POST, files=request.FILES)
if upload_image_form.is_valid():
image_file = request.cleaned_data['image_file']
# do something with the image...
return ...
else:
upload_image_form = UploadImageForm()
context = {'form':upload_image_form}
return render_to_response('path/to/upload_template.html', context, context_instance=RequestContext(request))
upload_template.html:
<form enctype="multipart/form-data" action="" method="post">
{% csrf_token %}
<table>
<tr>
<td>{{ form.image_file.label_tag }}</td>
<td>{{ form.image_file }}</td>
<td>{% if form.image_file.errors %}{% for error in form.image_file.errors %}{{ error }}{% endfor %}{% endif %}</td>
</tr>
</table>
<input type="submit" value="Submit"/>
</form>
Your template code looks good (it's missing {% csrf_token %}, which I'm not sure if GAE needs or not). Your view code should check to see if the request is a POST request or not.
In my example, I created a form called UploadImageForm, which accepts a single image_file to be uploaded. Logic works like so:
User visits example.com/upload_image
addImage() runs. Since this is a GET and not a POST request, it makes an empty UploadImageForm(), and renders it inside upload_template.html.
User is displayed the form.
User fills out the form and Submits an image.
Server receives POST request, and addImage() is called again.
We bind the uploaded file data to UploadImageForm.
If there are no errors (e.g. upload_image_form.is_valid() is True), we capture the image_file out of cleaned_data, and we can then do something with it.
If there are errors (upload_image_form.is_valid() is False), the template is re-displayed with error messages.
really simple ,
edit this line:
img = images.resize(request.POST.get('img'),50,50)
with this one:
img = request.FILES['img'].read()
make sure that you are using django 1.2
Try this.. it work for me... :)
def addImage(request):
image = Image()
image.title = request.POST.get("title")
image.blob = db.Blob(str(request.FILES['img']['content']))
image.put()
return HttpResponse('<html><head><meta HTTP-EQUIV="REFRESH" content="2; url=/"></head><body>One item added successfuly </body></html>')