idea:
take id's from html input
use id's to run sql and return relevant usernames
download the output as a csv on the front end when the "download" button is clicked
html
Enter comma delimited ids <input type="text" id="text1" name="text1"><br><br>
download
python
#app.route("/getPlotCSV", methods=['GET','POST'])
def getPlotCSV():
text1 = request.form['text1']
result = {}
a = []
x = []
a.extend([str(x) for x in text1.split(",")])
format_strings = ','.join(['%s'] * len(a))
cursor = cnxn.cursor()
sql = "SELECT DisplayName FROM dbo.[Users] where id IN ({seq})".format(
seq=','.join(['?'] * len(a)))
cursor.execute(sql,a)
for row, in cursor:
x.append(row)
csv = x
return Response(
csv,
mimetype="text/csv",
headers={"Content-disposition":
"attachment; filename=myplot.csv"})
The sql and input works because i have tested it separately without the csv download and it returns the correct data. The error i get at the moment is "400 Bad Request: The browser (or proxy) sent a request that this server could not understand." KeyError: 'text1'
what am i missing here?
The KeyError is because you haven't actually passed the value of text1 to your getPlotCSV route. A link on an HTML page won't also transfer the data with it. Instead you need to use a form with an action page, like this:
<form action="/getPageCSV" method="post">
Enter comma delimited ids: <input type="text" id="text1" name="text1">
<input type="submit" name="Submit" id="submit">
</form>
This should then pass those values to the url in the form action attribute, in this case your getPageCSV. It doesn't have to be POST, I've just done that as an example.
Then, when your route receives the data:
#app.route('/getPageCSV')
def getPlotCSV():
if request.method == "POST": #in other words, if the form is being submitted
text1 = request.form.get('text1') #use dict.get in case it isn't there
# your code
csv = x
return redirect(url_for('getPlotCSV')) #this time a get method
return Response(
csv,
mimetype="text/csv",
headers={"Content-disposition":
"attachment; filename=myplot.csv"})
The above won't specifically work without you adding in your own way to move the POST process data/csv over to when the user is redirected. You could do it as a request header, store it in the session storage or even put it in the query string, it's up to you, but you have to be able to display the results of your POST process into a GET request when the user is redirected.
Related
I have a python project in PythonAnywhere where the user uploads a file, it gets processed, and then it gets downloaded again by the user.
The file gets processed using a RandomForestRegressor model.
So far, it works perfectly. The user uploads and a new output file is generated. What I want to do is that the results of the model be returned in the website.
A message like this:
Accuracy of model on test set: 0.90
mse of model on train set: 0.08
The HTML is like this:
<html>
<body>
{% for comment in comments %}
<div class="row">
{{ comment }}
</div>
{% endfor %}
</body>
</html>
I was trying to add a comment after the proccess_data function is called. I added the render_template with the comment variable but it doesnt work. I even added it at the end of the def index() as a return but it just doesnt work.
output_file = process_data(filename)
comments = 'File processed succesfully'
render_template("main_page.html", comments=comments)
The python script is like this:
comments = []
#app.route('/', methods=["GET","POST"])
def index():
if request.method == "GET":
return render_template("main_page.html")
if request.method == 'POST':
#comments.append(request.form["contents"])
#check if the post request has the file part
if 'input_file' not in request.files:
#TODO
return redirect(request.url)
file = request.files['input_file']
#if the user does not select a file, the browser submits
#empty file without a filename
if file.filename == '':
#TODO
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
output_file = process_data(filename)
comments = 'File processed succesfully'
render_template("main_page.html", comments=comments)
si = io.StringIO()
output_file.to_csv(si,index=False, encoding='UTF8')
#-----Save dataframe to folder-----
filepath = os.path.join(app.config['UPLOAD_FOLDER'], 'results.csv')
output_file.to_csv(filepath)
response = make_response(si.getvalue())
response.headers["Content-Disposition"] = "attachment; filename=results.csv"
response.headers["Content-type"] = "text/csv"
return response
return
I don't work with python but I see you basically want to send two responses in one request and most likely at different times?
HTTP does not work like that. You can't send one response "mid process" and then some other after a while. It's always one request - one response. And a new tcp connection opens and closes immediately each time.
So if you want to notify user about the results of backend process some time before you actually send him the "final data", you have these options:
Divide this flow into multiple individual requests.
Use WebSockets.
I'm using fastapi-mail package, and trying to send multiple files to multiple email addresses. When I send the email to only one email address, the application works as expected. However, when I change to List[EmailStr] for sending to multiple email addresses, I get this error:
not a valid email address
Here is my code:
#app.post("/file")async def send_file(
background_tasks: BackgroundTasks,
email:List[EmailStr] = Form(...), #I Change here before EmailStr = Form(...)
file:Optional[List[UploadFile]] = File(...),) -> JSONResponse:
print(email)
print(file)
message = MessageSchema(
subject="Fastapi mail module",
recipients=email,
body="Simple background task",
subtype="html",
attachments=file)
fm = FastMail(ConnectionConfig(
MAIL_USERNAME=res("MAIL_USERNAME"),
MAIL_PASSWORD=res("MAIL_PASSWORD"),
MAIL_FROM="admin#acsebs.com",
MAIL_PORT=res("MAIL_PORT"),
MAIL_SERVER=res("MAIL_SERVER"),
MAIL_FROM_NAME="send attachment email service",
MAIL_TLS=res("MAIL_TLS"),
MAIL_SSL=res("MAIL_SSL"),
USE_CREDENTIALS=res("USE_CREDENTIALS"),
VALIDATE_CERTS=res("VALIDATE_CERTS")
))
background_tasks.add_task(fm.send_message, message)
return JSONResponse(status_code=200, content={"message": "email has been sent"})
Posting data through Swagger UI:
The error:
The issue is not with your code, but with Swagger UI when sending multiple values for the same field. As described in this answer, Swagger UI incorrectly adds all items as a single item to the list, separated by comma (you can confirm that by looking at the second screenshot you provided, under the "Curl" section). For example, when you pass two or more email addresses to your endpoint through Swagger UI, they are sent as:
['user1#example.com, user2#example.com']
instead of:
['user1#example.com', 'user2#example.com']
Hence, the error is raised, as 'user1#example.com, user2#example.com' (all together as a single string) is not a valid email address. If you sent a request using HTML <form> or JavaScript fetch—similar to Method 1 and Method 3 of this answer—you would see that your code would work just fine.
Note 1: Use a different <input> element for each email address, but use the same name value for all (i.e., emails, which is the name of the parameter defined in the endpoint).
Note 2: On a side note, be aware that the "the most important part to make a parameter Optional is the part = None", as described in this answer and this comment. You seem to have defined your files parameter in your endpoint with the Optional keyword, but using = File(...) or ignoring that part at all, would make files a required field; hence, make sure to use = File(None), if you want it to be optional instead.
Example:
#app.post("/email")
def send_email(emails: List[EmailStr] = Form(...),
files: Optional[List[UploadFile]] = File(None)):
return emails
#app.get('/emailForm', response_class=HTMLResponse)
def index():
return """
<html>
<body>
<form method="POST" action="/email" enctype="multipart/form-data">
<label for="email1">Email 1:</label>
<input type="text" id="email1" name="emails"><br><br>
<label for="email2">Email 2:</label>
<input type="text" id="email2" name="emails"><br><br>
<input type="file" id="files" name="files" multiple>
<input type="submit" value="Submit">
</form>
</body>
</html>
"""
Adjust the endpoint to work with Swagger UI
If you need to use Swagger UI and would like your endpoint to work when submitting requests through there as well, here is a solution, as suggested here. Perform a check on the length of the emails list, and if it is equal to 1 (meaning that the list contains a single item), then split this item using the comma delimiter to get the actual list of email addresses. Finally, loop through the list to validate each email using the email-validator, which is used by Pydantic behind the scenes.
Example:
from fastapi import FastAPI, Depends, UploadFile, File, Form, HTTPException, status
from email_validator import validate_email, EmailNotValidError
from typing import List, Optional
def check_email(email: str):
try:
validation = validate_email(email, check_deliverability=False)
return validation.email
except EmailNotValidError as e:
raise HTTPException(detail=f"'{email}' is not a valid email address. {str(e)}",
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
def email_checker(emails: List[str] = Form(...)):
if len(emails) == 1:
emails = [item.strip() for item in emails[0].split(',')]
return [check_email(email) for email in emails]
#app.post("/email")
def send_email(emails: List[str] = Depends(email_checker)):
return emails
Looks like your endpoint accepts a JSON object where you have to provide the types properly due pydantic validation, why don't you just don't provide the request in a JSON format like:
curl -X 'POST' \
'http://10.11.12.110:8000/file' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"filename": ["fileA", "fileB", "fileC"],
"email": ["user1#example.com", "user2#example.com"]
}'
I want to enter a word in a submit form and after I click Submit I want to be redirected to a new page where the word will be seen.
This is my python code:
#app.route("/", methods=['GET', 'POST'])
def home():
if request.method == 'POST':
word = request.form['strword']
cur = mysql.connection.cursor()
cur.execute("INSERT INTO words (word) VALUES (%s)", [word])
mysql.connection.commit()
cur.close()
return redirect(url_for('results', requested_word=word))
#adding word to MySQL (I want this word displayed in my website.)
return render_template("index.html")
#app.route("/results/", methods=['GET'])
def results():
i = 0
other_list = []
data = {'Word': 'Number of times'}
cur = mysql.connection.cursor()
users = cur.execute("SELECT word, COUNT(*) FROM words GROUP BY word HAVING COUNT(*) > 0;")
if users > 0:
user_details = cur.fetchall()
sorted_details = sorted(user_details, key=lambda x: x[1], reverse=True)
for user in sorted_details:
if i < 9:
i = i+1
data[user[0]] = user[1]
if i >= 9:
other_list.append(user[1])
data['Other'] = sum(other_list)
i = i+1
return render_template("results.html", data=data)
#modifying the data from MySQL in order to be accepted for Google Charts API.
As you can see above, I use once redirect(url_for('results', requested_word=word)) and once render_template("results.html", data=data).
<h1>You have typed the word: {{requested_word}}</h1> #not working
<h2>Data for google API: {{data}}</h2> #working
I want when I enter a word and click submit to be redirected on my results page and I want to show which word have I entered.
I think that the problem comes from the fact that I use twice render_template with different arguments(codes) - once with (requested_word=word) and once with (data=data).
You only use render_template once within your example. I mean, at least within the part of the code you're referring to.
The redirect calls up another URL, i.e. route, through an HTTP forwarding.
The passed parameters are embedded in the URL, which are placed in the location header of the server's response. The browser then calls this URL automatically based on the HTTP status code. In this case 302 Found.
If you refer to another route using url_for and redirect, you should also query the parameters passed within your route to which you are forwarded.
You can do this either through variable rules
or through query parameters via request.args. The former takes the value and passes it as an argument to your function, which maps the route. For the second solution, you ask for the value using its key from a dictionary.
From your results route you can then pass the transferred word on to your jinja2 template, as you do with the variable "data".
I'm trying to allow the click trigger to do some backend dynamic csv creation, and then return it back to the user as a csv download file. I guess I'm not sure how I should write out the return statement other than just putting return response. I'v come across some other posts saying that I would need to set my url to a hidden iframe?? Not sure what this means though. Any tips?
Ajax looks like this:
$('#download-maxes').on('click', function(){
$.ajax({
type: "POST",
url: "{{request.path}}download/",
dataType: 'json',
async: false,
data: JSON.stringify(workouts),
success: function(workoutData) {
console.log(workoutData);
},
error:function(error){
console.log(error);
}
});
});
And my django view looks like this:
def download(request):
#(... a lot of mongo stuff here & other things defined)
workouts = json.load(request.body)
response = HttpResponse(content_type='text/xlsx')
response['Content-Disposition'] = 'attachment; filename="team_maxes.xlsx"'
writer = csv.writer(response)
writer.writerow(['Name', 'Date', 'Workout', 'Max'])
for member in team_members.all():
for wo in workouts:
wo_data = db.activity_types.find_one({"name": wo["name"]})
best_wo = db.activity.find_one({"u_id": member.user.id, "a_t": str(wo_data["_id"]), "is_last": 1}) or 0
member_name = member.user.first_name + ' ' + member.user.last_name
try:
max_stat = best_wo["y_ts"]
except:
max_stat = 0
try:
date = best_wo["e_d"]
except:
date = ""
workout_name = wo_data["name"]
writer.writerow([member_name, date, workout_name, max_stat])
return response
You don't need to use ajax. Since you are POSTing some json data to your view, just make a form with a hidden text input, and set it's value to the json data. Then make a regular submit button in the form.
When the form gets submitted, and the server responds with Content-Disposition: attachment; filename="team_maxes.xlsx", your browser will automatically trigger a download.
If you decide to go this route, Keep in mind:
You are using a regular html form now, with the POST method, so you must remember to use django's {% csrf_token %} tag inside of it.
You will probably have set the input's value to your json string, right before submitting the form.
Your workouts json gets sent in a form input. So, assuming you named your input "workouts", in your view you would do something like:
workouts = json.loads(request.POST.get('workouts'))
Plus a bunch of error checking, of course.
As part of a project, I am trying to have python receive a form sent by HTML. Upon getting the variables, I need python to print either "True" or "False" in the console. Here is my current HTML code for the form.
...
<form name = "input" action = "CheckLogin.py" method = "get">
<!-- This is the form. Here the user submits their username and password.
The words before the colon are the words that appear on the user's screen -->
Username: <input type = "text" name = "htmlUser">
<br>
Password: <input type = "password" name = "htmlPassword">
<input type = "submit" value = "Submit">
<br>
</form>
...
Once I have had them submit their username and password, I want to use this data to check if they can log into an email server (I have used Google's in this case, Microsoft Server 2003 doesn't play nicely.) Here is my Python 3 script to do so:
def login(username, password):
import poplib
try:
server = poplib.POP3_SSL('pop.gmail.com', 995)#Weird bug here. When I use the exeter server, I am informed that the SSL number is incorrect. How do I fix?
server.user(username)
server.pass_(password)
print ("True")
except:
print("False")
login (username, password)
My question is, how can I have python get the username and password variables from the HTML webform?
Just get those values in CheckLogin.py and pass them to login():
import cgi
# ...
i = cgi.FieldStorage()
username = i["htmlUser"]
password = i["htmlPassword"]
login(username, password)