Web Development¶
Strona internetowa vs aplikacja internetowa
- strona internetowa
wyłącznie treść
statyczne pliki HTML i CSS
- aplikacja
interakcja
- narzędzia umożliwiające bardziej złożoną komunikację z serwerem:
po stronie serwera np. Python, PHP, Node.js
po stronie klienta np. Javascript, AJAX
Jeśli strona pełni funkcję wyłącznie informacyjną to nie należy jej nazywać aplikacją.
HTTP
Protokół obsługiwany przez przeglądarkę internetową. Określa zasady wymiany informacji pomiędzy serwerem a klientem (przeglądarką internetową). Klient wysyła żądanie do serwera na podstawie którego generowania jest odpowiedź.
HTTPS
Komunikacja pomiędzy klientem a serwerem jest szyfrowana.
URL (Uniform Resource Locator) vs URI (Uniform Resource Identifier)
URI - identyfikuje dany zasób
URL - opisuje gdzie dany zasób się znajduje i jak go pobrać np. http://mojastrona.pl/zdjecie.jpg
URL to URI, ale URI to nie URL.
Główne metody HTTP
GET - pobieranie zasobu
POST - wysyłanie danych (np. formularzy, plików)
PUT - nadpisywanie istniejącego zasobu (lub tworzenie nowego jeżeli nie istnieje)
PATCH - częściowa aktualizacja zasobu
DELETE - usuwanie zasobu
Czym jest framework?
Szkielet służący do budowania aplikacji. Bardzo często wymusza stosowanie dobrych praktyk (np. KISS, DRY).
wyposaża programistę w zestaw gotowych narzędzi i bibliotek
wyznacza strukturę aplikacji
często definiuje domyślną konfigurację, która jest użyteczna dla większości projektów
Czym jest Flask?
Microframework napisany w języku Python. Pozwala na łatwą rozbudowę aplikacji dzięki licznym rozszerzeniom. Oparty m.in. na Jinja2 - silniku szablonów służącemu do renderowania dynamicznych stron internetowych.
SQL
Język zapytań pozwalający na tworzenie, edycję oraz zarządzanie relacyjnymi bazami danych. Dane w bazie przechowywane są w postaci powiązanych ze sobą tabel. Typy relacji pomiędzy tabelami:
jeden-do-wielu
wiele-do-wielu
jeden-do-jednego
ORM (Object-Relational Mapping)
Mapowanie obiektowo-relacyjne pozwala na odwzorowanie tabel bazodanowych wraz z ich relacjami przy użyciu obiektów w aplikacji. Systemy ORM pozwalają m.in. na:
nawiązanie połączenia z bazą
wysyłanie zapytań SQL
zarządzanie sesją, transakcjami bazodanowymi
cache’owanie wyników zapytań
SQL vs ORM
SQL
posts |
||
---|---|---|
id |
INTEGER PRIMARY KEY |
NOT NULL |
title |
VARCHAR (128) |
NOT NULL |
content |
TEXT |
NOT NULL |
author |
VARCHAR (64) |
NOT NULL |
SELECT id, title, content, author FROM posts WHERE id = 5;
ORM
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(128), nullable=False)
content = db.Column(db.Text(), nullable=False)
author = db.Column(db.String(64), nullable=False)
Post.query.get(5)
Tworzenie aplikacji
main.py
from flask import Flask
app = Flask('blog')
@app.route('/')
def hello_world():
return 'Hello World'
Dekorator app.route
pozwala na powiązanie funkcji hello_world
z adresem URL /
.
Funkcja hello_world
zwraca treść, która będzie dostępna pod adresem URL http://localhost:5000
.
Uruchamianie aplikacji
export FLASK_APP=main.py
flask run
Aplikacja dostępna pod adresem: http://127.0.0.1:5000/
Prezentacja danych - pierwszy szablon
templates/index.html
<ul>
{% for post in posts %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
Tagi, będące częścią Jinja2
, pozwalają na wykonywanie kodu w szablonie HTML. Tagi, którymi będziemy się posługiwać podczas kursu:
for
: pętlaif/elif/else
: instrukcje warunkoweextends
: służy do zasygnalizowania silnikowi szablonów, że dany szablon rozszerza inny szablon (np. korzysta z jego kodu HTML)block
: służy do tworzenia bloków, których treść może być nadpisywana w szablonach korzystających z innych szablonów poprzez użycie taguextends
W szablonach HTML można korzystać ze zmiennych, które zostały przekazane do funkcji render_template
np. {{ posts }}.
Pobieranie postów z pliku
main.py
from flask import Flask, render_template
@app.route('/')
def index():
posts = get_all_posts()
return render_template('index.html', posts=posts)
Pobieranie pojedyńczego postu z pliku
main.py
@app.route('/posts/<id_>')
def post_detail(id_):
post = get_post_by_id(id_)
return render_template('post_detail.html', post=post)
Szablon dla pojedyńczego postu
templates/post_detail.html
<div>
<p>Autor: {{ post.author }}</p>
<strong>{{ post.title }}</strong>
<p>{{ post.content }}</p>
</div>
Aktualizacja szablonu zawierającego listę postów
templates/index.html
<ul>
{% for post in posts %}
<li><a href="{{ url_for('post_detail', id_=post.id) }}">{{ post.title }}</a></li>
{% endfor %}
</ul>
Funkcja url_for
pozwala na utworzenie adresu URL na podstawie nazwy funkcji (widoku) oraz jej argumentów.
Reużywalność szablonów
templates/base.html
<html>
<head>
<title>PyPiła 2020</title>
</head>
<body>
<h1>Najlepszy blog w internecie :)</h1>
{% block content %}{% endblock %}
</body>
</html>
templates/index.html
{% extends 'base.html' %}
{% block content %}
<ul>
{% for post in posts %}
<li>
<a href="{{ url_for('post_detail', id_=post.id) }}">{{ post.title }}</a>
</li>
{% endfor %}
</ul>
{% endblock %}
templates/post_detail.html
{% extends 'base.html' %}
{% block content %}
<div>
<p>Autor: {{ post.author }}</p>
<strong>{{ post.title }}</strong>
<p>{{ post.content }}</p>
</div>
<a href="{{ url_for('index') }}">Powrót</a>
{% endblock %}
Dodawanie postów do pliku
main.py
from flask import Flask, redirect, render_template, request, url_for
@app.route('/add-post', methods=('GET', 'POST'))
def add_post():
if request.method == 'POST':
form = request.form
post = save_post_to_file(form['title'], content=form['content'], author=form['author'])
return redirect(url_for('post_detail', id_=post.id))
return render_template('add_post.html')
Obiekt request
reprezentuje żadanie wysłane do serwera HTTP.
Wysyłanie danych z formularza odbywa się przy pomocy metody POST
.
Wspomniany obiekt request
zawiera treść formularza, którą możemy wykorzystać do zapisu postu.
templates/add_post.html
{% extends 'base.html' %}
{% block content %}
<form method="post">
<div>
<label for="title">Tytuł:</label>
<input name="title" id="title" required>
</div>
<div>
<label for="content">Treść:</label>
<textarea name="content" id="content" rows="4" cols="30" required></textarea>
</div>
<div>
<label for="author">Autor:</label>
<input name="author" id="author" required>
</div>
<input type="submit" value="Dodaj">
</form>
<a href="{{ url_for('index') }}">Powrót</a>
{% endblock %}
templates/index.html
{% extends 'base.html' %}
{% block content %}
<a href="{{ url_for('add_post') }}">Dodaj post</a>
<ul>
{% for post in posts %}
<li>
<a href="{{ url_for('post_detail', id_=post.id) }}">{{ post.title }}</a>
</li>
{% endfor %}
</ul>
{% endblock %}
Usuwanie postów z pliku
main.py
@app.route('/delete-post/<id_>', methods=('GET', 'POST'))
def delete_post(id_):
post = get_post_by_id(id_)
if request.method == 'POST':
delete_post_by_id(post.id)
return redirect(url_for('index'))
return render_template('delete_post.html', post=post)
templates/delete_post.html
{% extends 'base.html' %}
{% block content %}
<p>Czy na pewno chcesz usunąć post zatytułowany <strong>{{ post.title }}</strong>?</p>
<form method="post">
<button type="submit">Potwierdź</button>
</form>
<a href="{{ url_for('index') }}">Powrót</a>
{% endblock %}
templates/index.html
{% extends 'base.html' %}
{% block content %}
<a href="{{ url_for('add_post') }}">Dodaj post</a>
<ul>
{% for post in posts %}
<li>
<a href="{{ url_for('post_detail', id_=post.id) }}">{{ post.title }}</a>
<a href="{{ url_for('delete_post', id_=post.id) }}">Usuń</a>
</li>
{% endfor %}
</ul>
{% endblock %}
Pierwszy model
models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(128), nullable=False)
content = db.Column(db.Text(), nullable=False)
author = db.Column(db.String(64), nullable=False)
Integracja bazy danych z obiektem aplikacji
main.py
from flask import Flask
from models import db
app = Flask('blog')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///./blog.db'
db.init_app(app)
Tworzenie tabel
flask shell
from models import db
db.create_all()
Pobieranie postów z bazy danych
main.py
from flask import Flask, render_template
from models import db, Post
@app.route('/')
def index():
posts = Post.query.all()
return render_template('index.html', posts=posts)
Sprawdzenie wyświetlania postów
Brak wyników?
flask shell
from models import db
db.session.execute("INSERT INTO posts VALUES(null, 'Pierwszy post', 'Super interesująca treść', 'Jan Kowalski');")
db.session.commit()
Pobieranie pojedyńczego postu z bazy danych
main.py
@app.route('/posts/<id_>')
def post_detail(id_):
post = Post.query.get(id_)
return render_template('post_detail.html', post=post)
Dodawanie postów do bazy danych
main.py
from flask import Flask, redirect, render_template, request, url_for
@app.route('/add-post', methods=('GET', 'POST'))
def add_post():
if request.method == 'POST':
form = request.form
post = Post(title=form['title'], content=form['content'], author=form['author'])
db.session.add(post)
db.session.commit()
return redirect(url_for('post_detail', id_=post.id))
return render_template('add_post.html')
Usuwanie postów z bazy danych
main.py
@app.route('/delete-post/<id>', methods=('GET', 'POST'))
def delete_post(id):
post = Post.query.get(id_)
if request.method == 'POST':
db.session.delete(post)
db.session.commit()
return redirect(url_for('index'))
return render_template('delete_post.html', post=post)
Instrukcje warunkowe w szablonach
templates/index.html
{% extends 'base.html' %}
{% block content %}
<a href="{{ url_for('add_post') }}">Dodaj post</a>
{% if posts %}
<ul>
{% for post in posts %}
<li>
<a href="{{ url_for('post_detail', id_=post.id) }}">{{ post.title }}</a>
<a href="{{ url_for('delete_post', id_=post.id) }}">Usuń</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>Nie dodałeś jeszcze żadnego posta!</p>
{% endif %}
{% endblock %}
Stylowanie
templates/base.html
<html>
<head>
<title>PyPiła 2020</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<body class="container">
<h1 class="my-3">Najlepszy blog w internecie :)</h1>
{% block content %}{% endblock %}
</body>
</html>
templates/index.html
{% extends 'base.html' %}
{% block content %}
<a class="btn btn-primary btn-lg my-3" href="{{ url_for('add_post') }}">Dodaj post</a>
{% if posts %}
<ul class="list-group">
{% for post in posts %}
<li class="list-group-item">
<a href="{{ url_for('post_detail', id_=post.id) }}">{{ post.title }}</a>
<a class="btn btn-danger float-right" href="{{ url_for('delete_post', id_=post.id) }}">Usuń</a>
</li>
{% endfor %}
</ul>
{% else %}
<p class="alert alert-info">Nie dodałeś jeszcze żadnego posta!</p>
{% endif %}
{% endblock %}
templates/post_detail.html
{% extends 'base.html' %}
{% block content %}
<div class="card p-3 mb-3">
<p>Autor: {{ post.author }}</p>
<strong>{{ post.title }}</strong>
<p>{{ post.content }}</p>
</div>
<a href="{{ url_for('index') }}">Powrót</a>
{% endblock %}
templates/add_post.html
{% extends 'base.html' %}
{% block content %}
<form class="card p-3" method="post">
<div class="form-group">
<label for="title">Tytuł:</label>
<input class="form-control" name="title" id="title" required>
</div>
<div class="form-group">
<label for="content">Treść:</label>
<textarea class="form-control" name="content" id="content" rows="4" cols="30" required></textarea>
</div>
<div>
<label for="author">Autor:</label>
<input class="form-control" name="author" id="author" required>
</div>
<input class="btn btn-primary btn-block mt-3" type="submit" value="Dodaj">
</form>
<a href="{{ url_for('index') }}">Powrót</a>
{% endblock %}
templates/delete_post.html
{% extends 'base.html' %}
{% block content %}
<p>Czy na pewno chcesz usunąć post zatytułowany <strong>{{ post.title }}</strong>?</p>
<form method="post">
<button class="btn btn-primary" type="submit">Potwierdź</button>
</form>
<a href="{{ url_for('index') }}">Powrót</a>
{% endblock %}
Zadanie domowe
Dodaj do aplikacji widok pozwalający na edycję wybranego postu.