mirror of
https://github.com/tiyn/container-critique.git
synced 2025-04-01 15:47:48 +02:00
search: added full-text search and docstrings
This commit is contained in:
parent
a6e1735cac
commit
284a597d4a
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
data
|
||||
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_login import current_user, login_user, LoginManager, logout_user, \
|
||||
login_required
|
||||
from flask_wtf import CSRFProtect
|
||||
import os
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
import config
|
||||
from content import rating_to_star
|
||||
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__)
|
||||
@ -23,20 +26,55 @@ login = LoginManager(app)
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
@app.errorhandler(HTTPException)
|
||||
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("/")
|
||||
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)
|
||||
@ -44,6 +82,12 @@ def index():
|
||||
|
||||
@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()
|
||||
@ -54,7 +98,16 @@ def archive():
|
||||
|
||||
@app.route("/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.reverse()
|
||||
entries.sort(key=lambda y: y.item.date)
|
||||
@ -66,6 +119,15 @@ def user(name):
|
||||
|
||||
@app.route("/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)
|
||||
if entry is not None:
|
||||
return render_template("standalone.html", entry=entry)
|
||||
@ -74,22 +136,43 @@ def entry(ident):
|
||||
|
||||
@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
|
||||
|
||||
|
||||
@login.user_loader
|
||||
def load_user(ident):
|
||||
user = db.get_user_by_id(ident)
|
||||
if user is not None:
|
||||
return user
|
||||
return None
|
||||
@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
|
||||
|
||||
|
||||
@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:
|
||||
return redirect(url_for("index"))
|
||||
form = LoginForm()
|
||||
@ -107,12 +190,25 @@ def login():
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
"""
|
||||
Logs out the current user.
|
||||
|
||||
Returns:
|
||||
str: html formatted index page.
|
||||
"""
|
||||
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:
|
||||
return redirect(url_for("index"))
|
||||
form = RegisterForm()
|
||||
@ -132,12 +228,20 @@ def register():
|
||||
@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)
|
||||
|
||||
@ -145,10 +249,17 @@ def write_entry():
|
||||
@app.route("/delete_entry/<ident>", methods=["GET", "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()
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
|
@ -12,3 +12,9 @@ STYLE = "dark"
|
||||
|
||||
# Allow new registrations
|
||||
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.
|
||||
|
||||
Parameters:
|
||||
rating: rating with minimum of 0 and maximum of 100.
|
||||
rating: rating with minimum of 0 and maximum of 100
|
||||
|
||||
Returns:
|
||||
string: unicode-formatted star-rating string.
|
||||
string: unicode-formatted star-rating string
|
||||
"""
|
||||
res = u"\u272D"*int(rating/20)
|
||||
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():
|
||||
"""
|
||||
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):
|
||||
self.name = name
|
||||
@ -15,19 +26,61 @@ class User():
|
||||
self.pass_hash = pass_hash
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
def get_id(self):
|
||||
"""
|
||||
Get the id of the user.
|
||||
|
||||
Returns:
|
||||
int: id of the user
|
||||
"""
|
||||
return self.id
|
||||
|
||||
|
||||
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):
|
||||
self.name = name
|
||||
@ -35,29 +88,73 @@ class Item():
|
||||
self.id = None
|
||||
|
||||
def set_id(self, ident):
|
||||
"""
|
||||
Set the id of the item.
|
||||
|
||||
Returns:
|
||||
int: id of the item
|
||||
"""
|
||||
self.id = ident
|
||||
|
||||
|
||||
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.rating = rating
|
||||
self.reviewed = reviewed
|
||||
self.date = date
|
||||
self.item = None
|
||||
self.user = None
|
||||
|
||||
def set_id(self, ident):
|
||||
"""
|
||||
Set the id of the entry.
|
||||
|
||||
Parameters:
|
||||
ident(int): id of the entry
|
||||
"""
|
||||
self.id = ident
|
||||
|
||||
def set_item(self, item):
|
||||
"""
|
||||
Set the item of the entry.
|
||||
|
||||
Parameters:
|
||||
item(Item): item of the entry
|
||||
"""
|
||||
self.item = item
|
||||
|
||||
def set_user(self, user):
|
||||
"""
|
||||
Set the user of the entry.
|
||||
|
||||
Parameters:
|
||||
user(User): user of the entry
|
||||
"""
|
||||
self.user = user
|
||||
|
||||
|
||||
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):
|
||||
self.USER_TABLE_FILE = 'USERS'
|
||||
@ -70,12 +167,17 @@ class Database:
|
||||
"""
|
||||
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)
|
||||
|
||||
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()
|
||||
crs = db.cursor()
|
||||
query = "CREATE TABLE IF NOT EXISTS " + self.USER_TABLE_FILE + \
|
||||
@ -95,11 +197,21 @@ class Database:
|
||||
"text TEXT NOT NULL," + \
|
||||
"rating INTEGER NOT NULL," +\
|
||||
"user_id INTEGER REFERENCES " + self.USER_TABLE_FILE + "(id),"\
|
||||
"reviewed CHAR(10) NOT NULL)"
|
||||
"date CHAR(10) NOT NULL)"
|
||||
crs.execute(query)
|
||||
db.commit()
|
||||
|
||||
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)
|
||||
if self.get_user_by_name(username) is None and pass_hash is not None:
|
||||
db = self.connect()
|
||||
@ -113,6 +225,19 @@ class Database:
|
||||
return 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()
|
||||
crs = db.cursor()
|
||||
query = "INSERT OR IGNORE INTO " + self.ITEM_TABLE_FILE + \
|
||||
@ -122,15 +247,24 @@ class Database:
|
||||
" WHERE name = ? AND date = ?"
|
||||
crs.execute(query, (name, date))
|
||||
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 + \
|
||||
"(`item_id`, `text`, `rating`, `user_id`, `reviewed`)" + \
|
||||
"(`item_id`, `text`, `rating`, `user_id`, `date`)" + \
|
||||
"VALUES (?, ?, ?, ?, ?)"
|
||||
crs.execute(query, (item_id, text, rating, user_id, reviewed))
|
||||
crs.execute(query, (item_id, text, rating, user_id, date))
|
||||
db.commit()
|
||||
return crs.lastrowid
|
||||
|
||||
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()
|
||||
crs = db.cursor()
|
||||
query = "DELETE FROM " + self.ENTRY_TABLE_FILE + " WHERE id = ?"
|
||||
@ -139,16 +273,31 @@ class Database:
|
||||
return crs.lastrowid
|
||||
|
||||
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.db_to_entry(*item))
|
||||
res.append(self.entry_from_db(*item))
|
||||
return res
|
||||
|
||||
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()
|
||||
crs = db.cursor()
|
||||
query = "SELECT * FROM " + self.ENTRY_TABLE_FILE + " WHERE id = ?"
|
||||
@ -157,21 +306,39 @@ class Database:
|
||||
if fetched is None:
|
||||
return None
|
||||
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()
|
||||
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, (name, ))
|
||||
crs.execute(query, (username, ))
|
||||
res = []
|
||||
for item in crs.fetchall():
|
||||
res.append(self.db_to_entry(*item))
|
||||
res.append(self.entry_from_db(*item))
|
||||
return res
|
||||
|
||||
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()
|
||||
crs = db.cursor()
|
||||
query = "SELECT * FROM " + self.ITEM_TABLE_FILE + " WHERE id = ?"
|
||||
@ -180,9 +347,18 @@ class Database:
|
||||
if fetched is None:
|
||||
return None
|
||||
else:
|
||||
return self.db_to_item(*fetched)
|
||||
return self.item_from_db(*fetched)
|
||||
|
||||
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()
|
||||
crs = db.cursor()
|
||||
query = "SELECT * FROM " + self.USER_TABLE_FILE + " WHERE id = ?"
|
||||
@ -191,9 +367,18 @@ class Database:
|
||||
if fetched is None:
|
||||
return None
|
||||
else:
|
||||
return self.db_to_user(*fetched)
|
||||
return self.user_from_db(*fetched)
|
||||
|
||||
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()
|
||||
crs = db.cursor()
|
||||
query = "SELECT * FROM " + self.USER_TABLE_FILE + " WHERE name = ?"
|
||||
@ -202,20 +387,56 @@ class Database:
|
||||
if fetched is None:
|
||||
return None
|
||||
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.set_id(ident)
|
||||
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.set_id(ident)
|
||||
return item
|
||||
|
||||
def db_to_entry(self, ident, item_id, text, rating, user_id, reviewed):
|
||||
entry = Entry(text, rating, reviewed)
|
||||
def entry_from_db(self, ident, item_id, text, rating, user_id, date):
|
||||
"""
|
||||
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_item(self.get_item_by_id(item_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 flask_ckeditor import CKEditorField
|
||||
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.validators import DataRequired, EqualTo, InputRequired, \
|
||||
NumberRange, ValidationError, Length
|
||||
|
||||
|
||||
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(),
|
||||
@ -16,6 +19,9 @@ class LoginForm(FlaskForm):
|
||||
|
||||
|
||||
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(),
|
||||
@ -25,7 +31,19 @@ class RegisterForm(FlaskForm):
|
||||
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):
|
||||
"""
|
||||
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=[
|
||||
@ -37,5 +55,17 @@ class WriteForm(FlaskForm):
|
||||
submit = SubmitField("Publish")
|
||||
|
||||
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:
|
||||
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 -%}
|
||||
{% endif -%}
|
||||
<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>
|
||||
{% set ns.prev_item_date = entry.item.date -%}
|
||||
{% set ns.prev_item_id = entry.item.id -%}
|
||||
|
@ -15,7 +15,7 @@
|
||||
rated {{ entry.rating }}/100 by
|
||||
<a href="{{ url_for('user', name=entry.user.name) }}">
|
||||
{{ entry.user.name }}
|
||||
</a> on {{ entry.reviewed }}
|
||||
</a> on {{ entry.date }}
|
||||
</small><br>
|
||||
{% autoescape off -%}
|
||||
{{ entry.text }}
|
||||
|
@ -16,7 +16,7 @@
|
||||
{{ url_for("index", _anchor=entry.id, _external=True) }}
|
||||
</guid>
|
||||
<pubDate>
|
||||
{{ entry.reviewed }}
|
||||
{{ entry.date }}
|
||||
</pubDate>
|
||||
<description>
|
||||
{% 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>
|
||||
on
|
||||
<a href="{{ url_for('index', _anchor=entry.id) }}">
|
||||
{{ entry.reviewed }}
|
||||
{{ entry.date }}
|
||||
</a>
|
||||
</small><br>
|
||||
{% if current_user.id == entry.user.id -%}
|
||||
|
@ -1,6 +1,7 @@
|
||||
{% set navigation_bar = [
|
||||
(url_for("index"), 'index', 'Blog'),
|
||||
(url_for("archive"), 'archive', 'Archive')
|
||||
(url_for("index"), "index", "Blog"),
|
||||
(url_for("archive"), "archive", "Archive"),
|
||||
(url_for("search"), "search", "Search")
|
||||
] -%}
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
@ -27,7 +27,7 @@
|
||||
{% set ns.open_li = True -%}
|
||||
{% endif -%}
|
||||
<a href="{{ url_for('entry', ident=entry.id) }}">
|
||||
{{ entry.reviewed }} {{ r_to_star(entry.rating) }}
|
||||
{{ entry.date }} {{ r_to_star(entry.rating) }}
|
||||
</a>
|
||||
{% set ns.prev_item_date = entry.item.date -%}
|
||||
{% set ns.prev_item_id = entry.item.id -%}
|
||||
|
Loading…
x
Reference in New Issue
Block a user