|
|
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}'
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
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
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
## Methodes
|
|
|
This is the way to allow or block some [Methodes](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"
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
You have surely seen some Parametes embedde 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)
|
|
|
## Indicate 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.
|