|
|
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.
|
|
|
|