added search all functionality with flask-whooshalchemyplus
This commit is contained in:
parent
85a1298e63
commit
b30104eb46
@ -8,6 +8,8 @@ import os
|
|||||||
import click
|
import click
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from sqlalchemy.dialects import registry
|
from sqlalchemy.dialects import registry
|
||||||
|
import flask_whooshalchemyplus
|
||||||
|
|
||||||
registry.register("rqlite.pyrqlite", "sqlalchemy_rqlite.pyrqlite", "dialect")
|
registry.register("rqlite.pyrqlite", "sqlalchemy_rqlite.pyrqlite", "dialect")
|
||||||
|
|
||||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||||
@ -25,6 +27,9 @@ app.config['SQLALCHEMY_DATABASE_URI'] = 'rqlite+pyrqlite://localhost:4001/'
|
|||||||
app.config['DEBUG'] = True
|
app.config['DEBUG'] = True
|
||||||
app.config['PORT'] = 80
|
app.config['PORT'] = 80
|
||||||
|
|
||||||
|
# set the location for the whoosh index
|
||||||
|
app.config['WHOOSH_BASE'] = 'whoosh'
|
||||||
|
|
||||||
#app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'mydatabase.db')
|
#app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'mydatabase.db')
|
||||||
db = SQLAlchemy(app)
|
db = SQLAlchemy(app)
|
||||||
|
|
||||||
@ -33,3 +38,6 @@ socketio = SocketIO(app)
|
|||||||
|
|
||||||
app.config.from_object(__name__)
|
app.config.from_object(__name__)
|
||||||
from app import views
|
from app import views
|
||||||
|
|
||||||
|
flask_whooshalchemyplus.init_app(app) # initialize
|
||||||
|
|
||||||
|
@ -32,7 +32,8 @@ class ChatForm(FlaskForm):
|
|||||||
|
|
||||||
|
|
||||||
class SearchForm(FlaskForm):
|
class SearchForm(FlaskForm):
|
||||||
choices = [('Title', 'Title'),
|
choices = [('All', 'All'),
|
||||||
|
('Title', 'Title'),
|
||||||
('Category', 'Category')]
|
('Category', 'Category')]
|
||||||
select = SelectField('', choices=choices)
|
select = SelectField('', choices=choices)
|
||||||
search = StringField('', validators=[InputRequired()])
|
search = StringField('', validators=[InputRequired()])
|
||||||
|
@ -2,6 +2,8 @@ from app import db
|
|||||||
from marshmallow import Schema, fields, ValidationError, pre_load
|
from marshmallow import Schema, fields, ValidationError, pre_load
|
||||||
import datetime
|
import datetime
|
||||||
from sqlalchemy import Column, Integer, DateTime
|
from sqlalchemy import Column, Integer, DateTime
|
||||||
|
import flask_whooshalchemyplus
|
||||||
|
|
||||||
|
|
||||||
authors = db.Table('books_authors',
|
authors = db.Table('books_authors',
|
||||||
db.Column('book_id', db.Integer, db.ForeignKey('books.id'), primary_key=True),
|
db.Column('book_id', db.Integer, db.ForeignKey('books.id'), primary_key=True),
|
||||||
@ -15,6 +17,7 @@ stacks = db.Table('books_stacks',
|
|||||||
|
|
||||||
class Book(db.Model):
|
class Book(db.Model):
|
||||||
__tablename__ = 'books'
|
__tablename__ = 'books'
|
||||||
|
__searchable__ = ['title', 'category', 'fileformat'] # these fields will be indexed by whoosh
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key = True)
|
id = db.Column(db.Integer, primary_key = True)
|
||||||
title = db.Column(db.String(255))
|
title = db.Column(db.String(255))
|
||||||
|
@ -16,7 +16,7 @@ font-family: "Archivo Narrow";
|
|||||||
}
|
}
|
||||||
|
|
||||||
p{
|
p{
|
||||||
font-size: 22px;
|
font-size: 18px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ float: left;
|
|||||||
color: black;
|
color: black;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
font-size: 22px;
|
font-size: 18px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ padding: 0px 8px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.lead{
|
.lead{
|
||||||
font-size: 22px;
|
font-size: 18px;
|
||||||
|
|
||||||
}
|
}
|
||||||
.library_table{
|
.library_table{
|
||||||
@ -70,7 +70,7 @@ border-spacing:0; /* Removes the cell spacing via CSS */
|
|||||||
}
|
}
|
||||||
|
|
||||||
.library_table th{
|
.library_table th{
|
||||||
font-size: 21px;
|
font-size: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ background-color: #E8E8E8!important;
|
|||||||
|
|
||||||
|
|
||||||
.library_table .title_col{
|
.library_table .title_col{
|
||||||
font-size: 21px;
|
font-size: 20px;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ background-color: #fafafa;
|
|||||||
}
|
}
|
||||||
|
|
||||||
#title_xppl{
|
#title_xppl{
|
||||||
font-size: 55px;
|
font-size: 46px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -159,7 +159,7 @@ color: #fafafa;
|
|||||||
font-family:'Courier New';
|
font-family:'Courier New';
|
||||||
font-weight:100;
|
font-weight:100;
|
||||||
font-size:12px;
|
font-size:12px;
|
||||||
margin-top: 100px;
|
margin-top: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer pre{
|
.footer pre{
|
||||||
@ -192,7 +192,7 @@ font-size: 12px;
|
|||||||
top:0;
|
top:0;
|
||||||
left:0;
|
left:0;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
font-size: 25px;
|
font-size: 20px;
|
||||||
background-color: yellow;
|
background-color: yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,8 +134,8 @@ $( document ).ready(function() {
|
|||||||
duplicated: true,
|
duplicated: true,
|
||||||
pauseOnHover: true,
|
pauseOnHover: true,
|
||||||
duration: 7000,
|
duration: 7000,
|
||||||
speed: 8,
|
speed: 30,
|
||||||
gap: 100,
|
gap: 200,
|
||||||
startVisible:true
|
startVisible:true
|
||||||
});
|
});
|
||||||
console.log(response)
|
console.log(response)
|
||||||
|
62
app/views.py
62
app/views.py
@ -72,18 +72,6 @@ def uploaded_file_cover(filename):
|
|||||||
return send_from_directory(app.config['UPLOAD_FOLDER_COVER'],
|
return send_from_directory(app.config['UPLOAD_FOLDER_COVER'],
|
||||||
filename)
|
filename)
|
||||||
|
|
||||||
@app.route('/books', methods= ['POST','GET'])
|
|
||||||
def show_books():
|
|
||||||
books = db.session.query(Book).all()
|
|
||||||
search = SearchForm(request.form)
|
|
||||||
if request.method == 'POST':
|
|
||||||
if search.select.data == 'Title':
|
|
||||||
return redirect((url_for('search_results', query=search.search.data)))
|
|
||||||
if search.select.data == 'Category':
|
|
||||||
return redirect((url_for('search_cat', query=search.search.data)))
|
|
||||||
|
|
||||||
return render_template('show_books.html', books=books, form=search)
|
|
||||||
|
|
||||||
@app.route('/updates', methods=['POST', 'GET'])
|
@app.route('/updates', methods=['POST', 'GET'])
|
||||||
def get_updates():
|
def get_updates():
|
||||||
userin = UserIns.query.filter_by(title="lastViewed").first()
|
userin = UserIns.query.filter_by(title="lastViewed").first()
|
||||||
@ -283,12 +271,32 @@ def show_stack_by_id(id):
|
|||||||
|
|
||||||
## search
|
## search
|
||||||
|
|
||||||
@app.route('/search/titles/<query>/', methods=['POST', 'GET'])
|
## search
|
||||||
def search_results(query):
|
|
||||||
|
@app.route('/books', methods= ['POST','GET'])
|
||||||
|
def show_books():
|
||||||
|
books = db.session.query(Book).all()
|
||||||
|
search = SearchForm(request.form)
|
||||||
|
if request.method == 'POST':
|
||||||
|
return redirect((url_for('search_results', searchtype=search.select.data, query=search.search.data)))
|
||||||
|
|
||||||
|
return render_template('show_books.html', books=books, form=search)
|
||||||
|
|
||||||
|
@app.route('/search/<searchtype>/<query>/', methods=['POST', 'GET'])
|
||||||
|
def search_results(searchtype, query):
|
||||||
search = SearchForm(request.form)
|
search = SearchForm(request.form)
|
||||||
random_order=Book.query.order_by(func.random()).limit(10)
|
random_order=Book.query.order_by(func.random()).limit(10)
|
||||||
results=Book.query.filter(Book.title.contains(query)).all()
|
results=Book.query.filter(Book.title.contains(query)).all()
|
||||||
|
|
||||||
|
if searchtype == 'Title':
|
||||||
|
results=Book.query.filter(Book.title.contains(query)).all()
|
||||||
|
|
||||||
|
if searchtype == 'Category':
|
||||||
|
results=Book.query.filter(Book.category.contains(query)).all()
|
||||||
|
|
||||||
|
if searchtype== 'All':
|
||||||
|
results=Book.query.whoosh_search(query).all()
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
upload_form = UploadForm(title= query, author='')
|
upload_form = UploadForm(title= query, author='')
|
||||||
return render_template('red_link.html', form=upload_form, title=query)
|
return render_template('red_link.html', form=upload_form, title=query)
|
||||||
@ -296,32 +304,10 @@ def search_results(query):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
query = search.search.data
|
query = search.search.data
|
||||||
results = []
|
results = []
|
||||||
if search.select.data == 'Title':
|
return redirect((url_for('search_results', searchtype=search.select.data, query=search.search.data)))
|
||||||
return redirect((url_for('search_results', query=search.search.data)))
|
|
||||||
if search.select.data == 'Category':
|
|
||||||
return redirect((url_for('search_cat', query=search.search.data)))
|
|
||||||
|
|
||||||
return render_template('results.html', form=search, books=results, books_all=random_order, query=query)
|
return render_template('results.html', form=search, books=results, books_all=random_order, searchtype=search.select.data, query=query)
|
||||||
|
|
||||||
@app.route('/search/cat/<query>/', methods=['POST', 'GET'])
|
|
||||||
def search_cat(query):
|
|
||||||
search = SearchForm(request.form)
|
|
||||||
random_order=Book.query.order_by(func.random()).limit(10)
|
|
||||||
results=Book.query.filter(Book.category.contains(query)).all()
|
|
||||||
|
|
||||||
if not results:
|
|
||||||
upload_form = UploadForm(category=query)
|
|
||||||
return render_template('red_link.html', form=upload_form, category=query)
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
query = search.search.data
|
|
||||||
results = []
|
|
||||||
if search.select.data == 'Title':
|
|
||||||
return redirect((url_for('search_results', query=search.search.data)))
|
|
||||||
if search.select.data == 'Category':
|
|
||||||
return redirect((url_for('search_cat', query=search.search.data)))
|
|
||||||
|
|
||||||
return render_template('results.html', form=search, books=results, books_all=random_order, query=query)
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# The API
|
# The API
|
||||||
|
57
rebuild.py
Normal file
57
rebuild.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
|
||||||
|
import datetime
|
||||||
|
from app import app, models
|
||||||
|
import whoosh
|
||||||
|
import flask_whooshalchemyplus
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Rebuild all Whoosh search indices
|
||||||
|
Useful after manually importing data (side-stepping the SQLAlchemy ORM
|
||||||
|
and automatic Whoosh index updates)
|
||||||
|
If this is intended as a full rebuild, you should consider deleting the
|
||||||
|
Whoosh search database (as specified in app.config["WHOOSH_BASE"])
|
||||||
|
before running the rebuild. This will ensure that no old/stale
|
||||||
|
data is left in the search indices (this process doesn't delete removed
|
||||||
|
data, only recreated search entries for current data).
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
program_start = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
def log(message):
|
||||||
|
logtime = datetime.datetime.utcnow()
|
||||||
|
logdiff = logtime - program_start
|
||||||
|
print("{0} (+{1:.3f}): {2}".format(logtime.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
logdiff.total_seconds(),
|
||||||
|
message))
|
||||||
|
|
||||||
|
def rebuild_index(model):
|
||||||
|
"""Rebuild search index of Flask-SQLAlchemy model"""
|
||||||
|
log("Rebuilding {0} index...".format(model.__name__))
|
||||||
|
primary_field = model.pure_whoosh.primary_key_name
|
||||||
|
searchables = model.__searchable__
|
||||||
|
index_writer = flask_whooshalchemyplus.whoosh_index(app, model)
|
||||||
|
|
||||||
|
# Fetch all data
|
||||||
|
entries = model.query.all()
|
||||||
|
|
||||||
|
entry_count = 0
|
||||||
|
with index_writer.writer() as writer:
|
||||||
|
for entry in entries:
|
||||||
|
index_attrs = {}
|
||||||
|
for field in searchables:
|
||||||
|
index_attrs[field] = str(getattr(entry, field))
|
||||||
|
|
||||||
|
index_attrs[primary_field] = str(getattr(entry, primary_field))
|
||||||
|
writer.update_document(**index_attrs)
|
||||||
|
entry_count += 1
|
||||||
|
|
||||||
|
log("Rebuilt {0} {1} search index entries.".format(str(entry_count), model.__name__))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
model_list = [models.Book]
|
||||||
|
|
||||||
|
for model in model_list:
|
||||||
|
rebuild_index(model)
|
0
whoosh/Book/MAIN_WRITELOCK
Executable file
0
whoosh/Book/MAIN_WRITELOCK
Executable file
BIN
whoosh/Book/MAIN_p6r8oedtat7ay25v.seg
Normal file
BIN
whoosh/Book/MAIN_p6r8oedtat7ay25v.seg
Normal file
Binary file not shown.
BIN
whoosh/Book/_MAIN_1.toc
Normal file
BIN
whoosh/Book/_MAIN_1.toc
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user