From 61aa869b93d303eb4fb0f3657951639e09f683e5 Mon Sep 17 00:00:00 2001
From: tiyn
Date: Sat, 30 Jul 2022 23:56:17 +0200
Subject: [PATCH] src: logging and writing complete
---
.gitignore | 2 +
Dockerfile | 2 +
README.md | 24 ++++-----
rebuild.sh | 1 +
src/app.py | 88 +++++++++++++++++++++++---------
src/config.py | 3 ++
src/content.py | 91 +++++++++++++++++++++++++++++----
src/data.db | Bin 16384 -> 0 bytes
src/data/.gitkeep | 0
src/database.py | 98 ++++++++++++++++++++++++++++++------
src/forms.py | 26 ++++++++++
src/login.py | 34 -------------
src/requirements.txt | 3 +-
src/templates/login.html | 1 -
src/templates/register.html | 38 ++++++++++++++
src/templates/template.html | 15 +++---
src/templates/write.html | 45 +++++++++++++++++
17 files changed, 369 insertions(+), 102 deletions(-)
create mode 100644 .gitignore
delete mode 100644 src/data.db
create mode 100644 src/data/.gitkeep
create mode 100644 src/forms.py
delete mode 100644 src/login.py
create mode 100644 src/templates/register.html
create mode 100644 src/templates/write.html
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4119916
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+data
+data.db
diff --git a/Dockerfile b/Dockerfile
index 3f2c87f..befc2f4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,6 +6,8 @@ COPY src /blog
WORKDIR /blog
+VOLUME /blog/data
+
RUN pip3 install -r requirements.txt
VOLUME /blog/templates/entry
diff --git a/README.md b/README.md
index 482789c..b41a5a3 100644
--- a/README.md
+++ b/README.md
@@ -6,21 +6,21 @@ The blog is intended to be used to review and critique things.
## Features/To-Dos
-- [ ] Accounts
+- [x] Accounts
- [x] Login
- [x] Logout
- - [ ] Register
+ - [x] Register
- [ ] Review blog entries
- - [ ] Writing entries
+ - [x] Writing entries
- [ ] Editing entries
- [ ] Deleting entries
- [ ] Infinite-scroll blog page
-- [ ] Archive page
- - [ ] Months as headings
- - [ ] Links to scrolling blog page
- - [ ] Links to standalone article
-- [ ] Standalone article page
- - [ ] Links to scrolling blog page
+- [x] Archive page
+ - [x] Months as headings
+ - [x] Links to scrolling blog page
+ - [x] Links to standalone article
+- [x] Standalone article page
+ - [x] Links to scrolling blog page
- [ ] RSS feed
- [ ] Eye candy
- [ ] Star rating
@@ -58,9 +58,9 @@ Set the following volumes with the -v tag.
| 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 |
+| `config-file` | `/blog/config.py` | Config file |
+| `data` | `/blog/data` | Directory for data |
+| `css` | `/blog/static/css` | (optional) Directory for css files |
#### Ports
diff --git a/rebuild.sh b/rebuild.sh
index 816ac62..24c20a1 100755
--- a/rebuild.sh
+++ b/rebuild.sh
@@ -6,4 +6,5 @@ docker run --name container-critique \
--restart unless-stopped \
-p "5000:5000" \
-e FLASK_ENV=development \
+ -v data:/blog/data
-d tiyn/container-critique
diff --git a/src/app.py b/src/app.py
index ab3ad17..2c3bf94 100644
--- a/src/app.py
+++ b/src/app.py
@@ -1,14 +1,21 @@
-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 import Flask, flash, make_response, render_template, redirect, \
+ abort, url_for
+from flask_login import current_user, login_user, LoginManager, logout_user, \
+ login_required
from flask_wtf import CSRFProtect
+import os
-import content as con_gen
import config
+import content as con_gen
+from database import Database, User
+from forms import LoginForm, RegisterForm, WriteForm
app = Flask(__name__)
csrf = CSRFProtect()
-app.secret_key = "123534"
+db = Database()
+
+app.secret_key = os.urandom(32)
csrf.init_app(app)
login = LoginManager(app)
@@ -18,6 +25,8 @@ TITLE = config.TITLE
STYLE = config.STYLE
DESCRIPTION = config.DESCRIPTION
WEBSITE = config.WEBSITE
+REGISTER = config.REGISTER
+
@app.errorhandler(404)
def page_not_found(e):
@@ -38,9 +47,9 @@ def archive():
return render_template("archive.html", title=TITLE, content_string=content, style=STYLE)
-@app.route("/entry/")
-def entry(path):
- content = con_gen.gen_stand_string(path)
+@app.route("/entry/")
+def entry(ident):
+ content = con_gen.gen_stand_string(ident)
if content != "":
return render_template("standalone.html", title=TITLE, content_string=content, style=STYLE)
abort(404)
@@ -56,18 +65,14 @@ def feed():
response.headers["Content-Type"] = "application/rss+xml"
return response
+
@login.user_loader
def load_user(ident):
- ## TODO: load user from db by id
- db_user = db.get_by_id(ident)
+ db_user = db.get_user_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"])
@@ -76,22 +81,59 @@ def login():
return redirect(url_for("index"))
form = LoginForm()
if form.validate_on_submit():
- 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"))
+ db_user = db.get_user_by_name(form.username.data)
+ if db_user is not None:
+ user = db.db_to_user(*db_user)
+ if user.check_password(form.password.data):
+ login_user(user)
+ return redirect(url_for("index"))
+ flash("Invalid username or password.")
+ return redirect(url_for("login"))
return render_template("login.html", title=TITLE, form=form, style=STYLE)
+
@app.route('/logout')
+@app.route('/logout.html')
def logout():
logout_user()
return redirect(url_for('index'))
+
+@app.route("/register", methods=["GET", "POST"])
+@app.route("/register.html", methods=["GET", "POST"])
+def register():
+ if current_user.is_authenticated or not REGISTER:
+ return redirect(url_for("index"))
+ form = RegisterForm()
+ if form.validate_on_submit():
+ if not REGISTER:
+ return redirect(url_for("index"))
+ db_user = db.get_user_by_name(form.username.data)
+ if db_user is None:
+ user = User(form.username.data)
+ user.set_password(form.password.data)
+ ident = db.insert_user(user)
+ if ident is not None:
+ user.set_id(ident)
+ login_user(user)
+ return redirect(url_for("index"))
+ flash("An error occured during registration.")
+ return redirect(url_for("register"))
+ return render_template("register.html", title=TITLE, form=form, style=STYLE)
+
+
+@app.route("/write", methods=["GET", "POST"])
+@app.route("/write.html", methods=["GET", "POST"])
+@login_required
+def write():
+ if not current_user.is_authenticated:
+ return redirect(url_for("index"))
+ form = WriteForm()
+ if form.validate_on_submit():
+ db.insert_entry(form.name.data, form.date.data, form.text.data, form.rating.data, current_user.id)
+ return redirect(url_for("index"))
+ return render_template("write.html", title=TITLE, form=form, style=STYLE)
+
+
if __name__ == "__main__":
app.run(host="0.0.0.0")
diff --git a/src/config.py b/src/config.py
index b21bbab..3821a0c 100644
--- a/src/config.py
+++ b/src/config.py
@@ -9,3 +9,6 @@ WEBSITE = "localhost:5000"
# Theme for the blog: dark, light
STYLE = "dark"
+
+# Allow new registrations
+REGISTER = True
diff --git a/src/content.py b/src/content.py
index 458c6d2..4842c5a 100644
--- a/src/content.py
+++ b/src/content.py
@@ -1,3 +1,8 @@
+from database import Database
+
+db = Database()
+
+
def gen_arch_string():
"""
Creates and returns a archive string of every file in ENTRY_DIR.
@@ -5,32 +10,100 @@ def gen_arch_string():
Returns:
string: html-formatted archive-string
"""
- return ""
+ content_string = ""
+ last_year = ""
+ entries = db.get_entries()
+ if entries is None:
+ return ""
+ entries.sort(key=lambda y: y[2])
+ for entry in entries:
+ ident = entry[0]
+ title = entry[1]
+ year = entry[2]
+ rating = entry[4]
+ if year != last_year:
+ if last_year != "":
+ content_string += "\n"
+ content_string += "" + year + "
\n"
+ content_string += "\n"
+ last_year = year
+ content_string += "- "
+ content_string += "[link - standalone] "
+ content_string += title + \
+ " (" + str(year) + ") - " + str(rating) + "/100
"
+ content_string += " \n"
+
+ return content_string
def gen_index_string():
"""
- Create and returns a string including every file in the ENTRY_DIR as an index.
+ Create and returns a string including every file in the database as an index.
Returns:
string: html-formatted index string
"""
- return ""
+ content_string = ""
+ entries = db.get_entries()
+ if entries is None:
+ return ""
+ entries.reverse()
+ for entry in entries:
+ ident = entry[0]
+ title = entry[1]
+ year = entry[2]
+ text = entry[3]
+ rating = entry[4]
+ username = db.get_user_by_id(entry[5])[1]
+ reviewed = entry[6]
+ content_string += "\n"
+ content_string += "
" + title + " (" + year + ") - " + \
+ str(rating) + "/100
\n"
+ content_string += "[
" + "standalone" + "]
\n"
+ content_string += text
+ content_string += "
"
+ content_string += "
" + \
+ str(reviewed) + " by " + username + ""
+ content_string += "
"
+ return content_string
-def gen_stand_string(path_ex):
+def gen_stand_string(ident):
"""
- Creates a html-string for a file.
- If the file is markdown it will convert it.
- This functions ensures upscaling for future formats.
+ Creates a html-string for an entry.
Parameters:
- path_ex: path to a file.
+ ident: ident of an entry.
Returns:
string: html-formatted string string equivalent to the file
"""
- return ""
+ entry = db.get_entry_by_id(ident)
+ content_string = ""
+ if entry is not None:
+ ident = entry[0]
+ title = entry[1]
+ year = entry[2]
+ text = entry[3]
+ rating = entry[4]
+ username = db.get_user_by_id(entry[5])[1]
+ reviewed = entry[6]
+ content_string += "" + title + \
+ " (" + year + ") - " + str(rating) + "/100
\n"
+ content_string += "["
+ content_string += "" + "link" + ""
+ content_string += "]
\n"
+ content_string += "" + \
+ str(reviewed) + " by " + username + ""
+ content_string += "
\n"
+ content_string += text
+ content_string += "
"
+ return content_string
def get_rss_string():
diff --git a/src/data.db b/src/data.db
deleted file mode 100644
index ae25c7d0ea7d641b7a2f97ae2d5144fad2403457..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 16384
zcmeI(&u-H&90zcF8DpI^!Y%CP!w#)hp){$(IthtvvY;YOw=_BQRJn-*)z-hs1lK+Q
zkH))j8RCeWu>(aM;V^xFvg0_hE&FqGtPHM#NvT*qUrlAnT=JYyO5QL=2szMsSLcVI
z`ewWS@a}Kr0eLxkacKS^#t8*pPcqgGWARo*>@w>1{AkGDiy`xqxZe$R`<@8n=4NPi@y?IxuG3&)KW1SP
z1S|==SBYpYWKn#Yud+W|G#Wd5)@zHB$t+W!e@$54mh-AUs%9{1RfVCU_cz#wI#crt
z(Fp+o2tWV=5P$##AOHafKmY;|fWZF{SkfotXg!?DRjFo+@pYCvXT?Y9aPQ3VY#ql_
z?yZ((|9TO4ANs179H*XgQ%5YUg9s>e$>n
z&9hEsXStVg>1+=9srjAggn$49AOHafKmY;|fB*y_009U<;86+GjHBB9SBCrZ|2tyd
kJ*ov_F%WOX+mPoaQ^c>n+a
diff --git a/src/data/.gitkeep b/src/data/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/database.py b/src/database.py
index 6ef3a3a..cded5fa 100644
--- a/src/database.py
+++ b/src/database.py
@@ -1,14 +1,37 @@
+from datetime import date as dt
import os
import sqlite3
+from werkzeug.security import generate_password_hash, check_password_hash
-from login import User
+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 Database:
def __init__(self):
- self.TABLE_FILE = 'USERS'
- self.DB_DIR = os.path.dirname(".")
+ self.USER_TABLE_FILE = 'USERS'
+ self.ENTRY_TABLE_FILE = 'ENTRIES'
+ self.DB_DIR = os.path.dirname("./data/")
self.setup_db()
def connect(self):
@@ -22,41 +45,78 @@ class Database:
"""Creates a database with tables."""
db = self.connect()
crs = db.cursor()
- query = "CREATE TABLE IF NOT EXISTS " + self.TABLE_FILE + \
+ query = "CREATE TABLE IF NOT EXISTS " + self.USER_TABLE_FILE + \
"(id INTEGER PRIMARY KEY AUTOINCREMENT," + \
"name CHAR(32) NOT NULL UNIQUE," + \
"password CHAR(32) NOT NULL)"
crs.execute(query)
+ query = "CREATE TABLE IF NOT EXISTS " + self.ENTRY_TABLE_FILE + \
+ "(id INTEGER PRIMARY KEY AUTOINCREMENT," + \
+ "name CHAR(64) NOT NULL," + \
+ "date CHAR(4) NOT NULL," + \
+ "text TEXT NOT NULL," + \
+ "rating INTEGER NOT NULL," +\
+ "user_id INTEGER," +\
+ "reviewed CHAR(10) NOT NULL," +\
+ "FOREIGN KEY(user_id) REFERENCES " + self.USER_TABLE_FILE + "(id))"
+ crs.execute(query)
+ db.commit()
- def insert_user(self, name, password):
+ def insert_user(self, user):
"""Insert a new user into the database.
"""
- if self.check_name(name):
+ if self.check_user_name(user.name) and user.pass_hash is not None:
db = self.connect()
crs = db.cursor()
- query = "INSERT INTO " + self.TABLE_FILE + "(`name`,`password`)" + \
+ query = "INSERT INTO " + self.USER_TABLE_FILE + "(`name`,`password`)" + \
"VALUES (?, ?) ON CONFLICT DO NOTHING"
- crs.execute(query, (name, password))
+ crs.execute(query, (user.name, user.pass_hash))
db.commit()
- return True
- return False
+ return crs.lastrowid
+ return None
- def check_name(self, name):
- if self.get_by_name(name) is None:
+ def insert_entry(self, name, date, text, rating, user_id=None):
+ """Insert a new user into the database.
+ """
+ db = self.connect()
+ crs = db.cursor()
+ reviewed = dt.today().strftime('%Y-%m-%d')
+ query = "INSERT INTO " + self.ENTRY_TABLE_FILE + "(`name`,`date`, `text`, `rating`, `user_id`, `reviewed`)" + \
+ "VALUES (?, ?, ?, ?, ?, ?)"
+ crs.execute(query, (name, date, text, rating, user_id, reviewed))
+ db.commit()
+ return crs.lastrowid
+
+ def get_entries(self):
+ db = self.connect()
+ crs = db.cursor()
+ query = "SELECT * FROM " + self.ENTRY_TABLE_FILE
+ crs.execute(query)
+ return crs.fetchall()
+
+ def check_user_name(self, name):
+ if self.get_user_by_name(name) is None:
return True
return False
- def get_by_id(self, ident):
+ def get_entry_by_id(self, ident):
+ db = self.connect()
+ crs = db.cursor()
+ query = "SELECT * FROM " + self.ENTRY_TABLE_FILE + " WHERE id = ?"
+ crs.execute(query, (ident, ))
+ return crs.fetchone()
+
+ def get_user_by_id(self, ident):
db = self.connect()
crs = db.cursor()
- query = "SELECT * FROM " + self.TABLE_FILE + " WHERE id = ?"
+ query = "SELECT * FROM " + self.USER_TABLE_FILE + " WHERE id = ?"
crs.execute(query, (ident, ))
return crs.fetchone()
- def get_by_name(self, name):
+ def get_user_by_name(self, name):
db = self.connect()
crs = db.cursor()
- query = "SELECT * FROM " + self.TABLE_FILE + " WHERE name = ?"
+ query = "SELECT * FROM " + self.USER_TABLE_FILE + " WHERE name = ?"
crs.execute(query, (name, ))
return crs.fetchone()
@@ -64,3 +124,9 @@ class Database:
user = User(name, pass_hash)
user.set_id(ident)
return user
+
+
+#db = Database()
+#db.insert_entry("name", "2020", "text", 50, 1)
+#res = db.get_entries()
+#print(res)
diff --git a/src/forms.py b/src/forms.py
new file mode 100644
index 0000000..d551894
--- /dev/null
+++ b/src/forms.py
@@ -0,0 +1,26 @@
+from datetime import date
+from flask_wtf import FlaskForm
+from wtforms import StringField, PasswordField, SubmitField, TextAreaField
+from wtforms.fields.html5 import IntegerField
+from wtforms.validators import DataRequired, EqualTo, InputRequired, NumberRange
+
+
+class LoginForm(FlaskForm):
+ username = StringField("Username", validators=[DataRequired()])
+ password = PasswordField("Password", validators=[DataRequired()])
+ submit = SubmitField("Sign In")
+
+
+class RegisterForm(FlaskForm):
+ username = StringField("Username", validators=[DataRequired()])
+ password = PasswordField("Password", validators=[DataRequired()])
+ password2 = PasswordField(
+ "Repeat Password", validators=[DataRequired(), EqualTo("password")])
+ submit = SubmitField("Register")
+
+class WriteForm(FlaskForm):
+ name = StringField("Name", validators=[DataRequired()])
+ date = IntegerField("Release Year", default=date.today().year, validators=[DataRequired(), NumberRange(min=0, max=date.today().year, message="Year has to be valid.")])
+ text = TextAreaField("Text", validators=[DataRequired()])
+ rating = IntegerField("Rating", default=50, validators=[InputRequired(), NumberRange(min=0, max=100, message="Number has to be between 0 and 100.")])
+ submit = SubmitField("Publish")
diff --git a/src/login.py b/src/login.py
deleted file mode 100644
index c7220c5..0000000
--- a/src/login.py
+++ /dev/null
@@ -1,34 +0,0 @@
-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")
diff --git a/src/requirements.txt b/src/requirements.txt
index 120c115..b311835 100644
--- a/src/requirements.txt
+++ b/src/requirements.txt
@@ -1,5 +1,6 @@
Flask==2.1.2
Flask_Login==0.6.2
Flask_WTF==0.14.3
-Werkzeug==2.1.2
+Werkzeug==2.0.0
WTForms==2.2.1
+jinja2==3.0.3
diff --git a/src/templates/login.html b/src/templates/login.html
index 0c1f14c..5358fc6 100644
--- a/src/templates/login.html
+++ b/src/templates/login.html
@@ -15,7 +15,6 @@
{{ form.password.label }}
{{ form.password(size=32) }}
- {{ form.remember_me() }} {{ form.remember_me.label }}
{{ form.submit() }}
{% for mesg in get_flashed_messages() %}
{{ mesg }}
diff --git a/src/templates/register.html b/src/templates/register.html
new file mode 100644
index 0000000..e864fbe
--- /dev/null
+++ b/src/templates/register.html
@@ -0,0 +1,38 @@
+{% extends "template.html" %}
+
+{% block content %}
+
+
+{% endblock %}
diff --git a/src/templates/template.html b/src/templates/template.html
index d900ed4..e32cb37 100644
--- a/src/templates/template.html
+++ b/src/templates/template.html
@@ -16,11 +16,6 @@
Blog
Archive
- {% if current_user.is_anonymous %}
- Login
- {% else %}
- Logout
- {% endif %}
@@ -30,7 +25,15 @@