5 changed files with 297 additions and 4 deletions
-
13app/__init__.py
-
2app/app.py
-
95app/auth.py
-
57app/db.py
-
134app/static/style.css
@ -0,0 +1,95 @@ |
|||
import functools |
|||
|
|||
from flask import ( |
|||
Blueprint, flash, g, redirect, render_template, request, session, url_for |
|||
) |
|||
from werkzeug.security import check_password_hash, generate_password_hash |
|||
|
|||
# from db import get_db error |
|||
|
|||
bp = Blueprint('auth', __name__, url_prefix='/auth') |
|||
|
|||
|
|||
@bp.route('/register', methods=('GET', 'POST')) |
|||
def register(): |
|||
if request.method == 'POST': |
|||
username = request.form['username'] |
|||
password = request.form['password'] |
|||
db = get_db() |
|||
error = None |
|||
|
|||
if not username: |
|||
error = 'Username is required.' |
|||
elif not password: |
|||
error = 'Password is required.' |
|||
|
|||
if error is None: |
|||
try: |
|||
db.execute( |
|||
"INSERT INTO user (username, password) VALUES (?, ?)", |
|||
(username, generate_password_hash(password)), |
|||
) |
|||
db.commit() |
|||
except db.IntegrityError: |
|||
error = f"User {username} is already registered." |
|||
else: |
|||
return redirect(url_for("auth.login")) |
|||
|
|||
flash(error) |
|||
|
|||
return render_template('auth/register.html') |
|||
|
|||
|
|||
@bp.route('/login', methods=('GET', 'POST')) |
|||
def login(): |
|||
if request.method == 'POST': |
|||
username = request.form['username'] |
|||
password = request.form['password'] |
|||
db = get_db() |
|||
error = None |
|||
user = db.execute( |
|||
'SELECT * FROM user WHERE username = ?', (username,) |
|||
).fetchone() |
|||
|
|||
if user is None: |
|||
error = 'Incorrect username.' |
|||
elif not check_password_hash(user['password'], password): |
|||
error = 'Incorrect password.' |
|||
|
|||
if error is None: |
|||
session.clear() |
|||
session['user_id'] = user['id'] |
|||
return redirect(url_for('index')) |
|||
|
|||
flash(error) |
|||
|
|||
return render_template('auth/login.html') |
|||
|
|||
|
|||
@bp.before_app_request |
|||
def load_logged_in_user(): |
|||
user_id = session.get('user_id') |
|||
|
|||
if user_id is None: |
|||
g.user = None |
|||
else: |
|||
g.user = get_db().execute( |
|||
'SELECT * FROM user WHERE id = ?', (user_id,) |
|||
).fetchone() |
|||
|
|||
|
|||
@bp.route('/logout') |
|||
def logout(): |
|||
session.clear() |
|||
return redirect(url_for('index')) |
|||
|
|||
|
|||
def login_required(view): |
|||
@functools.wraps(view) |
|||
def wrapped_view(**kwargs): |
|||
if g.user is None: |
|||
return redirect(url_for('auth.login')) |
|||
|
|||
return view(**kwargs) |
|||
|
|||
return wrapped_view |
@ -0,0 +1,57 @@ |
|||
import sqlite3 ## tu rabim nekaj drugega |
|||
|
|||
import click |
|||
from flask import current_app, g |
|||
from flask.cli import with_appcontext |
|||
|
|||
|
|||
def get_db(): |
|||
if 'db' not in g: |
|||
g.db = sqlite3.connect( |
|||
current_app.config['DATABASE'], |
|||
detect_types=sqlite3.PARSE_DECLTYPES |
|||
) |
|||
g.db.row_factory = sqlite3.Row |
|||
|
|||
return g.db |
|||
|
|||
|
|||
def close_db(e=None): |
|||
db = g.pop('db', None) |
|||
|
|||
if db is not None: |
|||
db.close() |
|||
|
|||
|
|||
""" |
|||
open_resource() opens a file relative to the flaskr package, which is useful since you won’t necessarily know where that location is when deploying the application later. get_db returns a database connection, which is used to execute the commands read from the file. |
|||
|
|||
click.command() defines a command line command called init-db that calls the init_db function and shows a success message to the user. You can read Command Line Interface to learn more about writing commands. |
|||
""" |
|||
|
|||
|
|||
def init_db(): |
|||
db = get_db() |
|||
|
|||
with current_app.open_resource('schema.sql') as f: |
|||
db.executescript(f.read().decode('utf8')) |
|||
|
|||
|
|||
@click.command('init-db') |
|||
@with_appcontext |
|||
def init_db_command(): |
|||
"""Clear the existing data and create new tables.""" |
|||
init_db() |
|||
click.echo('Initialized the database.') |
|||
|
|||
def init_app(app): |
|||
app.teardown_appcontext(close_db) |
|||
app.cli.add_command(init_db_command) |
|||
|
|||
|
|||
""" |
|||
app.teardown_appcontext() tells Flask to call that function when cleaning up after returning the response. |
|||
|
|||
app.cli.add_command() adds a new command that can be called with the flask command. |
|||
""" |
|||
|
@ -0,0 +1,134 @@ |
|||
html { |
|||
font-family: sans-serif; |
|||
background: #eee; |
|||
padding: 1rem; |
|||
} |
|||
|
|||
body { |
|||
max-width: 960px; |
|||
margin: 0 auto; |
|||
background: white; |
|||
} |
|||
|
|||
h1, h2, h3, h4, h5, h6 { |
|||
font-family: serif; |
|||
color: #377ba8; |
|||
margin: 1rem 0; |
|||
} |
|||
|
|||
a { |
|||
color: #377ba8; |
|||
} |
|||
|
|||
hr { |
|||
border: none; |
|||
border-top: 1px solid lightgray; |
|||
} |
|||
|
|||
nav { |
|||
background: lightgray; |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 0 0.5rem; |
|||
} |
|||
|
|||
nav h1 { |
|||
flex: auto; |
|||
margin: 0; |
|||
} |
|||
|
|||
nav h1 a { |
|||
text-decoration: none; |
|||
padding: 0.25rem 0.5rem; |
|||
} |
|||
|
|||
nav ul { |
|||
display: flex; |
|||
list-style: none; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
nav ul li a, nav ul li span, header .action { |
|||
display: block; |
|||
padding: 0.5rem; |
|||
} |
|||
|
|||
.content { |
|||
padding: 0 1rem 1rem; |
|||
} |
|||
|
|||
.content > header { |
|||
border-bottom: 1px solid lightgray; |
|||
display: flex; |
|||
align-items: flex-end; |
|||
} |
|||
|
|||
.content > header h1 { |
|||
flex: auto; |
|||
margin: 1rem 0 0.25rem 0; |
|||
} |
|||
|
|||
.flash { |
|||
margin: 1em 0; |
|||
padding: 1em; |
|||
background: #cae6f6; |
|||
border: 1px solid #377ba8; |
|||
} |
|||
|
|||
.post > header { |
|||
display: flex; |
|||
align-items: flex-end; |
|||
font-size: 0.85em; |
|||
} |
|||
|
|||
.post > header > div:first-of-type { |
|||
flex: auto; |
|||
} |
|||
|
|||
.post > header h1 { |
|||
font-size: 1.5em; |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.post .about { |
|||
color: slategray; |
|||
font-style: italic; |
|||
} |
|||
|
|||
.post .body { |
|||
white-space: pre-line; |
|||
} |
|||
|
|||
.content:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.content form { |
|||
margin: 1em 0; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.content label { |
|||
font-weight: bold; |
|||
margin-bottom: 0.5em; |
|||
} |
|||
|
|||
.content input, .content textarea { |
|||
margin-bottom: 1em; |
|||
} |
|||
|
|||
.content textarea { |
|||
min-height: 12em; |
|||
resize: vertical; |
|||
} |
|||
|
|||
input.danger { |
|||
color: #cc2f2e; |
|||
} |
|||
|
|||
input[type=submit] { |
|||
align-self: start; |
|||
min-width: 10em; |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue