You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

453 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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.