Flask_from_zero_to_docker ======================== # Pre-requirements ## Create a Python virtual enviroement You can create a virtual enviroment using the *venv* command that comes with *Python*. this will create a virtual enviroment in the specified *.venv* directory. I like to make it hidden so that it doesn't show up during developpment. ```bash python3 -m venv .venv ``` --- ## Activate the virtual enviroement Then you need to activate the virtual enviroment. - for Bash ```bash source .venv/bin/activate ``` - for Fish ```bash . .venv/bin/activate.fish ``` --- If executed corectly your *(.venv)* should apear on your terminal ```bash (.venv) key@devbian ~/g/m/flask_to_docker > ``` --- ## Install libraries and make them exportable Once this is done you can check the installed libraries using *pip*. ```bash pip3 freeze ``` This should return empty as we didn't installed anything yet. So let's proceed and install Flaks ```bash pip3 install flask ``` Let's chech once again with *pip freeze*. ```bash pip3 freeze ``` This time you shuld get a list of installed libraries. ```bash (.venv) key@devbian ~/g/m/flask_to_docker > pip3 freeze blinker==1.8.2 click==8.1.7 Flask==3.0.3 itsdangerous==2.2.0 Jinja2==3.1.4 MarkupSafe==2.1.5 Werkzeug==3.0.3 ``` You can also expoirt the required libraries to a text file. ```bash pip3 freeze > requirements.txt ``` This file can afterward be usted to automatically install all the libraries used on this porject. ```bash pip install -r requirements.txt ``` This make it very easy to add *.venv* to your git ingnore list and for someone colning your project to easely install *pip libraries* after cloning your porject. --- # Default application [YouTube](https://youtu.be/o3bCVqF9gI0?si=WwHFP83WFAhRpMtf) ## The main funtion to start a Flask Server This is slef explanatory but each [Flask](https://flask.palletsprojects.com/en/2.3.x/tutorial/factory/) application start the same way. Let's create an *app.py* and start with the following steps. - Import *Flask* - We declare the *app()* - Add decoration for routes [@app.route('/')](https://flask.palletsprojects.com/en/2.3.x/api/#flask.Flask.route) - Define a function under the decoration *index():* - Run the app wiht [app.run()](https://flask.palletsprojects.com/en/2.3.x/api/#flask.Flask.run) ```python from flask import Flask # Importing flask app = Flask(__name__) @app.route('/') def index(): return "Hello World" if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True) ``` In the *run()* funtion there are some main parameters that needs to be defined these correspond to: - **host** ([*str*](https://docs.python.org/3/library/stdtypes.html#str)* | **None*) – the hostname to listen on. Set this to *0.0.0.0* to have the server available externally as well. Defaults to *127.0.0.1* (AKA *localhost*) or the host in the *SERVER_NAME* config variable if present. - **port** ([*int*](https://docs.python.org/3/library/functions.html#int)* | **None*) – the port of the webserver. Defaults to *5000* or the port defined in the *SERVER_NAME* config variable if present. - **debug** ([*bool*](https://docs.python.org/3/library/functions.html#bool)* | **None*) if given, enable or disable debug mode. See [debug](https://flask.palletsprojects.com/en/2.3.x/api/#flask.Flask.debug). --- ## First Run The firsat run is simple enought. Just execute your *app.py*. Just make sure that your *venv* is active. ```bash (.venv) key@devbian ~/g/m/flask_to_docker > python3 app.py ``` you should be greeted wiht : ```bash * Serving Flask app 'app' * Debug mode: on WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:5000 * Running on http://192.168.1.128:5000 Press CTRL+C to quit * Restarting with stat * Debugger is active! * Debugger PIN: 633-588-052 ``` If this is tha case open [http://localhost:5000/](http://localhost:5000/) on wour browser and you should be greeted wiht your *hello wolrd* message. ## Congratulations you have a running Flask Application. --- # Flask URL's & Route [YouTube](https://youtu.be/Jl9XzSPXSe4?si=TZAs7OxCU-TkGdjO) These are Some code snippets wiht examples please do not forget tha the start annd the end is the same as the *Default Application* described just before. ## Main Route Hereyou can see as before the main route *(/)* ```python @app.route('/') def index(): return "Welcome" ``` --- ## URL proccessors We can also use url proccessors to get something that we have typed on the url. In this case the url to visit would be [http://localhost:5000/greet/kerem](http://localhost:5000/greet/kerem) ```python @app.route('/greet/<name>') def greet(name): return f"Hello {name}" ``` Variables given to the url can also be *typed* here as integers. This allows us to do some advanced operations. In this case the url to visit would be [http://localhost:5000/add/20/10](http://localhost:5000/add/20/10) ```python @app.route('/add/<int:number1>/<int:number2>') def add(number1, number2): return f'{number1} + {number2} = {number1+number2}' ``` --- ## Responses Now to do generate some responses. Here you can use [curl](https://curl.se/) so see exactly what is happening ```python #We can also return some responses (like 404 page not found and co) @app.route('/hello') def hello(): response = make_response('Hello World') response.status_code = 202 response.headers['content-type'] = 'application/octet-stream' return response ``` So if we were to use *curl* we would gete here the following response.*http://127.0.0.1:5000/hello* is the same as *http://localhost:5000/hello* ```bash key@devbian ~> curl -I http://127.0.0.1:5000/hello HTTP/1.1 202 ACCEPTED Server: Werkzeug/3.0.3 Python/3.11.2 Date: Wed, 26 Jun 2024 07:06:06 GMT content-type: application/octet-stream Content-Length: 11 Connection: close ``` --- ## Methods This is the way to allow or block some [Methods](https://www.w3schools.com/tags/att_method.asp). ```python @app.route('/test_methodes', methods=['GET', 'POST']) def test_methodes(): if request.method == 'GET': return f"You made a GET request\n" if request.method == 'POST': return f"You made a POST request\n" else: return f"You will never see this message\n" ``` --- ## Parameters You have surely seen some Parametes embedded in liks like *?user=bgfdrt56+pass=Rtrssdf*. Flask alows some asy maniputation of those parameters. To test this : [http://localhost:5000/handle_url_params?greeting=hello&name=kerem](http://localhost:5000/handle_url_params?greeting=hello&name=kerem). If there was a mistmatch you would get the Error message instead. ```python #Handle parameters : @app.route('/handle_url_params') def handle_url_params(): if 'greeting' in request.args.keys() and 'name' in request.args.keys(): greeting = request.args['greeting'] name = request.args.get('name') return f'{greeting}, {name}' else: return f'some params are missing' ``` --- # Flask HTML files And [TEMPLATES](https://flask.palletsprojects.com/en/3.0.x/tutorial/templates/) [YouTube](https://youtu.be/w6Ui_DVxluc?si=B2JshnjVMu3DWcBC) ## Declare that we use Templates To indicate that we will use *templates* first we need to create a *directory* containing those tmeplates and second we need to *reference* it in our code. let's create the *directory* ```bash key@devbian ~/g/m/flask_to_docker > mkdir templates key@devbian ~/g/m/flask_to_docker > cd templates key@devbian ~/g/m/flask_to_docker > touch index.html ``` The sturcture should look like this. We can use [Tree](https://linux.die.net/man/1/tree) to find this out ```bash key@devbian ~/g/m/flask_to_docker > tree . ├── app.py └── templates └── index.html ``` Let's declare it in our code. *template_folder* take the source of *app.py* as starting route for directories. ```python app = Flask(__name__, template_folder='templates') ``` --- ## Using Templates The [`render_template()`](https://flask.palletsprojects.com/en/2.3.x/templating/#) let's use use tem when returning the content of the page. ```python @app.route('/') def index(): myval = 'Kynsight' myres = 30 mylist = [10,20,30,40,50] return render_template('index.html', value=myval, result=myres, list=mylist) ``` Now this allows us to have a huge flexibility that HTML doesn't otherwise allow. This is thnaks to [Jinja](https://jinja.palletsprojects.com/en/3.1.x/templates/). Let's see how it works. ## HTML & Jinja Now let's have a look to `index.html` herre we can now se some *Jinja* templates funtionlaities. ```HTML <!DOCTYPE html> <html lang="en"> <head charset="UTF-8"> <title>Flask APP</title> </head> <body> <h1>My First Heading</h1> <p>My value: {{ value }}</p> <p>My result: {{ result }}</p> <ul> {% for item in list %} {% if item == 30 %} <li style="color: red">{{ item }}</li> {% else %} <li {%if item == 20 %} style="color: blue" {%endif%} >{{ item }}</li> {% endif %} {% endfor %} </ul> </body> </html> ``` *Jinja* Allows us to use these kind of functions : `{% for item in list %}` that are ususaly inpossible wiht HTML. All *Jinja* Functions starts with `{%` and ends with `%}`. - They can also be used inline like: `<li {%if item == 20 %} style="color: blue" {%endif%} >{{ item }}</li>`. --- ## Extend Templates We cna extend templates to other templates. What does this bring us ? Let's say we want ta have the *same header* on *multiple pages* we can use a template with the header and only add an *content* *BLOCK* onto this template. In *templeates* directory we can now create a base html file let's call it *base.htm* and two pages that extends on it *index.html* and *other.html*. ```bash key@devbian ~/g/m/flask_to_docker > cd templates key@devbian ~/g/m/flask_to_docker > touch base.html other.html ``` the structure should look like this ```bash key@devbian ~/g/m/flask_to_docker > tree . ├── app.py └── templates ├── base.html ├── index.html └── other.html ``` *base.html* ```HTML <!DOCTYPE html> <html lang="en"> <head charset="UTF-8"> <title>Flask APP</title> </head> <body> <h1>My First Heading</h1> <p>My value: {{ value }}</p> <p>My result: {{ result }}</p> <ul> {% for item in list %} {% if item == 30 %} <li style="color: red">{{ item }}</li> {% else %} <li {%if item == 20 %} style="color: blue" {%endif%} >{{ item }}</li> {% endif %} {% endfor %} </ul> </body> </html> ``` *index.html* Here you can see that we are extending frim *base.html* with `{% extends "base.html" %}`. - we can indicate a title using the `block` that we calle title. - we can fill the content using the `block` that we calle content. ```HTML {% extends "base.html" %} {% block title %}Index Page{% endblock %} {% block content %} <h1>My First Heading</h1> <p>My value: {{ value }}</p> <p>My result: {{ result }}</p> <ul> {% for item in list %} {% if item == 30 %} <li style="color: red">{{ item }}</li> {% else %} <li {%if item == 20 %} style="color: blue" {%endif%} >{{ item }}</li> {% endif %} {% endfor %} </ul> {% endblock %} ``` *other.html* all the same rules applies here too. ```HTML {% extends "base.html" %} {% block title %}Other Page{% endblock %} {% block content %} <h1>Other</h1> {% endblock %} ``` but here we need to add a route to this page at *app.py* ```PYTHON @app.route('/other') def other(): return render_template('other.html') ``` --- ## Jinja [Filters](https://jinja.palletsprojects.com/en/3.0.x/templates/#filters) Filters that python uses lin `.uppercase` can't be used in Jinja. There are some filters taht already exists but most importnatly we can create our own filters. Here is an filter that alredy exitst which mase everything uppercase. But every filter needs to be piped trhpug Jinja thus the`|`. ```HTML <p> {{ sometext|upper }}</p> ``` Let's create a exmaple file for filters. Create *filter.html* in templates, and add a custom Filter namde `reverse_string` *filter.html*: ```HTML {% extends "base.html" %} {% block title %}Filter Page{% endblock %} {% block content %} <h1>Filter</h1> <p> {{ sometext }}</p> <p> {{ sometext|upper }}</p> <p> {{ sometext|lower }}</p> <p> {{ sometext|replace('l', 'L') }}</p> <p> {{ sometext|reverse_string }}</p> {% endblock %} ``` We of course need to define `reverse_string` in *app.py* as follows : ```PYTHON @app.template_filter('reverse_string') def reverse_string(s): return s[::-1] ``` you can see that we use a speciel decorator named [template_filter](https://tedboy.github.io/flask/generated/generated/flask.Flask.template_filter.html) to declare our custom filters. --- # Redirection Redirections can be tricky but thankfully Flask has a way od dynamic *redirection* wich takes a *function* as *argument* ```PYTHON @app.route('/redirect_endpoint') def redirect_endpoint(): return redirect(url_for('other')) ``` the *other* in `return redirect(url_for('other'))` used above, is redirection to the `def other():`defined under V: ```PYTHON @app.route('/wewwa') def other(): return render_template('other.html') ``` this mean when we type [http://localhost:5000/redirect_endpoint](http://localhost:5000/redirect_endpoint) we will be redirected to [http://localhost:5000/wewwa](http://localhost:5000/wewwa) with the page defined at *other.html* --- # GET & POST To have some fields is much better ! Here is an example how to handle some GET & POST Methods *app.py* ```PYTHON @app.route('/', methods=['GET', 'POST']) def index(): if request.method == 'GET': return render_template('index.html') elif request.method == 'POST': username = request.form.get('username') password = request.form.get('password') if username == 'kynsight' and password == 'pass': return 'Success' else: return 'Failure' ``` *inted.html* ```HTML {% extends "base.html" %} {% block title %}Index Page{% endblock %} {% block content %} <h1>Hellooo</h1> <form method="POST" action="{{ url_for('index') }}"> <input type="text" name="username" placeholder="Username"><br> <input type="text" name="password" placeholder="Password"><br> <input type="submit" value="login"> </form> {% endblock %} ``` So what is going on here ? - First we check in `index():` function with `if request.method == 'xxx':` What *Method* is being used. - If the *Methode* is *GET* we just print the page wiht the *Form* using `return render_template('index.html')` - If the *Methode* is *POST* we collect the information filler in *form* sent by pressing *submit* - We get collect the *information* by *referencing* form items by their *name* attribute : `request.form.get('username')` - Then we can put these infiormation in varibales and use them as we please. --- # Uploading & downloading files Be careful here are some notions that are not related to Flask or Jinja. Like [Pandas](https://pandas.pydata.org/) for excel manipulation and [os](https://docs.python.org/3/library/os.html) of opperating system functions. Nevertheless this is a very important topic with widely used applications for website. ## Uploading a file Here is the *File Upload form* located in *index.html* the most important element to know are : - [enctype="multipart/form-data"](https://www.w3schools.com/tags/att_form_enctype.asp) Used while uploading files - [accept=](https://www.w3schools.com/tags/att_input_accept.asp)"[media-types](http://www.iana.org/assignments/media-types/media-types.xhtml)" do define which types of files are allowed. ```html <h1>File Upload</h1> <form method="POST" action="{{ url_for('file_upload') }}" enctype="multipart/form-data"> <input type="file" name="file" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain" ><br> <input type="submit" value="upload File"> </form> ``` Wiht the `action="{{ url_for('file_upload') }}"` we go back to our *app.py* funtion *def file_upload():* where we treat this information. Where we do nothing else then to read the file and print his output. - If it's a *Text* file we put directly it's output - If it's a *Excel* file we read the file using pandas `df = pd.read_excel(file)` and return the result as html table using `return df.to_html()` ```python import pandas as pd @app.route('/file_upload', methods=['GET', 'POST']) def file_upload(): file = request.files.get('file') if file.content_type == 'text/plain': return file.read().decode() elif file.content_type == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' or file.content_type == 'application/vnd.ms-excel': df = pd.read_excel(file) return df.to_html() ``` --- ## Download a file directly Here is the *File Upload form* located in *index.html* tnothing mroe to know then the previous example: ```html <h1>Convert To CSV</h1> <form method="POST" action="{{ url_for('convert_csv') }}" enctype="multipart/form-data"> <input type="file" name="file" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" ><br> <input type="submit" value="upload File"> </form> ``` Wiht the `action="{{ url_for('convert_csv') }}"` we go back to our *app.py* funtion *def convert_csv():* where we convert the excel into csv but also directly donwload the ouput. It is a specific example and we don't need to waste too much time on it as there is, in my opignon a much better way to do this. ```python @app.route('/convert_csv', methods=['GET', 'POST']) def convert_csv(): file = request.files.get('file') df = pd.read_excel(file) response = Response( df.to_csv(), mimetype = 'text/csv', headers={'Content-Disposition': 'attachement; filename=result.csv'} ) return response ``` --- ## Download a file thourgh a directory Again nothing special to note here, same a the ones before. ```html <h1>Convert To CSV Two</h1> <form method="POST" action="{{ url_for('convert_csv_two') }}" enctype="multipart/form-data"> <input type="file" name="file" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" ><br> <input type="submit" value="upload File"> </form> ``` Wiht the `action="{{ url_for('convert_csv_two') }}"` we go back to our *app.py* funtion *def convert_csv_two():*, convert the file to csv and make it redy for download: - `os.makedirs('downloads')` we create a donwload directory - `filename = f'{uuid.uuid4()}.cvs'` we give the file a unique name - `df.to_csv(os.path.join('downloads', filename))`we convert the file into csv and register in *downloads* with his*filename* - `return render_template('download.html', filename=filename)` we return the *download.html* page with *filename* as argument ```python @app.route('/convert_csv_two', methods=['GET', 'POST']) def convert_csv_two(): file = request.files.get('file') df = pd.read_excel(file) if not os.path.exists('downloads'): os.makedirs('downloads') filename = f'{uuid.uuid4()}.cvs' df.to_csv(os.path.join('downloads', filename)) return render_template('download.html', filename=filename) ``` for that to work we need to create the *download.html* in *templates* directory: - `href="{{ url_for('download', filename=filename) }}"`Directs the request to `def download(filename):` function which generates the download proccess ```html {% extends "base.html" %} {% block title %}Download Page{% endblock %} {% block content %} <h1>Download {{ filename }}</h1> <a href="{{ url_for('download', filename=filename) }}"> Donwload </a> {% endblock %} ``` and aso create a new route for *download* in *app.py*: - `@app.route('/download/<filename>'` the route has de name route to */download* but allso the argument *<filename>* - `send_from_directory('downloads', filename, download_name='result.csv')` alows us to *send* the file *downloads* directory and *rename* it at the same time ```python @app.route('/download/<filename>', methods=['GET', 'POST']) def download(filename): return send_from_directory('downloads', filename, download_name='result.csv') ``` --- # JS/json Request for live update todo if needed. for now i am nto so shure. --- # Static Files & Integrating Bootstrap [YouTube](https://youtu.be/9At5sg-jPpU?si=q7GW8WL5Iz2V7HUa) To declare static file location (previously created), it just must be added to the creator of the Flask *app*. This way we can give */* as a root directory for each static file Ex: */css/style.css*. Flaks now know that he needs to search under */*static/css/style.css* this is practical for the future if we dise to change the ridectory of the *static* file. ```python app = Flask(__name__, template_folder='templates', static_folder='static', static_url_path='/') ``` Then we cna implement them on our pages as follows -> for Example : bootsrap in our *base.html* file : - `<link rel="stylesheet" type="text/css" href="/css/bootstrap.css"/>` To add bootstrap css - `<script src="/js/bootstrap.js">` To add bootstrap JavaScript ```html <!DOCTYPE html> <html lang="en"> <head charset="UTF-8"> <title>{% block title %}Flask APP {% endblock %}</title> <link rel="stylesheet" type="text/css" href="/css/style.css"/> <link rel="stylesheet" type="text/css" href="/css/bootstrap.css"/> </head> <body> <h1>This is from the base ;)</h1> {% block content %} {% endblock %} <script src="/js/bootstrap.js"> </body> </html> ``` which gives us a file structure like so : ```bahs key@devbian ~/g/m/flask_to_docker > tree . ├── app.py ├── README.md ├── static │ ├── css │ │ ├── bootstrap.css │ │ . │ │ . │ │ ├── bootstrap-utilities.rtl.min.css.map │ │ └── style.css │ ├── img │ │ └── logo.png │ └── js │ ├── bootstrap.js │ . │ . │ └── hello.js └── templates ├── base.html └── index.html ``` As you can see we also have implmented our custom javascript *hello.js* and this can also be implmented on inuque pages like so : ```javascript window.onload = function() { setTimeout(function(){alert('Bye Bye')}, 5000); }; ``` We can now use it on our *index.html* like so: - `<script src="/js/hello.js"></script>` ```html {% extends "base.html" %} {% block title %}Index Page{% endblock %} {% block content %} <h1>Hello Wolrd</h1> <p class="special">Hello</p> <a href="#" class="btn btn-primary">Button Text</a> <img src="/img/logo.png" alt="Text"/> <script src="/js/hello.js"></script> {% endblock %} ``` --- # Session and Cookies [YouTube](https://www.youtube.com/watch?v=8jOgqA7nlLo&list=PL7yh-TELLS1EyAye_UMnlsTGKxg8uatkM&index=7) *Sessions* and *cookies* are a very important part of a website. in shot - *Cookie* are on the *client side* and can be accessed by the user ! - *session* on the other hand are stored on the *server side* and are very good to keep sensitive information that the user should not be anble to see or modify. So let'a jump in to our *app.py* and let me show you how where we create, read, and delete *cookies* and *sessions* - inludes and intitaltion ```python from flask import session app.secret_key = 'SOME KEY' ``` - create a *session* and implment some *variables* called *keys* and render *index.html* with a *message* ```python @app.route('/set_data') def set_data(): session['name'] = 'Session1' session['other'] = 'HelloWorld' return render_template('index.html', message='Session data set.') ``` - First we check if the *key* exist with `if 'name' in session.keys()` - Then we can put them into into varibale, and generate *index.html* with a message inculde those *variables* ```python @app.route('/get_data') def get_data(): if 'name' in session.keys() and 'other' in session.keys(): name = session['name'] other = session['other'] return render_template('index.html', message=f'Name: {name} Content: {other}.') else: return render_template('index.html', message='Session has no data set.') ``` - here is how to *clear* a *session*. ```python @app.route('/clear_session') def clear_session(): session.clear() return render_template('index.html', message='Session has been cleared') ``` - creating a *cookie* requires a *response* command as it is stored on the *client side* side - First we create a response containig the *index.html* to render. - and add a *cookie* to this *response*. ```python @app.route('/set_cookie') def set_cookie(): response = make_response(render_template('index.html', message='Cookie Set')) response.set_cookie('cookie_name', 'cookie_value') return response ``` - To get the *cookies* content we need a *request*, again as it is stored on de *client side*. ```python @app.route('/get_cookie') def get_cookie(): cookie_value = request.cookies['cookie_name'] return render_template('index.html', message=f'Cookie value: {cookie_value}') ``` - To dekete the *cookies* content we a requires a *response*, again this time with `response.set_cookie('cookie_name', expires=0)` *expires=0* indicating that the cookie has expired. ```python @app.route('/remove_cookie') def remove_cookie(): response = make_response(render_template('index.html', message='Cookie removed')) response.set_cookie('cookie_name', expires=0) return response ``` --- # Message Flashing [YouTube](https://youtu.be/8jOgqA7nlLo?si=H8vJWGqg1mJAODzs&t=938) This can be made without using javascript, so it is cool ! *flash* module form *flask* allows us to implment flashing messages that *jinja* can intreprate. This can be very usefull to implment when we need to track the flow of opperation on a website. Here is an example how to implement it on our *app.py*, *index.html*, *base.html* and showing it by creating a small *login.html* - The route to be created at *app.py* dont forget to inlcude *flash* from *FLask*: - With the *'GET'* indicates that we are oppneing the page so we generate *login.html* - Afterwards whne we put our infomration an send a *'POST* request trough *login.html* we check if the data is correct an flash a message accordingly. Ex:`flash('Successfull Login!')`. ```python @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'GET': return render_template('login.html') elif request.method == 'POST': username = request.form.get('username') password = request.form.get('password') if username == 'kynsight' and password == 'pass': flash('Successfull Login!') return render_template('index.html', message="") else: flash('Login Failed!') return render_template('index.html', message="") ``` - content of *login.html* - with `<input type="submit" value="login">` we cna see that we point back to `def login():`. ```html {% extends "base.html" %} {% block title %}login Page{% endblock %} {% block content %} <h1>Login</h1> <form action="{{ url_for('login') }}" method='POST'> <input type="text" name="username" placeholder="user name"> <input type="text" name="password" placeholder="password"> <input type="submit" value="login"> </form> {% endblock %} ``` - when we flash the message we want it to be seen on each page so we include it on *base.html* on the `<body>` - Please be careful and note that the `get_flashed_messages()` function is *not* placed in the `{% block content %} {% endblock %}` ```html <body> <h1>This is from the base ;)</h1> {% with messages = get_flashed_messages() %} {% if messages %} <ul> {% for message in messages %} <li>{{ message }}</li> {% endfor%} </ul> {% endif %} {% endwith %} {% block content %} {% endblock %} <script src="/js/bootstrap.js"> </body> ``` --- # Flask SQLalchemy [YouTube](https://www.youtube.com/watch?v=RUUmEOooKVo&list=PL7yh-TELLS1EyAye_UMnlsTGKxg8uatkM&index=7) Here we need to restructure all of our code due to what is called [Circular Imports](https://stackoverflow.com/questions/22187279/why-do-circular-imports-seemingly-work-further-up-in-the-call-stack-but-then-rai) in Python. Have a look here : we have created a main folder *dbapp* which now contains all the required files to run our application. On top we have now *app.py* alongside *run.py* and *routes.py* thesse are now crutial to propoerly run our application. *models.py* will be explained further down. ```bash key@devbian ~/g/m/f/.o/7 (master)> tree -L 3 . └── dbapp ├── app.py ├── models.py ├── routes.py ├── run.py ├── static │ ├── css │ ├── img │ └── js └── templates ├── base.html ├── index.html └── login.html ``` let's start with *run,py*. here we now only import *flask* create the app and then run it in *\_\_main\_\_*. So in the future we will have to call `python3 run.py` to run our application - `create_app()` will be defined in *app.py* we will come to that later. ```python from app import create_app flask_app = create_app() if __name__ == '__main__': flask_app.run(host='0.0.0.0', debug='Ture') ``` Now for *routes.py* here we change the routes definition so that we can call it with `def register_routes(app, db):` durin initiation at *app.py*. We also need to pass the database *db* as we will implement it just afterwards. ```python from flask import Flask, render_template, request from models import Person def register_routes(app, db): @app.route('/', methods=['GET','POST']) def index(): return "Hello World" ``` Last but not least *app.py* here comes everything together: - we declare our main *import*s which doesn't cause any circular import issues - we create our db with `db = SQLAlchemy()` - `create_app()` will is defined. - `db.init_app(app)` link our *db* to our *app*. - Here is one way to avoid Circular Imports : `from routes import register_routes` we import our *routes.py* in the funtion `def create_app():` - `register_routes(app, db)` we create our *routes* - `Migrate(app, db)` we migrate our *database* ```python from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate db = SQLAlchemy() def create_app(): app = Flask(__name__, template_folder='templates', static_folder='static', static_url_path='/') app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///./test.db' db.init_app(app) from routes import register_routes register_routes(app, db) Migrate(app, db) return app ``` ## How to create and update/upgrade the database. Let's stard by definin some tables in our *models.py* - `class Person(db.Model):` Our person class is a *Database* *Model* - `__tablename__ = 'person'` will be the *table name* - `def __repr__(self):`Will *format* the *default* return wehn we do a *query* ```python from app import db class Person(db.Model): __tablename__ = 'person' pid = db.Column(db.Integer, primary_key=True) name = db.Column(db.Text, nullable=False) age = db.Column(db.Integer) job = db.Column(db.Text) def __repr__(self): return f'Person with name: {self.name} and age {self.age}' ``` Now we can *initiate* -> *migrate* -> *upgrade* our database Be careful!! each time we change a table in *models.py* we then need to *migrate* -> *upgrade* our database once more. ```bash (.venv) key@devbian ~/g/m/f/authapp (master)> flask db init Creating directory '/home/key/git/minibase/flask_to_docker/authapp/migrations' ... done Creating directory '/home/key/git/minibase/flask_to_docker/authapp/migrations/versions' ... done Generating /home/key/git/minibase/flask_to_docker/authapp/migrations/README ... done Generating /home/key/git/minibase/flask_to_docker/authapp/migrations/alembic.ini ... done Generating /home/key/git/minibase/flask_to_docker/authapp/migrations/script.py.mako ... done Generating /home/key/git/minibase/flask_to_docker/authapp/migrations/env.py ... done Please edit configuration/connection/logging settings in '/home/key/git/minibase/flask_to_docker/authapp/migrations/alembic.ini' before proceeding. (.venv) key@devbian ~/g/m/f/authapp (master)> flask db migrate INFO [alembic.runtime.migration] Context impl SQLiteImpl. INFO [alembic.runtime.migration] Will assume non-transactional DDL. INFO [alembic.autogenerate.compare] Detected added table 'person' Generating /home/key/git/minibase/flask_to_docker/authapp/migrations/versions/0b1a6cc67556_.py ... done (.venv) key@devbian ~/g/m/f/authapp (master)> flask db upgrade INFO [alembic.runtime.migration] Context impl SQLiteImpl. INFO [alembic.runtime.migration] Will assume non-transactional DDL. INFO [alembic.runtime.migration] Running upgrade -> 0b1a6cc67556, empty message ``` These commands will then create some new directories and detect the tables created in *models.py*. ```bash key@devbian ~/g/m/f/.o/7 (master)> tree -L 3 . └── dbapp ├── app.py ├── instance │ └── test.db ├── migrations │ ├── alembic.ini │ ├── env.py │ ├── __pycache__ │ ├── README │ ├── script.py.mako │ └── versions ├── models.py ├── __pycache__ │ ├── app.cpython-311.pyc │ ├── models.cpython-311.pyc │ └── routes.cpython-311.pyc ├── routes.py ├── run.py ├── static │ ├── css │ ├── img │ └── js └── templates ├── base.html ├── index.html └── login.html ``` --- # Creating User Login function (Implmentation of the database)[YouTube](https://www.youtube.com/watch?v=t9zA1gvrTvo&list=PL7yh-TELLS1EyAye_UMnlsTGKxg8uatkM&index=9) Now let's implement on top of what we did before, a login manager. - *models.py* - We import [UserMixin](https://stackoverflow.com/questions/63231163/what-is-the-usermixin-in-flask) to add some aditiona properties to our users like `get_id(self):` ```python from app import db from flask_login import UserMixin class User(db.Model, UserMixin): __tablename__ = 'users' uid = db.Column(db.Integer, primary_key=True) username = db.Column(db.String, nullable=False) password = db.Column(db.String, nullable=False) role = db.Column(db.String, nullable=True) description = db.Column(db.String, nullable=True) def __repr__(self): return f'<User:{self.username}, {self.role}>' def get_id(self): return self.uid ``` ```python from flask import Flask, render_template, request from models import Person def register_routes(app, db): @app.route('/', methods=['GET','POST']) def index(): if request.method == 'GET': people = Person.query.all() return render_template('index.html', people=people) elif request.method == 'POST': name = request.form.get('name') age = int(request.form.get('age')) job = request.form.get('job') person = Person(name=name, age=age, job=job) db.session.add(person) db.session.commit() people = Person.query.all() return render_template('index.html', people=people) @app.route('/delete/<pid>', methods=['DELETE']) def delete(pid): Person.query.filter(Person.pid==pid).delete() people = Person.query.all() db.session.commit() people = Person.query.all() return render_template('index.html', people=people) ``` Now let's go spte by step through *routes.py* - te required *imports* ```python from flask import Flask, render_template, request, redirect, url_for from models import User from flask_login import login_user, logout_user, current_user, login_required ``` - sign up: we get the information from the *from* in *signup.html* $ - Hash the password to safely save it in our *DB* with `hashed_pass = brcypt.generate_password_hash(password)` - then create a user object with `user = User(username=username, password=hashed_pass)` - add it to the database `db.session.add(user)` - commit (save) the database `db.session.commit()` ```python @app.route('/signup', methods = ['GET','POST']) def signup(): if request.method == 'GET': return render_template('signup.html') elif request.method == 'POST': username = request.form.get('username') password = request.form.get('password') hashed_pass = brcypt.generate_password_hash(password) user = User(username=username, password=hashed_pass) db.session.add(user) db.session.commit() return redirect(url_for('index')) ``` - Login is very similar to Signup only that we *compare* the given *password* to the *hashed* one registered on the DB - `login_user(user)` Is where the *flask_login* come into play. now we have a user *loged* our *session* and it is *tracked*. ```python @app.route('/login', methods = ['GET','POST']) def login(): if request.method == 'GET': return render_template('login.html') elif request.method == 'POST': username = request.form.get('username') password = request.form.get('password') user = User.query.filter(User.username == username).first() if brcypt.check_password_hash(user.password, password): login_user(user) return redirect(url_for('index')) else: return f"failed" ``` - as the user is tracked we can also easyly log him out with `logout_user()`. you can see that we don't have to pass any argument to it. ```python @app.route('/logout') def logout(): logout_user() return redirect(url_for('index')) ``` - now hpw dp we do if we want to limit the acces to a page to only logged mebers? `@login_required`as decorator would be enought ! ```python @app.route('/secret') @login_required def secret(): return "my SercreT" ``` Finishing it with *app.py* is also involved wiht the declaration of the login *session* and *callbacks* if needed ```python from flask import Flask, redirect, url_for from flask_login import LoginManager from flask_bcrypt import Bcrypt from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate ``` - now nothing changed much from the last chapter exept : - `@login_manager.user_loader ` Telling at *flask_login* how to handle an user log in. - `@login_manager.unauthorized_handler` ot define a custom action when unauthorized access is generated. etherwise we get the default message. ```python db = SQLAlchemy() def create_app(): app = Flask(__name__, template_folder='templates', static_folder='static', static_url_path='/') app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///./test.db' app.secret_key = 'SOME KEY' db.init_app(app) login_manager = LoginManager() login_manager.init_app(app) from models import User @login_manager.user_loader def load_user(uid): return User.query.get(uid) @login_manager.unauthorized_handler def unauthorized_callback(): return redirect(url_for('index')) bcrypt = Bcrypt(app) from routes import register_routes register_routes(app, db, bcrypt) Migrate(app, db) return app ``` --- # Blueprints [YouTube](https://www.youtube.com/watch?v=GZ51so48YnM&list=PL7yh-TELLS1EyAye_UMnlsTGKxg8uatkM&index=9) This is more of a restructuration of the whole project. let's first have a look into the file structure. as you can see bluprints tend to repeate themselves and can be used are modular build oject in the future. ```bash key@devbian ~/g/m/flask_to_docker (master)> tree . ├── blueprintapp │ ├── app.py │ ├── blueprints │ │ ├── core │ │ │ ├── __init__.py │ │ │ ├── routes.py │ │ │ └── templates │ │ │ └── core │ │ │ └── index.html │ │ ├── people │ │ │ ├── __init__.py │ │ │ ├── models.py │ │ │ ├── routes.py │ │ │ └── templates │ │ │ └── people │ │ │ ├── create.html │ │ │ └── index.html │ │ └── todos │ │ ├── __init__.py │ │ ├── models.py │ │ ├── routes.py │ │ └── templates │ │ └── todos │ │ ├── create.html │ │ └── index.html │ ├── __init__.py │ └── templates │ └── base.html └── run.py ``` let's have alook how to declare the `core` *Blueprint* by goin to the corresponding *blueprintapp/blueprints/core/routes.py*: - `Blueprint('core', __name__, template_folder='templates')` is wehre we declare the blueprint - The given `template_folder='templates'`corenponds to : *blueprintapp/blueprints/core/templates/* - We also have new decorations now have a llok at `@core.route` instead of the classica @app.route ```python from flask import Flask, render_template, Blueprint core = Blueprint('core', __name__, template_folder='templates') @core.route('/', methods=['GET', 'POST']) def index(): return render_template('core/index.html') ``` now how to inlcude Bluprint to the main app: - we firs timport the blueprint `from blueprintapp.blueprints.core.routes import core` - and then we regiter them with `app.register_blueprint(core, url_prefix='/')` the *url_prefix='/'* is the prefix that is given on the Internet Browser. - so wehn we give [http://localhost:5000/](http://localhost:5000/) we are pointing to *blueprintapp/blueprints/core/templates/core/index.html* ```python from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate db = SQLAlchemy() def create_app(): app = Flask(__name__, template_folder='templates', static_folder='static', static_url_path='/') app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///./test.db' app.secret_key = 'SOME KEY' db.init_app(app) # import and register all blueprints. from blueprintapp.blueprints.core.routes import core from blueprintapp.blueprints.todos.routes import todos from blueprintapp.blueprints.people.routes import people app.register_blueprint(core, url_prefix='/') app.register_blueprint(todos, url_prefix='/todos') app.register_blueprint(people, url_prefix='/people') Migrate(app, db) return app ``` --- # Using Docker to serve Flask