mirror of
https://github.com/tiyn/container-critique.git
synced 2025-04-01 23:57:49 +02:00
src: login works
This commit is contained in:
parent
eaf690f832
commit
cd09ab9a12
41
README.md
41
README.md
@ -5,9 +5,15 @@ The blog is intended to be used to review and critique things.
|
||||
|
||||
## Features/To-Dos
|
||||
|
||||
- [ ] Plain text support for blog entries
|
||||
- [ ] HTML files (.html)
|
||||
- [ ] Markdown Files (.md)
|
||||
|
||||
- [ ] Accounts
|
||||
- [x] Login
|
||||
- [x] Logout
|
||||
- [ ] Register
|
||||
- [ ] Review blog entries
|
||||
- [ ] Writing entries
|
||||
- [ ] Editing entries
|
||||
- [ ] Deleting entries
|
||||
- [ ] Infinite-scroll blog page
|
||||
- [ ] Archive page
|
||||
- [ ] Months as headings
|
||||
@ -16,14 +22,16 @@ The blog is intended to be used to review and critique things.
|
||||
- [ ] Standalone article page
|
||||
- [ ] Links to scrolling blog page
|
||||
- [ ] RSS feed
|
||||
- [ ] Navigation
|
||||
- [ ] Header
|
||||
- [ ] Footer
|
||||
- [ ] Switchable CSS
|
||||
- [ ] CSS dark-theme
|
||||
- [ ] CSS light-theme
|
||||
- [ ] Config file
|
||||
- [ ] Docker installation
|
||||
- [ ] Eye candy
|
||||
- [ ] Star rating
|
||||
- [ ] Rich text editor
|
||||
- [x] Navigation
|
||||
- [x] Header
|
||||
- [x] Footer
|
||||
- [x] Switchable CSS
|
||||
- [x] CSS dark-theme
|
||||
- [x] CSS light-theme
|
||||
- [x] Docker installation
|
||||
- [ ] Logo
|
||||
|
||||
## Usage
|
||||
@ -48,12 +56,11 @@ The `config.py` can be found in the `src` folder.
|
||||
|
||||
Set the following volumes with the -v tag.
|
||||
|
||||
| Volume-Name | Container mount | Description |
|
||||
| ------------- | --------------------------- | ------------------------------------------------------------ |
|
||||
| `config-file` | `/blog/src/config.py` | Config file |
|
||||
| `entries` | `/blog/src/templates/entry` | Directory for blog entries |
|
||||
| `css` | `/blog/src/static/css` | (optional) Directory for css files |
|
||||
| `html` | `/blog/src/templates` | (optional) Directory for templates (entry-volume not needed) |
|
||||
| Volume-Name | Container mount | Description |
|
||||
| ------------- | ---------------------- | ---------------------------------- |
|
||||
| `config-file` | `/blog/src/config.py` | Config file |
|
||||
| `css` | `/blog/src/static/css` | (optional) Directory for css files |
|
||||
| `html` | `/blog/src/templates` | (optional) Directory for templates |
|
||||
|
||||
#### Ports
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.6 KiB |
76
src/app.py
76
src/app.py
@ -1,58 +1,24 @@
|
||||
from flask import Flask, flash, make_response, render_template, request, redirect, abort, url_for
|
||||
from flask_login import current_user, login_user, LoginManager, logout_user
|
||||
from flask_wtf import CSRFProtect
|
||||
|
||||
import content as con_gen
|
||||
import config
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, PasswordField, SubmitField, BooleanField
|
||||
from wtforms.validators import DataRequired
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
login = LoginManager(app)
|
||||
login.login_view = 'login'
|
||||
csrf = CSRFProtect()
|
||||
app.secret_key = "123534"
|
||||
csrf.init_app(app)
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
username = StringField("Username", validators=[DataRequired()])
|
||||
password = PasswordField("Password", validators=[DataRequired()])
|
||||
remember_me = BooleanField("Remember Me")
|
||||
submit = SubmitField("Sign In")
|
||||
login = LoginManager(app)
|
||||
login.login_view = "login"
|
||||
|
||||
TITLE = config.TITLE
|
||||
STYLE = config.STYLE
|
||||
DESCRIPTION = config.DESCRIPTION
|
||||
WEBSITE = config.WEBSITE
|
||||
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
||||
class User():
|
||||
|
||||
def __init__(self, username):
|
||||
self.username = username
|
||||
self.id = 1
|
||||
self.is_active = True
|
||||
self.is_authenticated = False
|
||||
self.is_anonymous = False
|
||||
|
||||
def set_password(self, password):
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
def get_id(self):
|
||||
return self.id
|
||||
|
||||
u = User("marten")
|
||||
u.set_password("test")
|
||||
|
||||
class Config(object):
|
||||
SECRET_KEY = "123534"
|
||||
|
||||
app.config.from_object(Config)
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
return render_template("error.html", title=TITLE, errorcode="404", style=STYLE), 404
|
||||
@ -67,7 +33,7 @@ def index():
|
||||
|
||||
@app.route("/archive")
|
||||
@app.route("/archive.html")
|
||||
def blog_archive():
|
||||
def archive():
|
||||
content = con_gen.gen_arch_string()
|
||||
return render_template("archive.html", title=TITLE, content_string=content, style=STYLE)
|
||||
|
||||
@ -91,24 +57,36 @@ def feed():
|
||||
return response
|
||||
|
||||
@login.user_loader
|
||||
def load_user(id):
|
||||
def load_user(ident):
|
||||
## TODO: load user from db by id
|
||||
return id
|
||||
db_user = db.get_by_id(ident)
|
||||
if db_user is not None:
|
||||
return db.db_to_user(*db_user)
|
||||
return None
|
||||
|
||||
from login import LoginForm, User
|
||||
from database import Database
|
||||
|
||||
db = Database()
|
||||
|
||||
@app.route("/login", methods=["GET", "POST"])
|
||||
@app.route("/login.html", methods=["GET", "POST"])
|
||||
def login():
|
||||
#if current_user.is_authenticated:
|
||||
# return redirect("/index")
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for("index"))
|
||||
form = LoginForm()
|
||||
if form.validate_on_submit():
|
||||
user = u
|
||||
#user = form.username.data
|
||||
if user is None or not user.check_password(form.password.data):
|
||||
db_user = db.get_by_name(form.username.data)
|
||||
if db_user is None:
|
||||
flash("Invalid username or password")
|
||||
return redirect(url_for("login"))
|
||||
user = db.db_to_user(*db_user)
|
||||
if not user.check_password(form.password.data):
|
||||
flash("Invalid username or password")
|
||||
return redirect(url_for("login"))
|
||||
login_user(user, remember=form.remember_me.data)
|
||||
return redirect(url_for("index"))
|
||||
return render_template("login.html", title="Sign In", form=form, style=STYLE)
|
||||
return render_template("login.html", title=TITLE, form=form, style=STYLE)
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
|
@ -1,5 +1,3 @@
|
||||
ENTRY_DIR = "templates/entry"
|
||||
|
||||
def gen_arch_string():
|
||||
"""
|
||||
Creates and returns a archive string of every file in ENTRY_DIR.
|
||||
|
BIN
src/data.db
Normal file
BIN
src/data.db
Normal file
Binary file not shown.
66
src/database.py
Normal file
66
src/database.py
Normal file
@ -0,0 +1,66 @@
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
from login import User
|
||||
|
||||
|
||||
class Database:
|
||||
|
||||
def __init__(self):
|
||||
self.TABLE_FILE = 'USERS'
|
||||
self.DB_DIR = os.path.dirname(".")
|
||||
self.setup_db()
|
||||
|
||||
def connect(self):
|
||||
"""Connect to an existing database instance based on the object
|
||||
attributes.
|
||||
"""
|
||||
path = os.path.join(self.DB_DIR, "data.db")
|
||||
return sqlite3.connect(path)
|
||||
|
||||
def setup_db(self):
|
||||
"""Creates a database with tables."""
|
||||
db = self.connect()
|
||||
crs = db.cursor()
|
||||
query = "CREATE TABLE IF NOT EXISTS " + self.TABLE_FILE + \
|
||||
"(id INTEGER PRIMARY KEY AUTOINCREMENT," + \
|
||||
"name CHAR(32) NOT NULL UNIQUE," + \
|
||||
"password CHAR(32) NOT NULL)"
|
||||
crs.execute(query)
|
||||
|
||||
def insert_user(self, name, password):
|
||||
"""Insert a new user into the database.
|
||||
"""
|
||||
if self.check_name(name):
|
||||
db = self.connect()
|
||||
crs = db.cursor()
|
||||
query = "INSERT INTO " + self.TABLE_FILE + "(`name`,`password`)" + \
|
||||
"VALUES (?, ?) ON CONFLICT DO NOTHING"
|
||||
crs.execute(query, (name, password))
|
||||
db.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_name(self, name):
|
||||
if self.get_by_name(name) is None:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_by_id(self, ident):
|
||||
db = self.connect()
|
||||
crs = db.cursor()
|
||||
query = "SELECT * FROM " + self.TABLE_FILE + " WHERE id = ?"
|
||||
crs.execute(query, (ident, ))
|
||||
return crs.fetchone()
|
||||
|
||||
def get_by_name(self, name):
|
||||
db = self.connect()
|
||||
crs = db.cursor()
|
||||
query = "SELECT * FROM " + self.TABLE_FILE + " WHERE name = ?"
|
||||
crs.execute(query, (name, ))
|
||||
return crs.fetchone()
|
||||
|
||||
def db_to_user(self, ident, name, pass_hash):
|
||||
user = User(name, pass_hash)
|
||||
user.set_id(ident)
|
||||
return user
|
34
src/login.py
Normal file
34
src/login.py
Normal file
@ -0,0 +1,34 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, PasswordField, SubmitField, BooleanField
|
||||
from wtforms.validators import DataRequired
|
||||
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
||||
|
||||
class User():
|
||||
|
||||
def __init__(self, name, pass_hash=None):
|
||||
self.name = name
|
||||
self.id = 0
|
||||
self.is_active = True
|
||||
self.is_authenticated = True
|
||||
self.is_anonymous = False
|
||||
self.pass_hash = pass_hash
|
||||
|
||||
def set_password(self, password):
|
||||
self.pass_hash = generate_password_hash(password)
|
||||
|
||||
def set_id(self, ident):
|
||||
self.id = ident
|
||||
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.pass_hash, password)
|
||||
|
||||
def get_id(self):
|
||||
return self.id
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
username = StringField("Username", validators=[DataRequired()])
|
||||
password = PasswordField("Password", validators=[DataRequired()])
|
||||
remember_me = BooleanField("Remember Me")
|
||||
submit = SubmitField("Sign In")
|
@ -1,4 +1,5 @@
|
||||
Flask==2.1.2
|
||||
flask_login==0.6.2
|
||||
flask_wtf==1.0.1
|
||||
WTForms==3.0.1
|
||||
Flask_Login==0.6.2
|
||||
Flask_WTF==0.14.3
|
||||
Werkzeug==2.1.2
|
||||
WTForms==2.2.1
|
||||
|
@ -1,18 +1,26 @@
|
||||
{% extends "template.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Sign In</h1>
|
||||
<form action="" method="post" novalidate>
|
||||
{{ form.hidden_tag() }}
|
||||
<p>
|
||||
{{ form.username.label }}<br>
|
||||
{{ form.username(size=32) }}
|
||||
</p>
|
||||
<p>
|
||||
{{ form.password.label }}<br>
|
||||
{{ form.password(size=32) }}
|
||||
</p>
|
||||
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
|
||||
<p>{{ form.submit() }}</p>
|
||||
</form>
|
||||
|
||||
<div class="container">
|
||||
<div class="logging">
|
||||
<h1>Sign In</h1>
|
||||
<form action="" method="post" novalidate>
|
||||
{{ form.hidden_tag() }}
|
||||
<p>
|
||||
{{ form.username.label }}<br>
|
||||
{{ form.username(size=32) }}
|
||||
</p>
|
||||
<p>
|
||||
{{ form.password.label }}<br>
|
||||
{{ form.password(size=32) }}
|
||||
</p>
|
||||
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
|
||||
<p>{{ form.submit() }}</p>
|
||||
{% for mesg in get_flashed_messages() %}
|
||||
<p>{{ mesg }}</p>
|
||||
{% endfor %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -13,13 +13,13 @@
|
||||
<input type="checkbox" id="main-menu-check">
|
||||
<label for="main-menu-check" class="show-menu">☰</label>
|
||||
<div class="main-menu">
|
||||
<a href="/">Blog</a>
|
||||
<a href="/archive">Archive</a>
|
||||
<a href="{{ url_for('index') }}">Blog</a>
|
||||
<a href="{{ url_for('archive') }}">Archive</a>
|
||||
<label for="main-menu-check" class="hide-menu">X</label>
|
||||
{% if current_user.is_anonymous %}
|
||||
<a href="/login">Login</a>
|
||||
<a href="{{ url_for('login') }}">Login</a>
|
||||
{% else %}
|
||||
<a href="/logout">Logout</a>
|
||||
<a href="{{ url_for('logout') }}">Logout</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user