diff --git a/app/__init__.py b/app/__init__.py index 24660c2..b2dc543 100755 --- a/app/__init__.py +++ b/app/__init__.py @@ -8,6 +8,8 @@ import os import click from werkzeug.utils import secure_filename from sqlalchemy.dialects import registry +import flask_whooshalchemyplus + registry.register("rqlite.pyrqlite", "sqlalchemy_rqlite.pyrqlite", "dialect") 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['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') db = SQLAlchemy(app) @@ -33,3 +38,6 @@ socketio = SocketIO(app) app.config.from_object(__name__) from app import views + +flask_whooshalchemyplus.init_app(app) # initialize + diff --git a/app/forms.py b/app/forms.py index 47cd54d..e3b74cc 100755 --- a/app/forms.py +++ b/app/forms.py @@ -32,7 +32,8 @@ class ChatForm(FlaskForm): class SearchForm(FlaskForm): - choices = [('Title', 'Title'), + choices = [('All', 'All'), + ('Title', 'Title'), ('Category', 'Category')] select = SelectField('', choices=choices) search = StringField('', validators=[InputRequired()]) diff --git a/app/models.py b/app/models.py index 43e94d1..0b281fd 100755 --- a/app/models.py +++ b/app/models.py @@ -2,6 +2,8 @@ from app import db from marshmallow import Schema, fields, ValidationError, pre_load import datetime from sqlalchemy import Column, Integer, DateTime +import flask_whooshalchemyplus + authors = db.Table('books_authors', 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): __tablename__ = 'books' + __searchable__ = ['title', 'category', 'fileformat'] # these fields will be indexed by whoosh id = db.Column(db.Integer, primary_key = True) title = db.Column(db.String(255)) diff --git a/app/static/css/style.css b/app/static/css/style.css index 83127e1..023daa4 100755 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -16,7 +16,7 @@ font-family: "Archivo Narrow"; } p{ - font-size: 22px; + font-size: 18px; } @@ -36,7 +36,7 @@ float: left; color: black; text-align: center; padding: 14px 16px; - font-size: 22px; + font-size: 18px; text-decoration: none; } @@ -58,7 +58,7 @@ padding: 0px 8px; } .lead{ - font-size: 22px; + font-size: 18px; } .library_table{ @@ -70,7 +70,7 @@ border-spacing:0; /* Removes the cell spacing via CSS */ } .library_table th{ -font-size: 21px; +font-size: 20px; cursor: pointer; } @@ -87,7 +87,7 @@ background-color: #E8E8E8!important; .library_table .title_col{ - font-size: 21px; + font-size: 20px; padding-left: 10px; } @@ -106,7 +106,7 @@ background-color: #fafafa; } #title_xppl{ -font-size: 55px; +font-size: 46px; cursor: pointer; } @@ -159,7 +159,7 @@ color: #fafafa; font-family:'Courier New'; font-weight:100; font-size:12px; - margin-top: 100px; + margin-top: 200px; } .footer pre{ @@ -192,7 +192,7 @@ font-size: 12px; top:0; left:0; position: fixed; - font-size: 25px; + font-size: 20px; background-color: yellow; } diff --git a/app/static/js/app.js b/app/static/js/app.js index 653b5e6..5236b0f 100755 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -134,8 +134,8 @@ $( document ).ready(function() { duplicated: true, pauseOnHover: true, duration: 7000, - speed: 8, - gap: 100, + speed: 30, + gap: 200, startVisible:true }); console.log(response) diff --git a/app/views.py b/app/views.py index 77d0cca..ba0c01c 100755 --- a/app/views.py +++ b/app/views.py @@ -72,18 +72,6 @@ def uploaded_file_cover(filename): return send_from_directory(app.config['UPLOAD_FOLDER_COVER'], 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']) def get_updates(): userin = UserIns.query.filter_by(title="lastViewed").first() @@ -283,45 +271,43 @@ def show_stack_by_id(id): ## search -@app.route('/search/titles//', methods=['POST', 'GET']) -def search_results(query): - search = SearchForm(request.form) - random_order=Book.query.order_by(func.random()).limit(10) - results=Book.query.filter(Book.title.contains(query)).all() - - if not results: - upload_form = UploadForm(title= query, author='') - return render_template('red_link.html', form=upload_form, title=query) +## search +@app.route('/books', methods= ['POST','GET']) +def show_books(): + books = db.session.query(Book).all() + search = SearchForm(request.form) 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 redirect((url_for('search_results', searchtype=search.select.data, query=search.search.data))) - return render_template('results.html', form=search, books=results, books_all=random_order, query=query) + return render_template('show_books.html', books=books, form=search) -@app.route('/search/cat//', methods=['POST', 'GET']) -def search_cat(query): +@app.route('/search///', methods=['POST', 'GET']) +def search_results(searchtype, query): search = SearchForm(request.form) random_order=Book.query.order_by(func.random()).limit(10) - results=Book.query.filter(Book.category.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: - upload_form = UploadForm(category=query) - return render_template('red_link.html', form=upload_form, category=query) + upload_form = UploadForm(title= query, author='') + return render_template('red_link.html', form=upload_form, title=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 redirect((url_for('search_results', searchtype=search.select.data, query=search.search.data))) + + return render_template('results.html', form=search, books=results, books_all=random_order, searchtype=search.select.data, query=query) - return render_template('results.html', form=search, books=results, books_all=random_order, query=query) ### # The API diff --git a/rebuild.py b/rebuild.py new file mode 100644 index 0000000..3726790 --- /dev/null +++ b/rebuild.py @@ -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) \ No newline at end of file diff --git a/whoosh/Book/MAIN_WRITELOCK b/whoosh/Book/MAIN_WRITELOCK new file mode 100755 index 0000000..e69de29 diff --git a/whoosh/Book/MAIN_p6r8oedtat7ay25v.seg b/whoosh/Book/MAIN_p6r8oedtat7ay25v.seg new file mode 100644 index 0000000..6105580 Binary files /dev/null and b/whoosh/Book/MAIN_p6r8oedtat7ay25v.seg differ diff --git a/whoosh/Book/_MAIN_1.toc b/whoosh/Book/_MAIN_1.toc new file mode 100644 index 0000000..0e87a0a Binary files /dev/null and b/whoosh/Book/_MAIN_1.toc differ