# Python - Flask & MongoDB ### Create Virtual Environment Command Line ``` python -m venv {name of virtual envrionment} ``` for example ``` python -m venv .env ``` (. will make the folder invisible) Then install flask using pip install ``` pip install flask ``` After installation, remember to activate the environment --- ### Deployment example Heroku ``` pip install gunicorn ``` Procfile web: gunicorn app:app .flaskenv FLASK_APP=app.py FLASK_ENV=dev --- ### Create an app.py file ```python from flask import Flask # Create application object app = Flask(__name__) # Create route and its corresponding function @app.route("/") def index(): return "Hello Flask" # Run app.run() ``` Documentation: https://flask.palletsprojects.com/en/2.3.x/tutorial/layout/ --- ### Configurate Port Default port is 5000 ```python app.run(port=3000) ``` --- ### Configurate Route Route is written as a decorator Basic Configuration ```python @app.route("/data") def getData(): return "Here are some data" ``` Dynamica Configuration ```python @app.route("/user/<username>") # <username> is a variable def greeting(username): return f"Hello, {username}!" ``` --- ### Static files handling Statics files: do not execute program to get the requested files Steps: 1. Create a sub directory named "static" 2. Save any file into the folder 3. Obtain the file by visiting the route: /static/filename --- ### Customized Folder Customized at the creation of app object ```python app=Flask( __name__, static_folder="xxx", static_url_path="/xxx" ) ``` for example: ```python app = Flask( __name__, static_folder="static", static_url_path="/custom" ) ``` Folder name should be changed if the parameter is not "static" Usually, the following config is used: ```python static_url_path="/" ``` --- ### Request Basic Request: ```python from flask import request @app.route("/") def index(): print("Get Request Method", request.method) print("Get Protocal", request.scheme) print("Get Host Name", request.host) print("Get Path", request.path) print("Get URL", request.url) ``` Result: ``` Get Request Method GET Get Protocal http Get Host Name 127.0.0.1:5000 Get Path / Get URL http://127.0.0.1:5000/ ``` Headers related: ```python @app.route("/") def index(): print("Get User Agent", request.headers.get("user-agent")) print("Get Language", request.headers.get("accept-language")) print("Get Referrer", request.headers.get("referrer")) ``` Result: ``` Get User Agent Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.54 Get Language zh-TW,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 Get Referrer None ``` Some Application: Check Language: ```python lang = request.headers.get("accept-language") if lang.startswith("en"): return "Hello Flask" else: return "您好,歡迎光臨" ``` --- ### Query String format: ?x=xxx Implement a query string to calculate sum: ```python @app.route("/getSum") def getSum(): max_number = request.args.get("max", 100) # second parameter is the default value max_number = int(max_number) # need to cast to int total = 0 for n in range(1, max_number + 1): total += n return f"Sum Result: {total}" ``` URL: http://127.0.0.1:5000/getSum?max=1000 will result in 500500 Enhancement: pass in min and max and sum from min to max ```python @app.route("/getSum") def getSum(): min_number = request.args.get("min", 1) max_number = request.args.get("max", 100) total = 0 min_number = int(min_number) max_number = int(max_number) for n in range(min_number, max_number + 1): total += n return f"Result: {total}" ``` URL: http://127.0.0.1:5000/getSum?min=5&max=10 will result in 45 --- ### Response & Redirect Response in string ```python @app.route("/") def index(): return "something" ``` Response in JSON string: use json.dump() ```python @app.route("/") def index(): return json.dumps(dict) ``` **NOTES: Languages other than English will be encoded in ASCII, so we have to turn off the default parameter** ```python @app.route("/") def index(): return json.dumps(dict, ensure_ascii=False) ``` example: ```python @app.route("/") def index(): lang = request.headers.get("accept-language") if lang.startswith("en"): return json.dumps({ "status": "ok", "text": "Hello, world!" }) else: return json.dumps({ "status": "ok", "text": "您好,歡迎光臨" }, ensure_ascii=False) ``` Redirect: use redirect() ```python from flask import redirect @app.route("/") def index(): return redirect("url") ``` example ```python @app.route("/") def index(): return redirect("www.youtube.com") ``` homepage in different language application ```python @app.route("/") def index(): lang = request.headers.get("accept-language") if lang.startswith("en"): return redirect("/en/") else: return redirect("/zh/") @app.route("/en/") def index_english(): return json.dumps({ "status": "ok", "text": "Hello, world" }) @app.route("/zh/") def index_chinese(): return json.dumps({ "status": "ok", "text": "您好,歡迎光臨" }, ensure_ascii=False) ``` --- ### Template Engine ```python from flask import render_template @app.route("/") def index(): return render_template("filename") ``` template must be placed in a folder called "templates" Purpose: for faster and more complex front-end design example: This is a sample from template ```html <head> <h3>Hello {{ name }}! Welcome!</h3> <p>This content is rendered from template engine.</p> </head> ``` template engine variable can be passed in as a parameter ```python @app.route("/") def index(): return render_template("index", name="John") ``` Result: ``` Hello John! Welcome! This content is rendered from template engine. ``` --- ### Hyperlinks and Images(with html) Sample backend ```python app = Flask( __name__, static_folder="images", static_url_path="/" ) # Homepage Template @app.route("/") def index(): return render_template("index.html") @app.route("/page") def page(): return render_template("page.html") if __name__ == "__main__": app.run() ``` frontend: index.html ```htmlembedded <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>Hello and Welcome!</title> </head> <body> <h3>This is my first webpage</h3> <a href="https://css186.github.io/", target="_blank">This is my Portfolio Site</a> <h3>This is another webpage</h3> <a href="/page">Page</a> </body> </html> ``` page.html ```htmlembedded <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Second</title> </head> <body> <p>The second page!!</p> <br/> <img src="/xxx.jpg" /> </body> </html> ``` --- ### Form this will use the concept of query string Frontend ```htmlembedded <form action="url"> <input type="text" name="data" /> <button>submit</button> </form> ``` Backend ```python @app.route("url") def handle(): input = request.args.get("data", "") return "response" ``` Sample Code Frontend: Form ```htmlembedded <body> <hr/> <p>Calculation</p> <form action="/compute"> min: <input type="text" name="min" /> <br/> max: <input type="text" name="max" /> <br/> <button>Press to Compute</button> </form> <hr/> </body> ``` Result ```htmlembedded <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Result</title> </head> <body> <p>The Result is {{data}}</p> <a href="/">Return to Home</a> </body> </html> ``` Backend ```python @app.route("/compute") def compute(): min_num = request.args.get("min", "") max_num = request.args.get("max", "") total = 0 for i in range(int(min_num), int(max_num) + 1): total += i return render_template("result.html", data=total) ``` **the variable "total" will be passed into result.html page as {{data}} and then be presented on the webpage** --- ### Get & Post Method Get method is the default method ```python @app.get("/show") def show(): name = request.args.get("n", None) mail = request.args.get("m", None) return f"Your name is {name} and your email is {mail}" ``` ```htmlembedded <body> <p>Contact Me</p> <form action="/show", method="GET"> Name: <input type="text" name="n" /> <br/> Email: <input type="text" name="m" /> <br/> <button>Press to Submit</button> </form> </body> ``` Post Method the data from the user must be written in "request.form["variable name"]" ```python @app.post("/update") def handle(): input = request.form["data"] return input ``` ```htmlembedded <form action="/update", method="POST"> Data: <input type="text" name="data" /> <br/> <button>Press to Submit</button> </form> ``` Notes: * URL: GET * Hyperlinks: GET * Forms: GET or POST(default is GET, POST is safer than GET) -> inputting id & password will usually use POST Sample: Change homepage to specify GET method ```python @app.get("/") def index(): return render_template("index.html") ``` If we change the method to "@app.post("/")", then we will encounter a "Method not allowed error" Change compute page to POST method Backend ```python @app.post("/compute") def compute(): min_num = request.form["min"] max_num = request.form["max"] total = 0 for i in range(int(min_num), int(max_num) + 1): total += i return render_template("result.html", data=total) ``` Frontend ```htmlembedded <p>Calculation</p> <form action="/compute", method="POST"> min: <input type="text" name="min" /> <br/> max: <input type="text" name="max" /> <br/> <button>Press to Compute</button> </form> ``` --- ### Session Setting: ```python from flask import session app = Flask(...) app.secret_key = "XXXX" ``` secret_key should be secret Store variable into session ```python # Using session # Greeting page @app.get("/hello") def hello(): name = request.args.get("name", "") # store variable into session session["username"] = name return f"Hello, {name}" # Talk page @app.get("/talk") def talk(): username = session["username"] return f"Let's talk, {username}" ``` --- ### Python & MongoDB #### Connection: install pymongo[srv] ```python pip install pymongo[srv] ``` MongoDB Side * Set up network access and database access * Create cluster/database * Connect to cluster/database (choose "connect to your application") Connection Code in Python(provided by MongoDB and revised by me) ```python import pymongo uri = "mongodb+srv://username:password@cluster0.yf9rhuj.mongodb.net/?retryWrites=true&w=majority" # Create a new client and connect to the server client = pymongo.MongoClient(uri) ``` Add data into db (all data type will be a dictionary) ```python # Create a database named "test" (name can be changed) db = client.test # Create a collection named "users" (name can be changed) collection = db.users # Add data into MongoDB collection.insert_one({ "name": "Brian", "gender": "male" }) print("Add Successfully") ``` #### CRUD operation ##### 1. Create: Create: (one document) -> one dictionary ```python collection.insert_one(document) ``` Sample: ```python collection = db.users collection.insert_one({ "email": "test@test.com", "password": "test" }) ``` Obtain id of each record ```python result = collection.insert_one({ "email": "test@test.com", "password": "test" }) print(result.insert_id) ``` Create: (multiple documents) -> list of dictionary ```python colletion = db.users collection.insert_many([{ "email": "test@test.com", "password": "test" }, { "email": "abc@abc.com", "password": "abc" }]) ``` ```python result = collection.insert_many([{ "email": "test@test.com", "password": "test" }, { "email": "abc@abc.com", "password": "abc" }]) print(result.insert_ids) ``` ##### 2. Read: find the first record ```python collection = db.users data = collection.find_one() print(data) ``` find the record according to object id ```python from bson.objectid import ObjectId collection = db.users data = collection.find_one( ObjectId("xxxxxxxxx") ) print(data) ``` find all the record ```python collection = db.users cursor = collection.find() # Print data in loop for doc in cursor: print(doc) ``` ##### 3. Update: ``` collection.update_one(criteria, info to update) ``` Sample ```python collection = db.users collection.update_one({ "email": "test@test.com" }, { "$set":{ "password": "testtest" } }) ``` update many records ```python collection.update_many({ "level": 2 }, { "$set": { "role": "editor" } }) ``` **There are many methods to update the records** * "$set": replace / append * "$inc": increment * "$mul": multiply * "$unset": delete the field check update results: ```python # one record result = collection.update_one({....}) # many record result = collection.update_many({...}) # matches print(result.matches_count) # modified results print(result.modified_count) ``` Sample ```python # update new field result = collection.update_one({ "email": "john.wick@test.com" }, { "$set": { "description": "Hello and Die Hard" } }) print("Matched Count", result.matched_count) print("Modified Count", result.modified_count) # unset field result = collection.update_one({ "email": "john.wick@test.com" }, { "$unset": { "description": "" } }) print("Matched Count", result.matched_count) print("Modified Count", result.modified_count) ``` ##### 4. Delete: ``` collection.delete_one(criteria) collection.delete_many(criteria) ``` Get deleted count ```python collection = db.users result = collection.delete_many({ "level": 1 }) print(result.deleted_count) ``` #### Selection & Order Single Criteria ``` collection.find_one(selection criteria) collection.find(selection criteria) ``` Multiple Criteria ``` {"$and": [{criteria 1}, {criteria 2}...]} {"$or": [{criteria 1}, {criteria 2}...]} ``` Order ``` collection.find(criteria, sort=[("key_name", pymongo.ASCENDING | pymongo.DESCENDING)]) When criteria is {}, it means that we want all records to be sorted ``` Sample: ```python collection = db.users cursor = collection.find({}, sort=[ ("level", pymongo.ASCENDING) ]) for doc in cursor: print(doc) ``` --- ### Small Project - Full-Stack Membership System Functions: * Membership Registration * Member Login * Check Duplicate Registration * Connection to MongoDB #### Backend Code ```python # Create connection to database import pymongo uri = "xxxx" client = pymongo.MongoClient(uri) # Create database named "member_system" db = client.member_system # print("MongoDB Successfully Connected") # import Flask packages from flask import * # Create Flask application object app = Flask( __name__, static_folder = "public", static_url_path = "/" ) # Configurate session app.secret_key = "something secret" # homepage @app.route("/") def index(): return render_template("index.html") # Member page @app.route("/member") def member(): # Session control if "username" in session: # If valid, get username username = session.get("username", "") return render_template("member.html", username=username) else: return redirect("/") # Error page # /error?msg="error_message" @app.route("/error") def error(): error_message = request.args.get("msg", "Looks like there is something wrong.") return render_template("error.html", message=error_message) # Member sign-up(interact with db) # Use POST method to obtain input data @app.post("/signup") def sign_up(): # Receive input data from frontend input_username = request.form["username"] input_email = request.form["email"] input_password = request.form["password"] # check input data with mongoDB # named collection "users" collection = db.users # check if email is duplicated check_mail = collection.find_one({ "email": input_email }) # if duplicated(not None), then redirect to error page if check_mail: return redirect("/error?msg=This Email has been registered.") # else, store data into db collection.insert_one({ "username": input_username, "email": input_email, "password": input_password }) # and redirect to success page return render_template("success.html") # Log-in Page @app.route("/login") def login_page(): return render_template("login.html") # Member sign-in # Use Session to store sign-in data @app.post("/signin") def sign_in(): # Obtain users input input_email = request.form["email"] input_password = request.form["password"] # Check with db collection collection = db.users sign_in = collection.find_one({ "$and": [ {"email": input_email}, {"password": input_password} ] }) # if not found, then redirect to error page if not sign_in: return redirect("/error?msg=Wrong Username or Password.") # if found, store member info into session and redirect to memeber page session["username"] = sign_in["username"] return redirect("/member") # Member sign-out (Uses GET) # Remove user session @app.route("/signout") def sign_out(): # Remove session del session["username"] # Redirect to homepage return redirect("/") if __name__ == "__main__": # Configurate port=3000 app.run(port=3000) ``` #### Frontend Code **index(homepage)** ```htmlembedded <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Membership Registration System</title> <style> body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0; } .container { max-width: 400px; margin: 0 auto; background-color: #ffffff; padding: 20px; border-radius: 5px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); } h2 { text-align: center; } h3 { text-align: center; } label { display: block; margin-bottom: 5px; } .input-group { display: flex; flex-direction: column; margin-bottom: 15px; } input[type="text"], input[type="password"] { padding: 10px; border: 1px solid #ccc; border-radius: 3px; } button { background-color: #007BFF; color: #fff; border: none; padding: 10px 20px; cursor: pointer; border-radius: 3px; } button:hover { background-color: #0056b3; } .center-form { text-align: center; } p { text-align: center; } a { text-decoration: none; color: #007BFF; } a:hover { text-decoration: underline; } </style> </head> <body> <div class="container"> <h2>Contact Us</h2> <div> <h3>Please Leave Your Info to Become a Member</h3> <p>Already a Member? Click <a href="/login">Here</a></p> </div> <div class="center-form"> <form action="/signup" method="POST"> <div class="input-group"> <label for="username">Enter Your Name:</label> <input type="text" name="username" id="username" placeholder="Enter Your Name" required> </div> <div class="input-group"> <label for="email">Enter Your E-mail:</label> <input type="text" name="email" id="email" placeholder="name@example.com" required> </div> <div class="input-group"> <label for="password">Enter Your Password:</label> <input type="password" name="password" id="password" required> </div> <button type="submit">Submit</button> </form> </div> </div> </body> </html> ``` **member(memberpage)** ```htmlembedded <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Member Pages</title> <style> body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0; } .container { max-width: 400px; margin: 0 auto; background-color: #ffffff; padding: 20px; border-radius: 5px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); } h2 { text-align: center; } div { text-align: center; margin: 20px 0; } button { background-color: #007BFF; color: #fff; border: none; padding: 10px 20px; cursor: pointer; border-radius: 3px; display: block; margin: 0 auto; } button:hover { background-color: #0056b3; } </style> </head> <body> <div class="container"> <h2>Welcome!!</h2> <div>Nice seeing you, {{ username }}!</div> <button class="return-button" onclick="window.location.href='/signout';">Sign Out</button> </div> </body> </html> ``` **error** ```htmlembedded <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Error</title> <style> body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0; } .container { max-width: 400px; margin: 0 auto; background-color: #ffffff; padding: 20px; border-radius: 5px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); } h2 { text-align: center; } div { text-align: center; margin: 20px 0; } button { background-color: #007BFF; color: #fff; border: none; padding: 10px 20px; cursor: pointer; border-radius: 3px; display: block; margin: 0 auto; } button:hover { background-color: #0056b3; } </style> </head> <body> <h2>Oops...</h2> <div>{{ message }}</div> <div>Please try again.</div> <button onclick="window.location.href='/';"> Return Home</button> </body> </html> ``` **login** ```htmlembedded <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Member Login</title> <style> body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0; } .container { max-width: 400px; margin: 0 auto; background-color: #ffffff; padding: 20px; border-radius: 5px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); } h3 { text-align: center; } label { display: block; margin-bottom: 5px; } .input-group { display: flex; flex-direction: column; margin-bottom: 15px; } input[type="text"], input[type="password"] { width: 95%; padding: 10px; margin-bottom: 15px; border: 1px solid #ccc; border-radius: 3px; } button { background-color: #007BFF; color: #fff; border: none; padding: 10px 20px; cursor: pointer; border-radius: 3px; width: 100%; } button:hover { background-color: #0056b3; } .center-form { text-align: center; } .return-button { background-color: #ccc; color: #333; border: none; padding: 10px 20px; cursor: pointer; border-radius: 3px; margin-top: 10px; width: 100%; } .return-button:hover { background-color: #999; } </style> </head> <body> <div class="container"> <h3>Member Login</h3> <div class="center-form"> <form action="/signin" method="POST"> <label for="email">E-mail:</label> <input type="text" name="email" id="email" placeholder="name@example.com" required> <label for="password">Password:</label> <input type="password" name="password" id="password" required> <button type="submit">Log in</button> </form> <button class="return-button" onclick="window.location.href='/';">Return Home</button> </div> </div> </body> </html> ``` **success** ```htmlembedded <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Redirect to Homepage in 3 second --> <meta http-equiv = "refresh" content = "3; url = /" /> <title>Success</title> </head> <body> <h3>Registered Successfully!</h3> <p>Will return to Homepage shortly.</p> </body> </html> ```