mirror of
				https://github.com/tiyn/container-critique.git
				synced 2025-10-30 18:51:21 +01:00 
			
		
		
		
	search: added full-text search and docstrings
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,3 @@ | |||||||
| data | data | ||||||
| data.db | data.db | ||||||
|  | indexdir | ||||||
|   | |||||||
							
								
								
									
										137
									
								
								src/app.py
									
									
									
									
									
								
							
							
						
						
									
										137
									
								
								src/app.py
									
									
									
									
									
								
							| @@ -1,14 +1,17 @@ | |||||||
| from flask import Flask, flash, render_template, redirect, abort, url_for | from flask import Flask, flash, render_template, redirect, abort, url_for, \ | ||||||
|  |     request | ||||||
| from flask_ckeditor import CKEditor | from flask_ckeditor import CKEditor | ||||||
| from flask_login import current_user, login_user, LoginManager, logout_user, \ | from flask_login import current_user, login_user, LoginManager, logout_user, \ | ||||||
|     login_required |     login_required | ||||||
| from flask_wtf import CSRFProtect | from flask_wtf import CSRFProtect | ||||||
| import os | import os | ||||||
|  | from werkzeug.exceptions import HTTPException | ||||||
|  |  | ||||||
| import config | import config | ||||||
| from content import rating_to_star | from content import rating_to_star | ||||||
| from database import Database | from database import Database | ||||||
| from forms import LoginForm, RegisterForm, WriteForm | from forms import LoginForm, RegisterForm, WriteForm, SearchForm | ||||||
|  | from search import create_search_index, ft_search | ||||||
|  |  | ||||||
|  |  | ||||||
| app = Flask(__name__) | app = Flask(__name__) | ||||||
| @@ -23,20 +26,55 @@ login = LoginManager(app) | |||||||
| login.login_view = "login" | login.login_view = "login" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @login.user_loader | ||||||
|  | def load_user(ident): | ||||||
|  |     """ | ||||||
|  |     Returns a user by id. | ||||||
|  |  | ||||||
|  |     Parameters: | ||||||
|  |     ident(int): id of the user | ||||||
|  |  | ||||||
|  |     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 | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.context_processor | @app.context_processor | ||||||
| def inject_title(): | def inject_title(): | ||||||
|     return dict(title=config.TITLE, style=config.STYLE, \ |     """ | ||||||
|                 description=config.DESCRIPTION, \ |     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) |                 registration=config.ALLOW_REGISTRATION, r_to_star=rating_to_star) | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.errorhandler(404) | @app.errorhandler(HTTPException) | ||||||
| def page_not_found(e): | def page_not_found(e): | ||||||
|     return render_template("error.html", errorcode="404"), 404 |     """ | ||||||
|  |     Renders the error pages. | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |     str: html formatted Error page | ||||||
|  |     """ | ||||||
|  |     return render_template("error.html", errorcode=str(e.code) + " " + e.name), e.code | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/") | @app.route("/") | ||||||
| def index(): | def index(): | ||||||
|  |     """ | ||||||
|  |     Renders the index page. | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |     str: html formatted index page | ||||||
|  |     """ | ||||||
|     entries = db.get_entries() |     entries = db.get_entries() | ||||||
|     entries.reverse() |     entries.reverse() | ||||||
|     return render_template("index.html", entries=entries) |     return render_template("index.html", entries=entries) | ||||||
| @@ -44,6 +82,12 @@ def index(): | |||||||
|  |  | ||||||
| @app.route("/archive") | @app.route("/archive") | ||||||
| def archive(): | def archive(): | ||||||
|  |     """ | ||||||
|  |     Renders the archive page. | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |     str: html formatted archive page | ||||||
|  |     """ | ||||||
|     entries = db.get_entries() |     entries = db.get_entries() | ||||||
|     entries.sort(key=lambda y: y.item.name) |     entries.sort(key=lambda y: y.item.name) | ||||||
|     entries.reverse() |     entries.reverse() | ||||||
| @@ -54,7 +98,16 @@ def archive(): | |||||||
|  |  | ||||||
| @app.route("/user/<name>") | @app.route("/user/<name>") | ||||||
| def user(name): | def user(name): | ||||||
|     entries = db.get_entries_by_user(name) |     """ | ||||||
|  |     Renders the user page of a specific user. | ||||||
|  |  | ||||||
|  |     Parameters: | ||||||
|  |     name(str): name of the user | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |     str: html formatted user page | ||||||
|  |     """ | ||||||
|  |     entries = db.get_entries_by_username(name) | ||||||
|     entries.sort(key=lambda y: y.item.name) |     entries.sort(key=lambda y: y.item.name) | ||||||
|     entries.reverse() |     entries.reverse() | ||||||
|     entries.sort(key=lambda y: y.item.date) |     entries.sort(key=lambda y: y.item.date) | ||||||
| @@ -66,6 +119,15 @@ def user(name): | |||||||
|  |  | ||||||
| @app.route("/entry/<ident>") | @app.route("/entry/<ident>") | ||||||
| def entry(ident): | def entry(ident): | ||||||
|  |     """ | ||||||
|  |     Renders the entry page of a specific entry. | ||||||
|  |  | ||||||
|  |     Parameters: | ||||||
|  |     ident(str): ident of the entry | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |     str: html formatted entry page | ||||||
|  |     """ | ||||||
|     entry = db.get_entry_by_id(ident) |     entry = db.get_entry_by_id(ident) | ||||||
|     if entry is not None: |     if entry is not None: | ||||||
|         return render_template("standalone.html", entry=entry) |         return render_template("standalone.html", entry=entry) | ||||||
| @@ -74,22 +136,43 @@ def entry(ident): | |||||||
|  |  | ||||||
| @app.route("/feed") | @app.route("/feed") | ||||||
| def feed(): | def feed(): | ||||||
|  |     """ | ||||||
|  |     Renders the rss feed of a the feed. | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |     str: xml formatted feed | ||||||
|  |     """ | ||||||
|     entries = db.get_entries() |     entries = db.get_entries() | ||||||
|     entries.reverse() |     entries.reverse() | ||||||
|     rss_xml = render_template("rss.xml", entries=entries) |     rss_xml = render_template("rss.xml", entries=entries) | ||||||
|     return rss_xml |     return rss_xml | ||||||
|  |  | ||||||
|  |  | ||||||
| @login.user_loader | @app.route("/search", methods=["GET", "POST"]) | ||||||
| def load_user(ident): | def search(): | ||||||
|     user = db.get_user_by_id(ident) |     """ | ||||||
|     if user is not None: |     Renders the search page. | ||||||
|         return user |  | ||||||
|     return None |     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 | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/login", methods=["GET", "POST"]) | @app.route("/login", methods=["GET", "POST"]) | ||||||
| def login(): | 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")) |         return redirect(url_for("index")) | ||||||
|     form = LoginForm() |     form = LoginForm() | ||||||
| @@ -107,12 +190,25 @@ def login(): | |||||||
|  |  | ||||||
| @app.route('/logout') | @app.route('/logout') | ||||||
| def logout(): | def logout(): | ||||||
|  |     """ | ||||||
|  |     Logs out the current user. | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |     str: html formatted index page. | ||||||
|  |     """ | ||||||
|     logout_user() |     logout_user() | ||||||
|     return redirect(url_for("index")) |     return redirect(url_for("index")) | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/register", methods=["GET", "POST"]) | @app.route("/register", methods=["GET", "POST"]) | ||||||
| def register(): | 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")) |         return redirect(url_for("index")) | ||||||
|     form = RegisterForm() |     form = RegisterForm() | ||||||
| @@ -132,12 +228,20 @@ def register(): | |||||||
| @app.route("/write_entry", methods=["GET", "POST"]) | @app.route("/write_entry", methods=["GET", "POST"]) | ||||||
| @login_required | @login_required | ||||||
| def write_entry(): | 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: |     if not current_user.is_authenticated: | ||||||
|         return redirect(url_for("index")) |         return redirect(url_for("index")) | ||||||
|     form = WriteForm() |     form = WriteForm() | ||||||
|     if form.validate_on_submit(): |     if form.validate_on_submit(): | ||||||
|         db.insert_entry(form.name.data, form.date.data, |         db.insert_entry(form.name.data, form.date.data, | ||||||
|                         form.text.data, form.rating.data, current_user.id) |                         form.text.data, form.rating.data, current_user.id) | ||||||
|  |         create_search_index() | ||||||
|         return redirect(url_for("index")) |         return redirect(url_for("index")) | ||||||
|     return render_template("write.html", form=form) |     return render_template("write.html", form=form) | ||||||
|  |  | ||||||
| @@ -145,10 +249,17 @@ def write_entry(): | |||||||
| @app.route("/delete_entry/<ident>", methods=["GET", "POST"]) | @app.route("/delete_entry/<ident>", methods=["GET", "POST"]) | ||||||
| @login_required | @login_required | ||||||
| def delete_entry(ident): | def delete_entry(ident): | ||||||
|  |     """ | ||||||
|  |     Deletes an existing entry. | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |     str: html formatted index entry page. | ||||||
|  |     """ | ||||||
|     if not current_user.is_authenticated: |     if not current_user.is_authenticated: | ||||||
|         return redirect(url_for("index")) |         return redirect(url_for("index")) | ||||||
|     if current_user.id == db.get_entry_by_id(ident).user.id: |     if current_user.id == db.get_entry_by_id(ident).user.id: | ||||||
|         db.delete_entry(ident) |         db.delete_entry(ident) | ||||||
|  |         create_search_index() | ||||||
|     return redirect(url_for("index")) |     return redirect(url_for("index")) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,3 +12,9 @@ STYLE = "dark" | |||||||
|  |  | ||||||
| # Allow new registrations | # Allow new registrations | ||||||
| ALLOW_REGISTRATION = True | ALLOW_REGISTRATION = True | ||||||
|  |  | ||||||
|  | # Location of the search-indexing directory | ||||||
|  | INDEX_DIR = "indexdir" | ||||||
|  |  | ||||||
|  | # Number of search results to show | ||||||
|  | SEARCH_NUMBER = 10 | ||||||
|   | |||||||
| @@ -8,10 +8,10 @@ def rating_to_star(rating): | |||||||
|     Creates a string with stars based on the rating. |     Creates a string with stars based on the rating. | ||||||
|  |  | ||||||
|     Parameters: |     Parameters: | ||||||
|     rating: rating with minimum of 0 and maximum of 100. |     rating: rating with minimum of 0 and maximum of 100 | ||||||
|  |  | ||||||
|     Returns: |     Returns: | ||||||
|     string: unicode-formatted star-rating string. |     string: unicode-formatted star-rating string | ||||||
|     """ |     """ | ||||||
|     res = u"\u272D"*int(rating/20) |     res = u"\u272D"*int(rating/20) | ||||||
|     length = len(res) |     length = len(res) | ||||||
|   | |||||||
							
								
								
									
										259
									
								
								src/database.py
									
									
									
									
									
								
							
							
						
						
									
										259
									
								
								src/database.py
									
									
									
									
									
								
							| @@ -5,6 +5,17 @@ from werkzeug.security import generate_password_hash, check_password_hash | |||||||
|  |  | ||||||
|  |  | ||||||
| class User(): | class User(): | ||||||
|  |     """ | ||||||
|  |     A class to represent a user. | ||||||
|  |  | ||||||
|  |     Attributes: | ||||||
|  |     name (str): name of the user | ||||||
|  |     id (int): id of the user | ||||||
|  |     is_active (bool): check if the user is active | ||||||
|  |     is_authenticated (bool): check if the user is logged in | ||||||
|  |     is_anonymous (bool): check if the user is is_anonymous | ||||||
|  |     pass_hash (str): hash of the users password | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     def __init__(self, name, pass_hash=None): |     def __init__(self, name, pass_hash=None): | ||||||
|         self.name = name |         self.name = name | ||||||
| @@ -15,19 +26,61 @@ class User(): | |||||||
|         self.pass_hash = pass_hash |         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: | ||||||
|  |         password (str): password to add to the 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: | ||||||
|  |         id (str): id to add to the 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. | ||||||
|  |  | ||||||
|  |         Parameters: | ||||||
|  |         password (str): password to compare the users password to | ||||||
|  |  | ||||||
|  |         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(): | class Item(): | ||||||
|  |     """ | ||||||
|  |     A class to represent an item. | ||||||
|  |  | ||||||
|  |     Attributes: | ||||||
|  |     name (str): name of the item | ||||||
|  |     id (int): id of the item | ||||||
|  |     date (str): date the item was created | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     def __init__(self, name, date): |     def __init__(self, name, date): | ||||||
|         self.name = name |         self.name = name | ||||||
| @@ -35,29 +88,73 @@ class Item(): | |||||||
|         self.id = None |         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(): | class Entry(): | ||||||
|  |     """ | ||||||
|  |     A class to represent an entry. | ||||||
|  |  | ||||||
|     def __init__(self, text, rating, reviewed): |     Attributes: | ||||||
|  |     text (str): text of the entry | ||||||
|  |     rating (int): rating of the item | ||||||
|  |     date (str): date the entry was created | ||||||
|  |     item (Item): item that is referenced by the entry | ||||||
|  |     user (User): user that authored the entry | ||||||
|  |     id (int): id of the item | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, text, rating, date): | ||||||
|         self.text = text |         self.text = text | ||||||
|         self.rating = rating |         self.rating = rating | ||||||
|         self.reviewed = reviewed |         self.date = date | ||||||
|         self.item = None |         self.item = None | ||||||
|         self.user = 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: | class Database: | ||||||
|  |     """ | ||||||
|  |     A class to represent an entry. | ||||||
|  |  | ||||||
|  |     Attributes: | ||||||
|  |     USER_TABLE_FILE (str): name of the user table | ||||||
|  |     ENTRY_TABLE_FILE (str): name of the entry table | ||||||
|  |     ITEM_TABLE_FILE (str): name of the item table | ||||||
|  |     DB_DIR(PathLike): path that leads to the directory containing the database | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self.USER_TABLE_FILE = 'USERS' |         self.USER_TABLE_FILE = 'USERS' | ||||||
| @@ -70,12 +167,17 @@ class Database: | |||||||
|         """ |         """ | ||||||
|         Connect to an existing database instance based on the object |         Connect to an existing database instance based on the object | ||||||
|         attributes. |         attributes. | ||||||
|  |  | ||||||
|  |         Return: | ||||||
|  |         Connection: connection to the database | ||||||
|         """ |         """ | ||||||
|         path = os.path.join(self.DB_DIR, "data.db") |         path = os.path.join(self.DB_DIR, "data.db") | ||||||
|         return sqlite3.connect(path) |         return sqlite3.connect(path) | ||||||
|  |  | ||||||
|     def setup_db(self): |     def setup_db(self): | ||||||
|         """Creates a database with tables.""" |         """ | ||||||
|  |         Creates a database with the needed tables if it doesn't already exits. | ||||||
|  |         """ | ||||||
|         db = self.connect() |         db = self.connect() | ||||||
|         crs = db.cursor() |         crs = db.cursor() | ||||||
|         query = "CREATE TABLE IF NOT EXISTS " + self.USER_TABLE_FILE + \ |         query = "CREATE TABLE IF NOT EXISTS " + self.USER_TABLE_FILE + \ | ||||||
| @@ -95,11 +197,21 @@ class Database: | |||||||
|             "text TEXT NOT NULL," + \ |             "text TEXT NOT NULL," + \ | ||||||
|             "rating INTEGER NOT NULL," +\ |             "rating INTEGER NOT NULL," +\ | ||||||
|             "user_id INTEGER REFERENCES " + self.USER_TABLE_FILE + "(id),"\ |             "user_id INTEGER REFERENCES " + self.USER_TABLE_FILE + "(id),"\ | ||||||
|             "reviewed CHAR(10) NOT NULL)" |             "date CHAR(10) NOT NULL)" | ||||||
|         crs.execute(query) |         crs.execute(query) | ||||||
|         db.commit() |         db.commit() | ||||||
|  |  | ||||||
|     def insert_user(self, username, password): |     def insert_user(self, username, password): | ||||||
|  |         """ | ||||||
|  |         Insert a row in the user table. | ||||||
|  |  | ||||||
|  |         Parameters: | ||||||
|  |         username (str): name of the user to add | ||||||
|  |         password (str): password of the user to add | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |         int: number of the line the row was added, None if it wasn't successful | ||||||
|  |         """ | ||||||
|         pass_hash = generate_password_hash(password) |         pass_hash = generate_password_hash(password) | ||||||
|         if self.get_user_by_name(username) is None and pass_hash is not None: |         if self.get_user_by_name(username) is None and pass_hash is not None: | ||||||
|             db = self.connect() |             db = self.connect() | ||||||
| @@ -113,6 +225,19 @@ class Database: | |||||||
|         return None |         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: | ||||||
|  |         name (str): name of the entry to add | ||||||
|  |         date (str): date of the entry to add | ||||||
|  |         text (str): text of the entry to add | ||||||
|  |         rating (str): rating of the entry to add | ||||||
|  |         user_id (int): id of the user referenced by the entry to add | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |         int: number of the line the row was added | ||||||
|  |         """ | ||||||
|         db = self.connect() |         db = self.connect() | ||||||
|         crs = db.cursor() |         crs = db.cursor() | ||||||
|         query = "INSERT OR IGNORE INTO " + self.ITEM_TABLE_FILE + \ |         query = "INSERT OR IGNORE INTO " + self.ITEM_TABLE_FILE + \ | ||||||
| @@ -122,15 +247,24 @@ class Database: | |||||||
|             " WHERE name = ? AND date = ?" |             " WHERE name = ? AND date = ?" | ||||||
|         crs.execute(query, (name, date)) |         crs.execute(query, (name, date)) | ||||||
|         item_id = crs.fetchone()[0] |         item_id = crs.fetchone()[0] | ||||||
|         reviewed = dt.today().strftime('%Y-%m-%d') |         date = dt.today().strftime('%Y-%m-%d') | ||||||
|         query = "INSERT INTO " + self.ENTRY_TABLE_FILE + \ |         query = "INSERT INTO " + self.ENTRY_TABLE_FILE + \ | ||||||
|             "(`item_id`, `text`, `rating`, `user_id`, `reviewed`)" + \ |             "(`item_id`, `text`, `rating`, `user_id`, `date`)" + \ | ||||||
|             "VALUES (?, ?, ?, ?, ?)" |             "VALUES (?, ?, ?, ?, ?)" | ||||||
|         crs.execute(query, (item_id, text, rating, user_id, reviewed)) |         crs.execute(query, (item_id, text, rating, user_id, date)) | ||||||
|         db.commit() |         db.commit() | ||||||
|         return crs.lastrowid |         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: | ||||||
|  |         ident (int): id of the entry to remove | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |         int: number of the line the row was removed from | ||||||
|  |         """ | ||||||
|         db = self.connect() |         db = self.connect() | ||||||
|         crs = db.cursor() |         crs = db.cursor() | ||||||
|         query = "DELETE FROM " + self.ENTRY_TABLE_FILE + " WHERE id = ?" |         query = "DELETE FROM " + self.ENTRY_TABLE_FILE + " WHERE id = ?" | ||||||
| @@ -139,16 +273,31 @@ class Database: | |||||||
|         return crs.lastrowid |         return crs.lastrowid | ||||||
|  |  | ||||||
|     def get_entries(self): |     def get_entries(self): | ||||||
|  |         """ | ||||||
|  |         Return all the entries stored in the database. | ||||||
|  |  | ||||||
|  |         Return: | ||||||
|  |         List(Entry): list of entries in database | ||||||
|  |         """ | ||||||
|         db = self.connect() |         db = self.connect() | ||||||
|         crs = db.cursor() |         crs = db.cursor() | ||||||
|         query = "SELECT * FROM " + self.ENTRY_TABLE_FILE |         query = "SELECT * FROM " + self.ENTRY_TABLE_FILE | ||||||
|         crs.execute(query) |         crs.execute(query) | ||||||
|         res = [] |         res = [] | ||||||
|         for item in crs.fetchall(): |         for item in crs.fetchall(): | ||||||
|             res.append(self.db_to_entry(*item)) |             res.append(self.entry_from_db(*item)) | ||||||
|         return res |         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: | ||||||
|  |         ident (int): id of the entry to return | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |         Entry: entry that matched the given id | ||||||
|  |         """ | ||||||
|         db = self.connect() |         db = self.connect() | ||||||
|         crs = db.cursor() |         crs = db.cursor() | ||||||
|         query = "SELECT * FROM " + self.ENTRY_TABLE_FILE + " WHERE id = ?" |         query = "SELECT * FROM " + self.ENTRY_TABLE_FILE + " WHERE id = ?" | ||||||
| @@ -157,21 +306,39 @@ class Database: | |||||||
|         if fetched is None: |         if fetched is None: | ||||||
|             return None |             return None | ||||||
|         else: |         else: | ||||||
|             return self.db_to_entry(*fetched) |             return self.entry_from_db(*fetched) | ||||||
|  |  | ||||||
|     def get_entries_by_user(self, name): |     def get_entries_by_username(self, username): | ||||||
|  |         """ | ||||||
|  |         Return a entries stored in the database based on the entries name. | ||||||
|  |  | ||||||
|  |         Parameters: | ||||||
|  |         username (str): name of the entries to return | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |         List(Entry): entries that matched the given name | ||||||
|  |         """ | ||||||
|         db = self.connect() |         db = self.connect() | ||||||
|         crs = db.cursor() |         crs = db.cursor() | ||||||
|         query = "SELECT * FROM " + self.ENTRY_TABLE_FILE + \ |         query = "SELECT * FROM " + self.ENTRY_TABLE_FILE + \ | ||||||
|                 " WHERE user_id = (SELECT id FROM " + self.USER_TABLE_FILE + \ |                 " WHERE user_id = (SELECT id FROM " + self.USER_TABLE_FILE + \ | ||||||
|                 " WHERE name = ?)" |                 " WHERE name = ?)" | ||||||
|         crs.execute(query, (name, )) |         crs.execute(query, (username, )) | ||||||
|         res = [] |         res = [] | ||||||
|         for item in crs.fetchall(): |         for item in crs.fetchall(): | ||||||
|             res.append(self.db_to_entry(*item)) |             res.append(self.entry_from_db(*item)) | ||||||
|         return res |         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: | ||||||
|  |         ident (int): id of the item to return | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |         Item: item that matched the given id | ||||||
|  |         """ | ||||||
|         db = self.connect() |         db = self.connect() | ||||||
|         crs = db.cursor() |         crs = db.cursor() | ||||||
|         query = "SELECT * FROM " + self.ITEM_TABLE_FILE + " WHERE id = ?" |         query = "SELECT * FROM " + self.ITEM_TABLE_FILE + " WHERE id = ?" | ||||||
| @@ -180,9 +347,18 @@ class Database: | |||||||
|         if fetched is None: |         if fetched is None: | ||||||
|             return None |             return None | ||||||
|         else: |         else: | ||||||
|             return self.db_to_item(*fetched) |             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: | ||||||
|  |         ident (int): id of the user to return | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |         Item: user that matched the given id | ||||||
|  |         """ | ||||||
|         db = self.connect() |         db = self.connect() | ||||||
|         crs = db.cursor() |         crs = db.cursor() | ||||||
|         query = "SELECT * FROM " + self.USER_TABLE_FILE + " WHERE id = ?" |         query = "SELECT * FROM " + self.USER_TABLE_FILE + " WHERE id = ?" | ||||||
| @@ -191,9 +367,18 @@ class Database: | |||||||
|         if fetched is None: |         if fetched is None: | ||||||
|             return None |             return None | ||||||
|         else: |         else: | ||||||
|             return self.db_to_user(*fetched) |             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: | ||||||
|  |         name (str): name of the user to return | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |         Entry: user that matched the given name | ||||||
|  |         """ | ||||||
|         db = self.connect() |         db = self.connect() | ||||||
|         crs = db.cursor() |         crs = db.cursor() | ||||||
|         query = "SELECT * FROM " + self.USER_TABLE_FILE + " WHERE name = ?" |         query = "SELECT * FROM " + self.USER_TABLE_FILE + " WHERE name = ?" | ||||||
| @@ -202,20 +387,56 @@ class Database: | |||||||
|         if fetched is None: |         if fetched is None: | ||||||
|             return None |             return None | ||||||
|         else: |         else: | ||||||
|             return self.db_to_user(*fetched) |             return self.user_from_db(*fetched) | ||||||
|  |  | ||||||
|     def db_to_user(self, ident, name, pass_hash): |     def user_from_db(self, ident, name, pass_hash): | ||||||
|  |         """ | ||||||
|  |         Return a user from given database parameters. | ||||||
|  |  | ||||||
|  |         Parameters: | ||||||
|  |         ident: id of the user | ||||||
|  |         name: text of the user | ||||||
|  |         pass_hash: password hash of the user | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |         User: user element with given variables | ||||||
|  |         """ | ||||||
|         user = User(name, pass_hash) |         user = User(name, pass_hash) | ||||||
|         user.set_id(ident) |         user.set_id(ident) | ||||||
|         return user |         return user | ||||||
|  |  | ||||||
|     def db_to_item(self, ident, name, date): |     def item_from_db(self, ident, name, date): | ||||||
|  |         """ | ||||||
|  |         Return an item from given database parameters. | ||||||
|  |  | ||||||
|  |         Parameters: | ||||||
|  |         ident: id of the item | ||||||
|  |         name: text of the item | ||||||
|  |         date: date of the day the item was created | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |         Item: entry element with given variables | ||||||
|  |         """ | ||||||
|         item = Item(name, date) |         item = Item(name, date) | ||||||
|         item.set_id(ident) |         item.set_id(ident) | ||||||
|         return item |         return item | ||||||
|  |  | ||||||
|     def db_to_entry(self, ident, item_id, text, rating, user_id, reviewed): |     def entry_from_db(self, ident, item_id, text, rating, user_id, date): | ||||||
|         entry = Entry(text, rating, reviewed) |         """ | ||||||
|  |         Return an entry from given database parameters. | ||||||
|  |  | ||||||
|  |         Parameters: | ||||||
|  |         ident: id of the entry | ||||||
|  |         item_id: id of the referenced item | ||||||
|  |         text: text of the entry | ||||||
|  |         rating: rating of the entry | ||||||
|  |         user_id: id of the user that authored the entry | ||||||
|  |         date: date of the day the entry was written | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |         Entry: entry element with given variables | ||||||
|  |         """ | ||||||
|  |         entry = Entry(text, rating, date) | ||||||
|         entry.set_id(ident) |         entry.set_id(ident) | ||||||
|         entry.set_item(self.get_item_by_id(item_id)) |         entry.set_item(self.get_item_by_id(item_id)) | ||||||
|         entry.set_user(self.get_user_by_id(user_id)) |         entry.set_user(self.get_user_by_id(user_id)) | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								src/forms.py
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								src/forms.py
									
									
									
									
									
								
							| @@ -1,13 +1,16 @@ | |||||||
| from datetime import date | from datetime import date | ||||||
| from flask_ckeditor import CKEditorField | from flask_ckeditor import CKEditorField | ||||||
| from flask_wtf import FlaskForm | from flask_wtf import FlaskForm | ||||||
| from wtforms import StringField, PasswordField, SubmitField | from wtforms import StringField, PasswordField, SubmitField, TextField | ||||||
| from wtforms.fields.html5 import IntegerField | from wtforms.fields.html5 import IntegerField | ||||||
| from wtforms.validators import DataRequired, EqualTo, InputRequired, \ | from wtforms.validators import DataRequired, EqualTo, InputRequired, \ | ||||||
|     NumberRange, ValidationError, Length |     NumberRange, ValidationError, Length | ||||||
|  |  | ||||||
|  |  | ||||||
| class LoginForm(FlaskForm): | class LoginForm(FlaskForm): | ||||||
|  |     """ | ||||||
|  |     A Class for the Form that is used while logging in. | ||||||
|  |     """ | ||||||
|     username = StringField("Username", validators=[DataRequired(), |     username = StringField("Username", validators=[DataRequired(), | ||||||
|                                                    Length(min=4, max=32)]) |                                                    Length(min=4, max=32)]) | ||||||
|     password = PasswordField("Password", validators=[DataRequired(), |     password = PasswordField("Password", validators=[DataRequired(), | ||||||
| @@ -16,6 +19,9 @@ class LoginForm(FlaskForm): | |||||||
|  |  | ||||||
|  |  | ||||||
| class RegisterForm(FlaskForm): | class RegisterForm(FlaskForm): | ||||||
|  |     """ | ||||||
|  |     A Class for the Form that is used while registering. | ||||||
|  |     """ | ||||||
|     username = StringField("Username", validators=[DataRequired(), |     username = StringField("Username", validators=[DataRequired(), | ||||||
|                                                    Length(min=4, max=32)]) |                                                    Length(min=4, max=32)]) | ||||||
|     password = PasswordField("Password", validators=[DataRequired(), |     password = PasswordField("Password", validators=[DataRequired(), | ||||||
| @@ -25,7 +31,19 @@ class RegisterForm(FlaskForm): | |||||||
|     submit = SubmitField("Register") |     submit = SubmitField("Register") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SearchForm(FlaskForm): | ||||||
|  |     """ | ||||||
|  |     A Class for the Form that is used while searching. | ||||||
|  |     """ | ||||||
|  |     query_str = TextField( | ||||||
|  |         "Query", [DataRequired("Please enter the search term")]) | ||||||
|  |     submit = SubmitField("Search") | ||||||
|  |  | ||||||
|  |  | ||||||
| class WriteForm(FlaskForm): | class WriteForm(FlaskForm): | ||||||
|  |     """ | ||||||
|  |     A Class for the Form that is used while writing a new entry. | ||||||
|  |     """ | ||||||
|     name = StringField("Name", validators=[DataRequired(), |     name = StringField("Name", validators=[DataRequired(), | ||||||
|                                            Length(min=2, max=64)]) |                                            Length(min=2, max=64)]) | ||||||
|     date = IntegerField("Release Year", default=date.today().year, validators=[ |     date = IntegerField("Release Year", default=date.today().year, validators=[ | ||||||
| @@ -37,5 +55,17 @@ class WriteForm(FlaskForm): | |||||||
|     submit = SubmitField("Publish") |     submit = SubmitField("Publish") | ||||||
|  |  | ||||||
|     def validate_text(self, text): |     def validate_text(self, text): | ||||||
|  |         """ | ||||||
|  |         Validate a given input for html level one headers. | ||||||
|  |  | ||||||
|  |         Parameters: | ||||||
|  |         text (str): text to validate | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |         None | ||||||
|  |  | ||||||
|  |         Raises: | ||||||
|  |         ValidatenError: if the text contains a first level html tag | ||||||
|  |         """ | ||||||
|         if "<h1>" in text.data or "</h1>" in text.data: |         if "<h1>" in text.data or "</h1>" in text.data: | ||||||
|             raise ValidationError("Headings on level 1 are not permitted.") |             raise ValidationError("Headings on level 1 are not permitted.") | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								src/indexdir/.gitkeep
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/indexdir/.gitkeep
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										80
									
								
								src/search.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/search.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | import os | ||||||
|  | import re | ||||||
|  |  | ||||||
|  | from whoosh import scoring | ||||||
|  | from whoosh.index import create_in, open_dir | ||||||
|  | from whoosh.fields import Schema, TEXT, ID | ||||||
|  | from whoosh.qparser import QueryParser | ||||||
|  |  | ||||||
|  | import config | ||||||
|  | from database import Database | ||||||
|  |  | ||||||
|  | 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: | ||||||
|  |     text (str): text to clean | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |     str: text without html tags | ||||||
|  |     """ | ||||||
|  |     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() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def ft_search_times(query_str, number): | ||||||
|  |     """ | ||||||
|  |     Search for a given term and returns a specific amount of results. | ||||||
|  |  | ||||||
|  |     Parameters: | ||||||
|  |     query_str (str): term to search for | ||||||
|  |     number (int): number of results to return | ||||||
|  |  | ||||||
|  |     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 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def ft_search(query_str): | ||||||
|  |     """ | ||||||
|  |     Search for a given term and show the predefined amount of results. | ||||||
|  |  | ||||||
|  |     Parameters: | ||||||
|  |     query_str (str): term to search for | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |     List(Entry): list of entries that matched the search | ||||||
|  |     """ | ||||||
|  |     return ft_search_times(query_str, config.SEARCH_NUMBER) | ||||||
| @@ -27,7 +27,7 @@ | |||||||
|             {% set ns.open_li = True -%} |             {% set ns.open_li = True -%} | ||||||
|             {% endif -%} |             {% endif -%} | ||||||
|                 <a href="{{ url_for('entry', ident=entry.id) }}"> |                 <a href="{{ url_for('entry', ident=entry.id) }}"> | ||||||
|                     {{ entry.reviewed }} {{ r_to_star(entry.rating) }} by {{ entry.user.name }} |                     {{ entry.date }} {{ r_to_star(entry.rating) }} by {{ entry.user.name }} | ||||||
|                 </a> |                 </a> | ||||||
|             {% set ns.prev_item_date = entry.item.date -%} |             {% set ns.prev_item_date = entry.item.date -%} | ||||||
|             {% set ns.prev_item_id = entry.item.id -%} |             {% set ns.prev_item_id = entry.item.id -%} | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ | |||||||
|                     rated {{ entry.rating }}/100 by |                     rated {{ entry.rating }}/100 by | ||||||
|                     <a href="{{ url_for('user', name=entry.user.name) }}"> |                     <a href="{{ url_for('user', name=entry.user.name) }}"> | ||||||
|                         {{ entry.user.name }} |                         {{ entry.user.name }} | ||||||
|                     </a> on {{ entry.reviewed }} |                     </a> on {{ entry.date }} | ||||||
|                 </small><br> |                 </small><br> | ||||||
|                 {% autoescape off -%} |                 {% autoescape off -%} | ||||||
|                 {{ entry.text }} |                 {{ entry.text }} | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ | |||||||
|             {{ url_for("index", _anchor=entry.id, _external=True) }} |             {{ url_for("index", _anchor=entry.id, _external=True) }} | ||||||
|         </guid> |         </guid> | ||||||
|         <pubDate> |         <pubDate> | ||||||
|             {{ entry.reviewed }} |             {{ entry.date }} | ||||||
|         </pubDate> |         </pubDate> | ||||||
|         <description> |         <description> | ||||||
|             {% autoescape off -%} |             {% autoescape off -%} | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								src/templates/search.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/templates/search.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | {% extends "template.html" -%} | ||||||
|  |  | ||||||
|  | {% 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> | ||||||
|  | </div> | ||||||
|  | {% endblock -%} | ||||||
| @@ -14,7 +14,7 @@ | |||||||
|                 </a> |                 </a> | ||||||
|                 on |                 on | ||||||
|                 <a href="{{ url_for('index', _anchor=entry.id) }}"> |                 <a href="{{ url_for('index', _anchor=entry.id) }}"> | ||||||
|                     {{ entry.reviewed }} |                     {{ entry.date }} | ||||||
|                 </a> |                 </a> | ||||||
|             </small><br> |             </small><br> | ||||||
|             {% if current_user.id == entry.user.id -%} |             {% if current_user.id == entry.user.id -%} | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| {% set navigation_bar = [ | {% set navigation_bar = [ | ||||||
|     (url_for("index"), 'index', 'Blog'), |     (url_for("index"), "index", "Blog"), | ||||||
|     (url_for("archive"), 'archive', 'Archive') |     (url_for("archive"), "archive", "Archive"), | ||||||
|  |     (url_for("search"), "search", "Search") | ||||||
| ] -%} | ] -%} | ||||||
|  |  | ||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ | |||||||
|             {% set ns.open_li = True -%} |             {% set ns.open_li = True -%} | ||||||
|             {% endif -%} |             {% endif -%} | ||||||
|                     <a href="{{ url_for('entry', ident=entry.id) }}"> |                     <a href="{{ url_for('entry', ident=entry.id) }}"> | ||||||
|                         {{ entry.reviewed }} {{ r_to_star(entry.rating) }} |                         {{ entry.date }} {{ r_to_star(entry.rating) }} | ||||||
|                     </a> |                     </a> | ||||||
|             {% set ns.prev_item_date = entry.item.date -%} |             {% set ns.prev_item_date = entry.item.date -%} | ||||||
|             {% set ns.prev_item_id = entry.item.id -%} |             {% set ns.prev_item_id = entry.item.id -%} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user