1.0.0: Edit functionality added

This commit is contained in:
2026-05-10 02:38:19 +02:00
parent 39d34bf51c
commit 4905e662f0
23 changed files with 1094 additions and 740 deletions

View File

@@ -1,19 +1,17 @@
from flask import Flask, flash, render_template, redirect, abort, url_for, \
request
from flask_ckeditor import CKEditor
from flask_login import current_user, login_user, LoginManager, logout_user, \
login_required
from flask_wtf import CSRFProtect
import os
from flask import (Flask, abort, flash, redirect, render_template, request, url_for)
from flask_ckeditor import CKEditor
from flask_login import (LoginManager, current_user, login_required, login_user, logout_user)
from flask_wtf import CSRFProtect
from werkzeug.exceptions import HTTPException
import config
from content import rating_to_star
from database import Database
from forms import LoginForm, RegisterForm, WriteForm, SearchForm
from forms import LoginForm, RegisterForm, SearchForm, WriteForm
from search import create_search_index, ft_search
app = Flask(__name__)
csrf = CSRFProtect()
db = Database()
@@ -28,7 +26,7 @@ login.login_view = "login"
@login.user_loader
def load_user(ident):
"""
"""
Returns a user by id.
Parameters:
@@ -37,68 +35,70 @@ def load_user(ident):
Returns:
User: user that matches the id, None if none matches the id
"""
user = db.get_user_by_id(ident)
if user is not None:
return user
return None
user = db.get_user_by_id(ident)
if user is not None:
return user
return None
@app.context_processor
def inject_title():
"""
"""
Injects variables to the jinja2 templates.
Returns:
dict: dictionary of variables to inject.
"""
return dict(title=config.TITLE, style=config.STYLE,
description=config.DESCRIPTION,
registration=config.ALLOW_REGISTRATION, r_to_star=rating_to_star)
return dict(title=config.TITLE,
style=config.STYLE,
description=config.DESCRIPTION,
registration=config.ALLOW_REGISTRATION,
r_to_star=rating_to_star)
@app.errorhandler(HTTPException)
def page_not_found(e):
"""
"""
Renders the error pages.
Returns:
str: html formatted Error page
"""
return render_template("error.html", errorcode=str(e.code) + " " + e.name), e.code
return render_template("error.html", errorcode=str(e.code) + " " + e.name), e.code
@app.route("/")
def index():
"""
"""
Renders the index page.
Returns:
str: html formatted index page
"""
entries = db.get_entries()
entries.reverse()
return render_template("index.html", entries=entries)
entries = db.get_entries()
entries.reverse()
return render_template("index.html", entries=entries)
@app.route("/archive")
def archive():
"""
"""
Renders the archive page.
Returns:
str: html formatted archive page
"""
entries = db.get_entries()
entries.sort(key=lambda y: y.item.name)
entries.reverse()
entries.sort(key=lambda y: y.item.date)
entries.reverse()
return render_template("archive.html", entries=entries)
entries = db.get_entries()
entries.sort(key=lambda y: y.item.name)
entries.reverse()
entries.sort(key=lambda y: y.item.date)
entries.reverse()
return render_template("archive.html", entries=entries)
@app.route("/user/<name>")
def user(name):
"""
"""
Renders the user page of a specific user.
Parameters:
@@ -107,19 +107,19 @@ def user(name):
Returns:
str: html formatted user page
"""
entries = db.get_entries_by_username(name)
entries.sort(key=lambda y: y.item.name)
entries.reverse()
entries.sort(key=lambda y: y.item.date)
entries.reverse()
if entries != []:
return render_template("user.html", name=name, entries=entries)
abort(404)
entries = db.get_entries_by_username(name)
entries.sort(key=lambda y: y.item.name)
entries.reverse()
entries.sort(key=lambda y: y.item.date)
entries.reverse()
if entries != []:
return render_template("user.html", name=name, entries=entries)
abort(404)
@app.route("/entry/<ident>")
def entry(ident):
"""
"""
Renders the entry page of a specific entry.
Parameters:
@@ -128,140 +128,171 @@ def entry(ident):
Returns:
str: html formatted entry page
"""
entry = db.get_entry_by_id(ident)
if entry is not None:
return render_template("standalone.html", entry=entry)
abort(404)
entry = db.get_entry_by_id(ident)
if entry is not None:
return render_template("standalone.html", entry=entry)
abort(404)
@app.route("/feed")
def feed():
"""
"""
Renders the rss feed of a the feed.
Returns:
str: xml formatted feed
"""
entries = db.get_entries()
entries.reverse()
rss_xml = render_template("rss.xml", entries=entries)
return rss_xml
entries = db.get_entries()
entries.reverse()
rss_xml = render_template("rss.xml", entries=entries)
return rss_xml
@app.route("/search", methods=["GET", "POST"])
def search():
"""
"""
Renders the search page.
Returns:
str: html formatted search page.
"""
form = SearchForm()
if request.method == "POST":
query_str = request.form["query_str"]
query_res = ft_search(query_str)
return render_template("search.html", form=form, results=query_res), 200
return render_template("search.html", form=form, content=""), 200
form = SearchForm()
if request.method == "POST":
query_str = request.form["query_str"]
query_res = ft_search(query_str)
return render_template("search.html", form=form, results=query_res), 200
return render_template("search.html", form=form, content=""), 200
@app.route("/login", methods=["GET", "POST"])
def login():
"""
"""
Logs the user in.
Returns:
str: html formatted login page, if login is successful renders the index
page.
"""
if current_user.is_authenticated:
if current_user.is_authenticated:
return redirect(url_for("index"))
form = LoginForm()
if form.validate_on_submit():
user = db.get_user_by_name(form.username.data)
print(user)
if user is not None:
if user.check_password(form.password.data):
login_user(user)
return redirect(url_for("index"))
form = LoginForm()
if form.validate_on_submit():
user = db.get_user_by_name(form.username.data)
print(user)
if user is not None:
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", form=form)
flash("Invalid username or password.")
return redirect(url_for("login"))
return render_template("login.html", form=form)
@app.route('/logout')
def logout():
"""
"""
Logs out the current user.
Returns:
str: html formatted index page.
"""
logout_user()
return redirect(url_for("index"))
logout_user()
return redirect(url_for("index"))
@app.route("/register", methods=["GET", "POST"])
def register():
"""
"""
Registers new users.
Returns:
str: html formatted registration page, if registration is successful
renders the index page.
"""
if current_user.is_authenticated or not config.ALLOW_REGISTRATION:
if current_user.is_authenticated or not config.ALLOW_REGISTRATION:
return redirect(url_for("index"))
form = RegisterForm()
if form.validate_on_submit():
user = db.get_user_by_name(form.username.data)
if user is None:
ident = db.insert_user(form.username.data, form.password.data)
if ident is not None:
user = db.get_user_by_id(ident)
login_user(user)
return redirect(url_for("index"))
form = RegisterForm()
if form.validate_on_submit():
user = db.get_user_by_name(form.username.data)
if user is None:
ident = db.insert_user(form.username.data, form.password.data)
if ident is not None:
user = db.get_user_by_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", form=form)
flash("An error occured during registration.")
return redirect(url_for("register"))
return render_template("register.html", form=form)
@app.route("/write_entry", methods=["GET", "POST"])
@login_required
def write_entry():
"""
"""
Stores newly written entries.
Returns:
str: html formatted write entry page, if posting of the entry is successful
renders the index page.
"""
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)
create_search_index()
return redirect(url_for("index"))
return render_template("write.html", form=form)
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)
create_search_index()
return redirect(url_for("index"))
return render_template("write.html", form=form)
@app.route("/delete_entry/<ident>", methods=["GET", "POST"])
@app.route("/delete_entry/<ident>", methods=["POST"])
@login_required
def delete_entry(ident):
"""
"""
Deletes an existing entry.
Returns:
str: html formatted index entry page.
"""
if not current_user.is_authenticated:
return redirect(url_for("index"))
if current_user.id == db.get_entry_by_id(ident).user.id:
db.delete_entry(ident)
create_search_index()
if not current_user.is_authenticated:
return redirect(url_for("index"))
if current_user.id == db.get_entry_by_id(ident).user.id:
db.delete_entry(ident)
create_search_index()
return redirect(url_for("index"))
@app.route("/edit_entry/<ident>", methods=["GET", "POST"])
@login_required
def edit_entry(ident):
entry = db.get_entry_by_id(ident)
if entry is None:
abort(404)
if current_user.id != entry.user.id:
abort(403)
form = WriteForm()
if request.method == "GET":
form.name.data = entry.item.name
form.date.data = entry.item.date
form.text.data = entry.text
form.rating.data = entry.rating
if form.validate_on_submit():
db.update_entry(ident, form.name.data, form.date.data, form.text.data, form.rating.data)
create_search_index()
return redirect(url_for("entry", ident=ident))
return render_template("write.html", form=form, edit_mode=True)
if __name__ == "__main__":
app.run(host="0.0.0.0")
app.run(host="0.0.0.0")

View File

@@ -4,7 +4,7 @@ db = Database()
def rating_to_star(rating):
"""
"""
Creates a string with stars based on the rating.
Parameters:
@@ -13,10 +13,10 @@ def rating_to_star(rating):
Returns:
string: unicode-formatted star-rating string
"""
res = u"\u272D"*int(rating/20)
length = len(res)
if rating/20 % 1 >= 0.5:
length += 1
res += u" \u2BE8 "
res += (u"\u2606" * (5 - length))
return res
res = u"\u272D" * int(rating / 20)
length = len(res)
if rating / 20 % 1 >= 0.5:
length += 1
res += u" \u2BE8 "
res += (u"\u2606" * (5 - length))
return res

View File

@@ -1,11 +1,12 @@
from datetime import date as dt
import os
import sqlite3
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import date as dt
from werkzeug.security import check_password_hash, generate_password_hash
class User():
"""
"""
A class to represent a user.
Attributes:
@@ -17,16 +18,16 @@ class User():
pass_hash (str): hash of the users password
"""
def __init__(self, name, pass_hash=None):
self.name = name
self.id = None
self.is_active = True
self.is_authenticated = True
self.is_anonymous = False
self.pass_hash = pass_hash
def __init__(self, name, pass_hash=None):
self.name = name
self.id = None
self.is_active = True
self.is_authenticated = True
self.is_anonymous = False
self.pass_hash = pass_hash
def set_password(self, password):
"""
def set_password(self, password):
"""
Set the password hash of the user from a password.
Parameters:
@@ -35,10 +36,10 @@ class User():
Returns:
None
"""
self.pass_hash = generate_password_hash(password)
self.pass_hash = generate_password_hash(password)
def set_id(self, ident):
"""
def set_id(self, ident):
"""
Set the id of the user.
Parameters:
@@ -47,10 +48,10 @@ class User():
Returns:
None
"""
self.id = ident
self.id = ident
def check_password(self, password):
"""
def check_password(self, password):
"""
Check if a given password matches the one of the users by comparing the
hashes.
@@ -60,20 +61,20 @@ class User():
Returns:
bool: True if it matches the users password, False otherwise
"""
return check_password_hash(self.pass_hash, password)
return check_password_hash(self.pass_hash, password)
def get_id(self):
"""
def get_id(self):
"""
Get the id of the user.
Returns:
int: id of the user
"""
return self.id
return self.id
class Item():
"""
"""
A class to represent an item.
Attributes:
@@ -82,23 +83,23 @@ class Item():
date (str): date the item was created
"""
def __init__(self, name, date):
self.name = name
self.date = date
self.id = None
def __init__(self, name, date):
self.name = name
self.date = date
self.id = None
def set_id(self, ident):
"""
def set_id(self, ident):
"""
Set the id of the item.
Returns:
int: id of the item
"""
self.id = ident
self.id = ident
class Entry():
"""
"""
A class to represent an entry.
Attributes:
@@ -110,43 +111,43 @@ class Entry():
id (int): id of the item
"""
def __init__(self, text, rating, date):
self.text = text
self.rating = rating
self.date = date
self.item = None
self.user = None
def __init__(self, text, rating, date):
self.text = text
self.rating = rating
self.date = date
self.item = None
self.user = None
def set_id(self, ident):
"""
def set_id(self, ident):
"""
Set the id of the entry.
Parameters:
ident(int): id of the entry
"""
self.id = ident
self.id = ident
def set_item(self, item):
"""
def set_item(self, item):
"""
Set the item of the entry.
Parameters:
item(Item): item of the entry
"""
self.item = item
self.item = item
def set_user(self, user):
"""
def set_user(self, user):
"""
Set the user of the entry.
Parameters:
user(User): user of the entry
"""
self.user = user
self.user = user
class Database:
"""
"""
A class to represent an entry.
Attributes:
@@ -156,53 +157,53 @@ class Database:
DB_DIR(PathLike): path that leads to the directory containing the database
"""
def __init__(self):
self.USER_TABLE_FILE = 'USERS'
self.ENTRY_TABLE_FILE = 'ENTRIES'
self.ITEM_TABLE_FILE = 'ITEMS'
self.DB_DIR = os.path.dirname("./data/")
self.setup_db()
def __init__(self):
self.USER_TABLE_FILE = 'USERS'
self.ENTRY_TABLE_FILE = 'ENTRIES'
self.ITEM_TABLE_FILE = 'ITEMS'
self.DB_DIR = os.path.dirname("./data/")
self.setup_db()
def connect(self):
"""
def connect(self):
"""
Connect to an existing database instance based on the object
attributes.
Return:
Connection: connection to the database
"""
path = os.path.join(self.DB_DIR, "data.db")
return sqlite3.connect(path)
path = os.path.join(self.DB_DIR, "data.db")
return sqlite3.connect(path)
def setup_db(self):
"""
def setup_db(self):
"""
Creates a database with the needed tables if it doesn't already exits.
"""
db = self.connect()
crs = db.cursor()
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.ITEM_TABLE_FILE + \
"(id INTEGER PRIMARY KEY AUTOINCREMENT," + \
"name CHAR(32) NOT NULL," + \
"date CHAR(4)," + \
"UNIQUE(date, name))"
crs.execute(query)
query = "CREATE TABLE IF NOT EXISTS " + self.ENTRY_TABLE_FILE + \
"(id INTEGER PRIMARY KEY AUTOINCREMENT," + \
"item_id INTEGER NOT NULL REFERENCES " + self.ITEM_TABLE_FILE + "(id)," + \
"text TEXT NOT NULL," + \
"rating INTEGER NOT NULL," +\
"user_id INTEGER REFERENCES " + self.USER_TABLE_FILE + "(id),"\
"date CHAR(10) NOT NULL)"
crs.execute(query)
db.commit()
db = self.connect()
crs = db.cursor()
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.ITEM_TABLE_FILE + \
"(id INTEGER PRIMARY KEY AUTOINCREMENT," + \
"name CHAR(32) NOT NULL," + \
"date CHAR(4)," + \
"UNIQUE(date, name))"
crs.execute(query)
query = "CREATE TABLE IF NOT EXISTS " + self.ENTRY_TABLE_FILE + \
"(id INTEGER PRIMARY KEY AUTOINCREMENT," + \
"item_id INTEGER NOT NULL REFERENCES " + self.ITEM_TABLE_FILE + "(id)," + \
"text TEXT NOT NULL," + \
"rating INTEGER NOT NULL," +\
"user_id INTEGER REFERENCES " + self.USER_TABLE_FILE + "(id),"\
"date CHAR(10) NOT NULL)"
crs.execute(query)
db.commit()
def insert_user(self, username, password):
"""
def insert_user(self, username, password):
"""
Insert a row in the user table.
Parameters:
@@ -212,20 +213,20 @@ class Database:
Returns:
int: number of the line the row was added, None if it wasn't successful
"""
pass_hash = generate_password_hash(password)
if self.get_user_by_name(username) is None and pass_hash is not None:
db = self.connect()
crs = db.cursor()
query = "INSERT INTO " + self.USER_TABLE_FILE + \
"(`name`,`password`)" + \
"VALUES (?, ?) ON CONFLICT DO NOTHING"
crs.execute(query, (username, pass_hash))
db.commit()
return crs.lastrowid
return None
pass_hash = generate_password_hash(password)
if self.get_user_by_name(username) is None and pass_hash is not None:
db = self.connect()
crs = db.cursor()
query = "INSERT INTO " + self.USER_TABLE_FILE + \
"(`name`,`password`)" + \
"VALUES (?, ?) ON CONFLICT DO NOTHING"
crs.execute(query, (username, pass_hash))
db.commit()
return crs.lastrowid
return None
def insert_entry(self, name, date, text, rating, user_id=None):
"""
def insert_entry(self, name, date, text, rating, user_id=None):
"""
Insert a row in the entry table.
Parameters:
@@ -238,25 +239,25 @@ class Database:
Returns:
int: number of the line the row was added
"""
db = self.connect()
crs = db.cursor()
query = "INSERT OR IGNORE INTO " + self.ITEM_TABLE_FILE + \
"(`name`,`date`)" + "VALUES (?, ?)"
crs.execute(query, (name, date))
query = "SELECT id FROM " + self.ITEM_TABLE_FILE + \
" WHERE name = ? AND date = ?"
crs.execute(query, (name, date))
item_id = crs.fetchone()[0]
date = dt.today().strftime('%Y-%m-%d')
query = "INSERT INTO " + self.ENTRY_TABLE_FILE + \
"(`item_id`, `text`, `rating`, `user_id`, `date`)" + \
"VALUES (?, ?, ?, ?, ?)"
crs.execute(query, (item_id, text, rating, user_id, date))
db.commit()
return crs.lastrowid
db = self.connect()
crs = db.cursor()
query = "INSERT OR IGNORE INTO " + self.ITEM_TABLE_FILE + \
"(`name`,`date`)" + "VALUES (?, ?)"
crs.execute(query, (name, date))
query = "SELECT id FROM " + self.ITEM_TABLE_FILE + \
" WHERE name = ? AND date = ?"
crs.execute(query, (name, date))
item_id = crs.fetchone()[0]
date = dt.today().strftime('%Y-%m-%d')
query = "INSERT INTO " + self.ENTRY_TABLE_FILE + \
"(`item_id`, `text`, `rating`, `user_id`, `date`)" + \
"VALUES (?, ?, ?, ?, ?)"
crs.execute(query, (item_id, text, rating, user_id, date))
db.commit()
return crs.lastrowid
def delete_entry(self, ident):
"""
def delete_entry(self, ident):
"""
Delete a row from the entry table based on the entrys id.
Parameters:
@@ -265,31 +266,69 @@ class Database:
Returns:
int: number of the line the row was removed from
"""
db = self.connect()
crs = db.cursor()
query = "DELETE FROM " + self.ENTRY_TABLE_FILE + " WHERE id = ?"
crs.execute(query, (ident, ))
db.commit()
return crs.lastrowid
db = self.connect()
crs = db.cursor()
query = "DELETE FROM " + self.ENTRY_TABLE_FILE + " WHERE id = ?"
crs.execute(query, (ident,))
db.commit()
return crs.lastrowid
def get_entries(self):
def update_entry(self, ident, name, item_date, text, rating):
"""
Update an existing entry.
Parameters:
ident (int): id of the entry
name (str): updated item name
item_date (str): updated item date
text (str): updated review text
rating (int): updated rating
"""
db = self.connect()
crs = db.cursor()
query = "SELECT item_id FROM " + self.ENTRY_TABLE_FILE + " WHERE id = ?"
crs.execute(query, (ident,))
fetched = crs.fetchone()
if fetched is None:
return None
item_id = fetched[0]
query = "UPDATE " + self.ITEM_TABLE_FILE + \
" SET name = ?, date = ? WHERE id = ?"
crs.execute(query, (name, item_date, item_id))
query = "UPDATE " + self.ENTRY_TABLE_FILE + \
" SET text = ?, rating = ? WHERE id = ?"
crs.execute(query, (text, rating, ident))
db.commit()
return ident
def get_entries(self):
"""
Return all the entries stored in the database.
Return:
List(Entry): list of entries in database
"""
db = self.connect()
crs = db.cursor()
query = "SELECT * FROM " + self.ENTRY_TABLE_FILE
crs.execute(query)
res = []
for item in crs.fetchall():
res.append(self.entry_from_db(*item))
return res
db = self.connect()
crs = db.cursor()
query = "SELECT * FROM " + self.ENTRY_TABLE_FILE
crs.execute(query)
res = []
for item in crs.fetchall():
res.append(self.entry_from_db(*item))
return res
def get_entry_by_id(self, ident):
"""
def get_entry_by_id(self, ident):
"""
Return an entry stored in the database based on the entrys id.
Parameters:
@@ -298,18 +337,18 @@ class Database:
Returns:
Entry: entry that matched the given id
"""
db = self.connect()
crs = db.cursor()
query = "SELECT * FROM " + self.ENTRY_TABLE_FILE + " WHERE id = ?"
crs.execute(query, (ident, ))
fetched = crs.fetchone()
if fetched is None:
return None
else:
return self.entry_from_db(*fetched)
db = self.connect()
crs = db.cursor()
query = "SELECT * FROM " + self.ENTRY_TABLE_FILE + " WHERE id = ?"
crs.execute(query, (ident,))
fetched = crs.fetchone()
if fetched is None:
return None
else:
return self.entry_from_db(*fetched)
def get_entries_by_username(self, username):
"""
def get_entries_by_username(self, username):
"""
Return a entries stored in the database based on the entries name.
Parameters:
@@ -318,19 +357,19 @@ class Database:
Returns:
List(Entry): entries that matched the given name
"""
db = self.connect()
crs = db.cursor()
query = "SELECT * FROM " + self.ENTRY_TABLE_FILE + \
" WHERE user_id = (SELECT id FROM " + self.USER_TABLE_FILE + \
" WHERE name = ?)"
crs.execute(query, (username, ))
res = []
for item in crs.fetchall():
res.append(self.entry_from_db(*item))
return res
db = self.connect()
crs = db.cursor()
query = "SELECT * FROM " + self.ENTRY_TABLE_FILE + \
" WHERE user_id = (SELECT id FROM " + self.USER_TABLE_FILE + \
" WHERE name = ?)"
crs.execute(query, (username,))
res = []
for item in crs.fetchall():
res.append(self.entry_from_db(*item))
return res
def get_item_by_id(self, ident):
"""
def get_item_by_id(self, ident):
"""
Return an item stored in the database based on the items id.
Parameters:
@@ -339,18 +378,18 @@ class Database:
Returns:
Item: item that matched the given id
"""
db = self.connect()
crs = db.cursor()
query = "SELECT * FROM " + self.ITEM_TABLE_FILE + " WHERE id = ?"
crs.execute(query, (ident, ))
fetched = crs.fetchone()
if fetched is None:
return None
else:
return self.item_from_db(*fetched)
db = self.connect()
crs = db.cursor()
query = "SELECT * FROM " + self.ITEM_TABLE_FILE + " WHERE id = ?"
crs.execute(query, (ident,))
fetched = crs.fetchone()
if fetched is None:
return None
else:
return self.item_from_db(*fetched)
def get_user_by_id(self, ident):
"""
def get_user_by_id(self, ident):
"""
Return a user stored in the database based on the users id.
Parameters:
@@ -359,18 +398,18 @@ class Database:
Returns:
Item: user that matched the given id
"""
db = self.connect()
crs = db.cursor()
query = "SELECT * FROM " + self.USER_TABLE_FILE + " WHERE id = ?"
crs.execute(query, (ident, ))
fetched = crs.fetchone()
if fetched is None:
return None
else:
return self.user_from_db(*fetched)
db = self.connect()
crs = db.cursor()
query = "SELECT * FROM " + self.USER_TABLE_FILE + " WHERE id = ?"
crs.execute(query, (ident,))
fetched = crs.fetchone()
if fetched is None:
return None
else:
return self.user_from_db(*fetched)
def get_user_by_name(self, name):
"""
def get_user_by_name(self, name):
"""
Return a user stored in the database based on the user name.
Parameters:
@@ -379,18 +418,18 @@ class Database:
Returns:
Entry: user that matched the given name
"""
db = self.connect()
crs = db.cursor()
query = "SELECT * FROM " + self.USER_TABLE_FILE + " WHERE name = ?"
crs.execute(query, (name, ))
fetched = crs.fetchone()
if fetched is None:
return None
else:
return self.user_from_db(*fetched)
db = self.connect()
crs = db.cursor()
query = "SELECT * FROM " + self.USER_TABLE_FILE + " WHERE name = ?"
crs.execute(query, (name,))
fetched = crs.fetchone()
if fetched is None:
return None
else:
return self.user_from_db(*fetched)
def user_from_db(self, ident, name, pass_hash):
"""
def user_from_db(self, ident, name, pass_hash):
"""
Return a user from given database parameters.
Parameters:
@@ -401,12 +440,12 @@ class Database:
Returns:
User: user element with given variables
"""
user = User(name, pass_hash)
user.set_id(ident)
return user
user = User(name, pass_hash)
user.set_id(ident)
return user
def item_from_db(self, ident, name, date):
"""
def item_from_db(self, ident, name, date):
"""
Return an item from given database parameters.
Parameters:
@@ -417,12 +456,12 @@ class Database:
Returns:
Item: entry element with given variables
"""
item = Item(name, date)
item.set_id(ident)
return item
item = Item(name, date)
item.set_id(ident)
return item
def entry_from_db(self, ident, item_id, text, rating, user_id, date):
"""
def entry_from_db(self, ident, item_id, text, rating, user_id, date):
"""
Return an entry from given database parameters.
Parameters:
@@ -436,8 +475,8 @@ class Database:
Returns:
Entry: entry element with given variables
"""
entry = Entry(text, rating, date)
entry.set_id(ident)
entry.set_item(self.get_item_by_id(item_id))
entry.set_user(self.get_user_by_id(user_id))
return entry
entry = Entry(text, rating, date)
entry.set_id(ident)
entry.set_item(self.get_item_by_id(item_id))
entry.set_user(self.get_user_by_id(user_id))
return entry

View File

@@ -1,61 +1,64 @@
from datetime import date
from flask_ckeditor import CKEditorField
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, StringField
from wtforms import PasswordField, StringField, SubmitField
from wtforms.fields import IntegerField
from wtforms.validators import DataRequired, EqualTo, InputRequired, \
NumberRange, ValidationError, Length
from wtforms.validators import (DataRequired, EqualTo, InputRequired, Length, NumberRange,
ValidationError)
class LoginForm(FlaskForm):
"""
"""
A Class for the Form that is used while logging in.
"""
username = StringField("Username", validators=[DataRequired(),
Length(min=4, max=32)])
password = PasswordField("Password", validators=[DataRequired(),
Length(min=4, max=32)])
submit = SubmitField("Sign In")
username = StringField("Username", validators=[DataRequired(), Length(min=4, max=32)])
password = PasswordField("Password", validators=[DataRequired(), Length(min=4, max=32)])
submit = SubmitField("Sign In")
class RegisterForm(FlaskForm):
"""
"""
A Class for the Form that is used while registering.
"""
username = StringField("Username", validators=[DataRequired(),
Length(min=4, max=32)])
password = PasswordField("Password", validators=[DataRequired(),
Length(min=4, max=32)])
password2 = PasswordField(
"Repeat Password", validators=[DataRequired(), EqualTo("password")])
submit = SubmitField("Register")
username = StringField("Username", validators=[DataRequired(), Length(min=4, max=32)])
password = PasswordField("Password", validators=[DataRequired(), Length(min=4, max=32)])
password2 = PasswordField("Repeat Password", validators=[DataRequired(), EqualTo("password")])
submit = SubmitField("Register")
class SearchForm(FlaskForm):
"""
"""
A Class for the Form that is used while searching.
"""
query_str = StringField(
"Query", [DataRequired("Please enter the search term")])
submit = SubmitField("Search")
query_str = StringField("Query", [DataRequired("Please enter the search term")])
submit = SubmitField("Search")
class WriteForm(FlaskForm):
"""
"""
A Class for the Form that is used while writing a new entry.
"""
name = StringField("Name", validators=[DataRequired(),
Length(min=2, max=64)])
date = IntegerField("Release Year", default=date.today().year, validators=[
DataRequired(), NumberRange(min=0, max=date.today().year,
message="Year has to be valid.")])
text = CKEditorField("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")
name = StringField("Name", validators=[DataRequired(), Length(min=2, max=64)])
date = IntegerField("Release Year",
default=date.today().year,
validators=[
DataRequired(),
NumberRange(min=0, max=date.today().year, message="Year has to be valid.")
])
text = CKEditorField("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")
def validate_text(self, text):
"""
def validate_text(self, text):
"""
Validate a given input for html level one headers.
Parameters:
@@ -67,5 +70,5 @@ class WriteForm(FlaskForm):
Raises:
ValidatenError: if the text contains a first level html tag
"""
if "<h1>" in text.data or "</h1>" in text.data:
raise ValidationError("Headings on level 1 are not permitted.")
if "<h1>" in text.data or "</h1>" in text.data:
raise ValidationError("Headings on level 1 are not permitted.")

View File

@@ -1,7 +1,14 @@
Flask
Flask_CKEditor
Flask_Login
Flask_WTF
Werkzeug
Whoosh
WTForms
bleach==6.3.0
blinker==1.9.0
click==8.3.3
flask==3.1.3
flask-ckeditor==1.0.0
flask-login==0.6.3
flask-wtf==1.3.0
itsdangerous==2.2.0
jinja2==3.1.6
markupsafe==3.0.3
webencodings==0.5.1
werkzeug==3.1.8
whoosh==2.7.4
wtforms==3.2.2

View File

@@ -2,8 +2,8 @@ import os
import re
from whoosh import scoring
from whoosh.fields import ID, TEXT, Schema
from whoosh.index import create_in, open_dir
from whoosh.fields import Schema, TEXT, ID
from whoosh.qparser import QueryParser
import config
@@ -13,7 +13,7 @@ CLEANR = re.compile('<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});')
def remove_html_tags(text):
"""
"""
Convert a text from html formatted to unformatted.
Parameters:
@@ -22,31 +22,30 @@ def remove_html_tags(text):
Returns:
str: text without html tags
"""
res = re.sub(CLEANR, '', text)
return res
res = re.sub(CLEANR, '', text)
return res
def create_search_index():
"""
"""
Create the index data to search all entries.
"""
db = Database()
schema = Schema(title=TEXT(stored=True),
path=ID(stored=True), content=TEXT(stored=True))
if not os.path.exists(config.INDEX_DIR):
os.mkdir(config.INDEX_DIR)
ix = create_in(config.INDEX_DIR, schema)
writer = ix.writer()
for entry in db.get_entries():
path = str(entry.id)
text = entry.item.name + " " + entry.item.date + " " + entry.text + \
" by " + entry.user.name + " " + entry.date
writer.add_document(title=entry.item.name, path=path, content=text)
writer.commit()
db = Database()
schema = Schema(title=TEXT(stored=True), path=ID(stored=True), content=TEXT(stored=True))
if not os.path.exists(config.INDEX_DIR):
os.mkdir(config.INDEX_DIR)
ix = create_in(config.INDEX_DIR, schema)
writer = ix.writer()
for entry in db.get_entries():
path = str(entry.id)
text = entry.item.name + " " + entry.item.date + " " + entry.text + \
" by " + entry.user.name + " " + entry.date
writer.add_document(title=entry.item.name, path=path, content=text)
writer.commit()
def ft_search_times(query_str, number):
"""
"""
Search for a given term and returns a specific amount of results.
Parameters:
@@ -56,19 +55,19 @@ def ft_search_times(query_str, number):
Returns:
List(Entry): list of entries that matched the search
"""
ix = open_dir(config.INDEX_DIR)
results = []
db = Database()
with ix.searcher(weighting=scoring.BM25F) as s:
query = QueryParser("content", ix.schema).parse(query_str)
matches = s.search(query, limit=number)
for match in matches:
results.append(db.get_entry_by_id(match["path"]))
return results
ix = open_dir(config.INDEX_DIR)
results = []
db = Database()
with ix.searcher(weighting=scoring.BM25F) as s:
query = QueryParser("content", ix.schema).parse(query_str)
matches = s.search(query, limit=number)
for match in matches:
results.append(db.get_entry_by_id(match["path"]))
return results
def ft_search(query_str):
"""
"""
Search for a given term and show the predefined amount of results.
Parameters:
@@ -77,4 +76,4 @@ def ft_search(query_str):
Returns:
List(Entry): list of entries that matched the search
"""
return ft_search_times(query_str, config.SEARCH_NUMBER)
return ft_search_times(query_str, config.SEARCH_NUMBER)

View File

@@ -14,12 +14,14 @@
--text1: rgb(220, 120, 0);
}
a {
a,
.link-like {
color: var(--link0);
transition: var(--transtime);
}
a:hover {
a:hover,
.link-like:hover {
color: var(--link1);
}

View File

@@ -14,12 +14,14 @@
--text1: rgb(0,0,120);
}
a {
a,
.link-like {
color: var(--link0);
transition: var(--transtime);
}
a:hover {
a:hover,
.link-like:hover {
color: var(--link1);
}

View File

@@ -8,15 +8,26 @@
padding: 0;
}
a {
a,
.link-like {
text-decoration: none;
transition: var(--transtime);
}
a:hover {
a:hover,
.link-like:hover {
cursor: pointer;
}
.link-like {
background: none;
border: none;
padding: 0;
margin: 0;
color: inherit;
font: inherit;
}
body {
margin: 0;
}

View File

@@ -1,43 +1,43 @@
{% extends "template.html" -%}
{% block content -%}
<div class="container">
<div class="archive">
<h1>Archive</h1><br>
{% set ns = namespace(prev_item_date="", prev_item_id=None, open_li = False, open_ul = False) -%}
{% for entry in entries -%}
{% if ns.prev_item_id != entry.item.id and ns.prev_item_id != None -%}
</li>
{% set ns.open_li = False -%}
{% endif -%}
{% if entry.item.date != ns.prev_item_date -%}
{% if ns.prev_item_date != "" -%}
</ul>
{% set ns.open_ul = False -%}
{% endif -%}
<h2> {{ entry.item.date }} </h2>
<ul>
{% set ns.open_ul = True -%}
{% endif -%}
{% if ns.prev_item_id == entry.item.id -%}
<br>
{% else -%}
<li>
{{ entry.item.name }}<br>
{% set ns.open_li = True -%}
{% endif -%}
<a href="{{ url_for('entry', ident=entry.id) }}">
{{ entry.date }} {{ r_to_star(entry.rating) }} by {{ entry.user.name }}
</a>
{% set ns.prev_item_date = entry.item.date -%}
{% set ns.prev_item_id = entry.item.id -%}
{% endfor -%}
{% if ns.open_li -%}
</li>
{% endif -%}
{% if ns.open_ul -%}
</ul>
{% endif -%}
</div>
</div>
<div class="container">
<div class="archive">
<h1>Archive</h1><br>
{% set ns = namespace(prev_item_date="", prev_item_id=None, open_li = False, open_ul = False) -%}
{% for entry in entries -%}
{% if ns.prev_item_id != entry.item.id and ns.prev_item_id != None -%}
</li>
{% set ns.open_li = False -%}
{% endif -%}
{% if entry.item.date != ns.prev_item_date -%}
{% if ns.prev_item_date != "" -%}
</ul>
{% set ns.open_ul = False -%}
{% endif -%}
<h2> {{ entry.item.date }} </h2>
<ul>
{% set ns.open_ul = True -%}
{% endif -%}
{% if ns.prev_item_id == entry.item.id -%}
<br>
{% else -%}
<li>
{{ entry.item.name }}<br>
{% set ns.open_li = True -%}
{% endif -%}
<a href="{{ url_for('entry', ident=entry.id) }}">
{{ entry.date }} {{ r_to_star(entry.rating) }} by {{ entry.user.name }}
</a>
{% set ns.prev_item_date = entry.item.date -%}
{% set ns.prev_item_id = entry.item.id -%}
{% endfor -%}
{% if ns.open_li -%}
</li>
{% endif -%}
{% if ns.open_ul -%}
</ul>
{% endif -%}
</div>
</div>
{% endblock -%}

View File

@@ -1,10 +1,10 @@
{% extends "template.html" -%}
{% block content -%}
<div class="container">
<div class="important">
Error<br>
<span>{{ errorcode }}</span>
</div>
</div>
<div class="container">
<div class="important">
Error<br>
<span>{{ errorcode }}</span>
</div>
</div>
{% endblock -%}

View File

@@ -1,27 +1,27 @@
{% extends "template.html" -%}
{% block content -%}
<div class="container">
<div class="blog">
<h1>Blog</h1><br>
{% for entry in entries -%}
<div class="entry">
<h1 id="{{ entry.id }}">
<a href="{{ url_for('entry', ident=entry.id) }}">
{{ entry.item.name }} ({{ entry.item.date }}) {{ r_to_star(entry.rating) }}
</a>
</h1>
<small>
rated {{ entry.rating }}/100 by
<a href="{{ url_for('user', name=entry.user.name) }}">
{{ entry.user.name }}
</a> on {{ entry.date }}
</small><br>
{% autoescape off -%}
{{ entry.text }}
{% endautoescape -%}
</div><br>
{% endfor -%}
</div>
</div>
<div class="container">
<div class="blog">
<h1>Blog</h1><br>
{% for entry in entries -%}
<div class="entry">
<h1 id="{{ entry.id }}">
<a href="{{ url_for('entry', ident=entry.id) }}">
{{ entry.item.name }} ({{ entry.item.date }}) {{ r_to_star(entry.rating) }}
</a>
</h1>
<small>
rated {{ entry.rating }}/100 by
<a href="{{ url_for('user', name=entry.user.name) }}">
{{ entry.user.name }}
</a> on {{ entry.date }}
</small><br>
{% autoescape off -%}
{{ entry.text }}
{% endautoescape -%}
</div><br>
{% endfor -%}
</div>
</div>
{% endblock -%}

View File

@@ -1,24 +1,24 @@
{% extends "template.html" -%}
{% block content -%}
<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.submit() }}</p>
{% for mesg in get_flashed_messages() -%}
<p>{{ mesg }}</p>
{% endfor -%}
</form>
</div>
</div>
<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.submit() }}</p>
{% for mesg in get_flashed_messages() -%}
<p>{{ mesg }}</p>
{% endfor -%}
</form>
</div>
</div>
{% endblock -%}

View File

@@ -1,37 +1,37 @@
{% extends "template.html" -%}
{% block content -%}
<div class="container">
<div class="logging">
<h1>Register</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}
{% for error in form.username.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}
{% for error in form.password.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
</p>
<p>
{{ form.password2.label }}<br>
{{ form.password2(size=32) }}
{% for error in form.password2.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
</p>
<p>{{ form.submit() }}</p>
{% for mesg in get_flashed_messages() -%}
<p>{{ mesg }}</p>
{% endfor -%}
</form>
</div>
</div>
<div class="container">
<div class="logging">
<h1>Register</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}
{% for error in form.username.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}
{% for error in form.password.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
</p>
<p>
{{ form.password2.label }}<br>
{{ form.password2(size=32) }}
{% for error in form.password2.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
</p>
<p>{{ form.submit() }}</p>
{% for mesg in get_flashed_messages() -%}
<p>{{ mesg }}</p>
{% endfor -%}
</form>
</div>
</div>
{% endblock -%}

View File

@@ -1,29 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ title }}</title>
<channel>
<title>{{ title }}</title>
<description>{{ description }}</description>
<language>en-us</language>
<link>{{ url_for("feed", _external=True) }}</link>
<atom:link href="{{ url_for('feed', _external=True) }}" rel="self" type="application/rss+xml" />
{% for entry in entries -%}
<item>
<title>
{{ entry.item.name }} ({{ entry.item.date }}) {{ r_to_star(entry.rating) }} by {{ entry.user.name }}
</title>
<guid>
{{ url_for("index", _anchor=entry.id, _external=True) }}
</guid>
<pubDate>
{{ entry.date }}
</pubDate>
<description>
{% autoescape off -%}
text
{% endautoescape -%}
</description>
</item>
{% endfor -%}
</channel>
<link>{{
url_for("feed", _external=True) }}</link>
<atom:link
href="{{ url_for('feed', _external=True) }}" rel="self" type="application/rss+xml" /> {%
for entry in entries -%} <item>
<title>
{{ entry.item.name }} ({{ entry.item.date }}) {{ r_to_star(entry.rating) }} by {{
entry.user.name }}
</title>
<guid>
{{ url_for("index", _anchor=entry.id, _external=True) }}
</guid>
<pubDate>
{{ entry.date }}
</pubDate>
<description>
{% autoescape off -%}
text
{% endautoescape -%}
</description>
</item>
{% endfor -%} </channel>
</rss>

View File

@@ -2,22 +2,23 @@
{% block content %}
<div class="container">
<h1>Search</h1><br>
<div class="search">
<form action="{{ url_for('search') }}" method=post>
{{ form.hidden_tag() }}
{{ form.query_str }}
{{ form.submit }}
</form>
<ul>
{% for entry in results -%}
<li>
<a href="{{ url_for('entry', ident=entry.id) }}">
{{ entry.date }} {{ r_to_star(entry.rating) }} {{ entry.item.name }} ({{ entry.item.date }}) by {{ entry.user.name }}
</a>
</li>
{% endfor -%}
</ul>
</div>
<h1>Search</h1><br>
<div class="search">
<form action="{{ url_for('search') }}" method=post>
{{ form.hidden_tag() }}
{{ form.query_str }}
{{ form.submit }}
</form>
<ul>
{% for entry in results -%}
<li>
<a href="{{ url_for('entry', ident=entry.id) }}">
{{ entry.date }} {{ r_to_star(entry.rating) }} {{ entry.item.name }} ({{ entry.item.date }}) by {{
entry.user.name }}
</a>
</li>
{% endfor -%}
</ul>
</div>
</div>
{% endblock -%}

View File

@@ -1,34 +1,39 @@
{% extends "template.html" -%}
{% block content -%}
<div class="container">
<div class="standalone">
<h1>
{{ entry.item.name }} ({{ entry.item.date }})
{{ r_to_star(entry.rating) }}
</h1>
<small>
rated {{ entry.rating }}/100 by
<a href="{{ url_for('user', name=entry.user.name) }}">
{{ entry.user.name }}
</a>
on
<a href="{{ url_for('index', _anchor='{0:d}'.format(entry.id)) }}">
{{ entry.date }}
</a>
</small><br>
{% if current_user.id == entry.user.id -%}
<small>
[
<a href="{{ url_for('delete_entry', ident='{0:d}'.format(entry.id)) }}">
delete entry
</a>
]
</small><br>
{% endif -%}
{% autoescape off -%}
{{ entry.text }}
{% endautoescape -%}<br>
</div>
</div>
<div class="container">
<div class="standalone">
<h1>
{{ entry.item.name }} ({{ entry.item.date }})
{{ r_to_star(entry.rating) }}
</h1>
<small>
rated {{ entry.rating }}/100 by
<a href="{{ url_for('user', name=entry.user.name) }}">
{{ entry.user.name }}
</a>
on
<a href="{{ url_for('index', _anchor='{0:d}'.format(entry.id)) }}">
{{ entry.date }}
</a>
</small><br>
{% if current_user.id == entry.user.id -%}
<a class="link-like" href="{{ url_for('edit_entry', ident='{0:d}'.format(entry.id)) }}">
edit entry
</a>
-
<form method="post" action="{{ url_for('delete_entry', ident='{0:d}'.format(entry.id)) }}" style="display:inline;"
onsubmit="return confirm('Delete entry?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="link-like">
delete entry
</button>
</form>
{% endif -%}
{% autoescape off -%}
{{ entry.text }}
{% endautoescape -%}<br>
</div>
</div>
{% endblock -%}

View File

@@ -1,48 +1,51 @@
{% set navigation_bar = [
(url_for("index"), "index", "Blog"),
(url_for("archive"), "archive", "Archive"),
(url_for("search"), "search", "Search")
(url_for("index"), "index", "Blog"),
(url_for("archive"), "archive", "Archive"),
(url_for("search"), "search", "Search")
] -%}
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width" initial-scale=1.0>
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='graphics/logo.png') }}">
<link href="{{ url_for('static', filename='css/' + style + '.css') }}" rel="stylesheet" type="text/css">
<title>{{ title }}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width" initial-scale=1.0>
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='graphics/logo.png') }}">
<link href="{{ url_for('static', filename='css/' + style + '.css') }}" rel="stylesheet" type="text/css">
</head>
<body>
<div class="main-menu-dropdown">
<!-- <img class="logo" src="/static/images/logo.png"> -->
<a href="{{ url_for('index') }}">
<img class="logo" src="{{ url_for('static', filename='graphics/logo.png') }}">
<span>{{ title }}</span>
</a>
<input type="checkbox" id="main-menu-check">
<label for="main-menu-check" class="show-menu">&#9776;</label>
<div class="main-menu">
{% for href, id, caption in navigation_bar -%}
<a href="{{ href|e }}">{{ caption|e }}</a>
{% endfor -%}
<label for="main-menu-check" class="hide-menu">X</label>
</div>
<div class="main-menu-dropdown">
<!-- <img class="logo" src="/static/images/logo.png"> -->
<a href="{{ url_for('index') }}">
<img class="logo" src="{{ url_for('static', filename='graphics/logo.png') }}">
<span>{{ title }}</span>
</a>
<input type="checkbox" id="main-menu-check">
<label for="main-menu-check" class="show-menu">&#9776;</label>
<div class="main-menu">
{% for href, id, caption in navigation_bar -%}
<a href="{{ href|e }}">{{ caption|e }}</a>
{% endfor -%}
<label for="main-menu-check" class="hide-menu">X</label>
</div>
{% block content -%}
{% endblock -%}
<footer>
Made with <a href="https://github.com/tiyn/container-critique">Container Critique </a>.<br>
{% if current_user.is_anonymous -%}
<a href="{{ url_for('login') }}">Login</a>
{% if registration -%}
-
<a href="{{ url_for('register') }}">Register</a>
{% endif -%}
{% else -%}
<a href="{{ url_for('logout') }}">Logout</a> -
<a href="{{ url_for('write_entry') }}">Write entry</a>
{% endif -%}
</footer>
</div>
{% block content -%}
{% endblock -%}
<footer>
Made with <a href="https://github.com/tiyn/container-critique">Container Critique </a>.<br>
{% if current_user.is_anonymous -%}
<a href="{{ url_for('login') }}">Login</a>
{% if registration -%}
-
<a href="{{ url_for('register') }}">Register</a>
{% endif -%}
{% else -%}
<a href="{{ url_for('logout') }}">Logout</a> -
<a href="{{ url_for('write_entry') }}">Write entry</a>
{% endif -%}
</footer>
</body>
</html>

View File

@@ -2,42 +2,42 @@
{% block content -%}
<div class="container">
<div class="archive">
<h1>User: {{ name }}</h1><br>
{% set ns = namespace(prev_item_date="", prev_item_id=None, open_li = False, open_ul = False) -%}
{% for entry in entries -%}
{% if ns.prev_item_id != entry.item.id and ns.prev_item_id != None -%}
</li>
{% set ns.open_li = False -%}
{% endif -%}
{% if entry.item.date != ns.prev_item_date -%}
{% if ns.prev_item_date != "" -%}
</ul>
{% set ns.open_ul = False -%}
{% endif -%}
<h2> {{ entry.item.date }} </h2>
<ul>
{% set ns.open_ul = True -%}
{% endif -%}
{% if ns.prev_item_id == entry.item.id -%}
<br>
{% else -%}
<li>
{{ entry.item.name }}<br>
{% set ns.open_li = True -%}
{% endif -%}
<a href="{{ url_for('entry', ident=entry.id) }}">
{{ entry.date }} {{ r_to_star(entry.rating) }}
</a>
{% set ns.prev_item_date = entry.item.date -%}
{% set ns.prev_item_id = entry.item.id -%}
{% endfor -%}
{% if ns.open_li -%}
</li>
{% endif -%}
{% if ns.open_ul -%}
</ul>
{% endif -%}
</div>
<div class="archive">
<h1>User: {{ name }}</h1><br>
{% set ns = namespace(prev_item_date="", prev_item_id=None, open_li = False, open_ul = False) -%}
{% for entry in entries -%}
{% if ns.prev_item_id != entry.item.id and ns.prev_item_id != None -%}
</li>
{% set ns.open_li = False -%}
{% endif -%}
{% if entry.item.date != ns.prev_item_date -%}
{% if ns.prev_item_date != "" -%}
</ul>
{% set ns.open_ul = False -%}
{% endif -%}
<h2> {{ entry.item.date }} </h2>
<ul>
{% set ns.open_ul = True -%}
{% endif -%}
{% if ns.prev_item_id == entry.item.id -%}
<br>
{% else -%}
<li>
{{ entry.item.name }}<br>
{% set ns.open_li = True -%}
{% endif -%}
<a href="{{ url_for('entry', ident=entry.id) }}">
{{ entry.date }} {{ r_to_star(entry.rating) }}
</a>
{% set ns.prev_item_date = entry.item.date -%}
{% set ns.prev_item_id = entry.item.id -%}
{% endfor -%}
{% if ns.open_li -%}
</li>
{% endif -%}
{% if ns.open_ul -%}
</ul>
{% endif -%}
</div>
</div>
{% endblock -%}

View File

@@ -2,46 +2,46 @@
{% block content -%}
<div class="container">
<div class="writing">
<h1>Write Critique</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.name.label }}<br>
{{ form.name(size=64) }}
{% for error in form.name.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
</p>
<p>
{{ form.date.label }}<br>
{{ form.date }}
{% for error in form.date.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
</p>
<p>
<div class="writing">
<h1>Write Critique</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.name.label }}<br>
{{ form.name(size=64) }}
{% for error in form.name.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
</p>
<p>
{{ form.date.label }}<br>
{{ form.date }}
{% for error in form.date.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
</p>
<p>
{{ form.text.label }}<br>
{{ form.text }}
{% for error in form.text.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
{{ ckeditor.load() }}
{{ ckeditor.config(name="text") }}
</p>
<p>
{{ form.rating.label }}<br>
{{ form.rating }}
{% for error in form.rating.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
</p>
<p>{{ form.submit() }}</p>
{% for mesg in get_flashed_messages() -%}
<p>{{ mesg }}</p>
{% endfor -%}
</form>
</div>
{{ form.text.label }}<br>
{{ form.text }}
{% for error in form.text.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
{{ ckeditor.load() }}
{{ ckeditor.config(name="text") }}
</p>
<p>
{{ form.rating.label }}<br>
{{ form.rating }}
{% for error in form.rating.errors -%}
<span style="color: red;">[{{ error }}]</span>
{% endfor -%}
</p>
<p>{{ form.submit() }}</p>
{% for mesg in get_flashed_messages() -%}
<p>{{ mesg }}</p>
{% endfor -%}
</form>
</div>
</div>
{% endblock -%}