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