diff --git a/dev.py b/dev.py new file mode 100644 index 0000000..9c84965 --- /dev/null +++ b/dev.py @@ -0,0 +1,8 @@ +"""Expose a development application.""" + +from xppl.app import create_app +from xppl.socketio import socketio + +app = create_app('xppl.config.Development') + +socketio.run(app) diff --git a/run.py b/run.py deleted file mode 100755 index 73c639f..0000000 --- a/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from xppl import app, socketio - -socketio.run(app, host='0.0.0.0', port=8080) diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..4197b2c --- /dev/null +++ b/wsgi.py @@ -0,0 +1,5 @@ +"""Expose a production application.""" + +from xppl.app import create_app + +app = create_app('xppl.config.Production') diff --git a/xppl/__init__.py b/xppl/__init__.py index c69d88b..e69de29 100755 --- a/xppl/__init__.py +++ b/xppl/__init__.py @@ -1,37 +0,0 @@ -import os -from os import environ - -from dotenv import find_dotenv, load_dotenv -from flask import Flask -from flask_socketio import SocketIO -from flask_sqlalchemy import SQLAlchemy -from sqlalchemy.dialects import registry - -registry.register( - 'rqlite.pyrqlite', - 'sqlalchemy_rqlite.pyrqlite', - 'dialect' -) - -BASEDIR = os.path.abspath(os.path.dirname(__file__)) -UPLOAD_FOLDER = os.path.join(BASEDIR, 'uploads') -UPLOAD_FOLDER_COVER = os.path.join(BASEDIR, 'cover') - -load_dotenv(find_dotenv()) - -app = Flask(__name__) - -app.config['SECRET_KEY'] = 'super secret key' -app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER -app.config['SQLALCHEMY_DATABASE_URI'] = 'rqlite+pyrqlite://localhost:4001/' -app.config['DEBUG'] = True -app.config['PORT'] = 80 - -db = SQLAlchemy(app) -light = not os.path.isdir(UPLOAD_FOLDER) -DOMAIN = environ.get('DOMAIN') -socketio = SocketIO(app) - -app.config.from_object(__name__) - -from xppl import views # noqa diff --git a/xppl/app.py b/xppl/app.py new file mode 100644 index 0000000..23b7cf8 --- /dev/null +++ b/xppl/app.py @@ -0,0 +1,57 @@ +"""Main application factory.""" + +from dotenv import find_dotenv, load_dotenv +from flask import Flask +from sqlalchemy.dialects import registry + + +def create_app(config): + app = Flask(__name__.split('.')[0]) + + app.config.from_object(config) + + configure_dotenv() + configure_pyrqlite() + configure_socketio(app) + configure_sqlalchemy(app) + + with app.app_context(): + from xppl import views # noqa + + return app + + +def configure_pyrqlite(): + """Configure PyRQLite.""" + registry.register( + 'rqlite.pyrqlite', + 'sqlalchemy_rqlite.pyrqlite', + 'dialect' + ) + + +def configure_dotenv(): + """Configure the environment.""" + load_dotenv(find_dotenv()) + + +def configure_sqlalchemy(app): + """Configure SQLAlchemy.""" + from xppl.database import db + from xppl.models import ( # noqa + Book, Author, Instance, + Potential, UserIns, Chat, + Stack, + ) + + db.init_app(app) + + with app.app_context(): + db.create_all() + + +def configure_socketio(app): + """Configure SocketIO.""" + from xppl.socketio import socketio + + socketio.init_app(app) diff --git a/xppl/config.py b/xppl/config.py new file mode 100644 index 0000000..85324cb --- /dev/null +++ b/xppl/config.py @@ -0,0 +1,32 @@ +"""The Application settings.""" + +from os.path import abspath, dirname, isdir, join + + +class Base(): + """The base configuration.""" + BASEDIR = abspath(dirname(__file__)) + + UPLOAD_FOLDER_COVER = join(BASEDIR, 'cover') + UPLOAD_FOLDER = join(BASEDIR, 'uploads') + LIGHT = isdir(UPLOAD_FOLDER) + + DEBUG = False + TESTING = False + + SQLALCHEMY_TRACK_MODIFICATIONS = False + SQLALCHEMY_DATABASE_URI = 'rqlite+pyrqlite://localhost:4001/' + + +class Production(Base): + """The production configuration.""" + ENV = 'production' + DOMAIN = 'https://book.vvvvvvaria.org' + + +class Development(Base): + """The development configuration.""" + ENV = 'development' + DEBUG = True + TESTING = True + DOMAIN = 'http://localhost' diff --git a/xppl/database.py b/xppl/database.py new file mode 100644 index 0000000..787a01c --- /dev/null +++ b/xppl/database.py @@ -0,0 +1,5 @@ +"""Database initialisation.""" + +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() diff --git a/xppl/forms.py b/xppl/forms.py index 4feeb3b..dd6c979 100755 --- a/xppl/forms.py +++ b/xppl/forms.py @@ -1,4 +1,3 @@ -from xppl.models import Author from flask_wtf import FlaskForm from wtforms import FieldList, FileField from wtforms import Form as NoCsrfForm @@ -6,6 +5,7 @@ from wtforms import validators from wtforms.fields import FormField, SelectField, StringField, SubmitField from wtforms.fields.html5 import DecimalRangeField from wtforms.validators import DataRequired, InputRequired +from xppl.models import Author # - - - Forms - - - diff --git a/xppl/models.py b/xppl/models.py index 1d8f5b0..97feb3b 100755 --- a/xppl/models.py +++ b/xppl/models.py @@ -1,8 +1,8 @@ import datetime -from xppl import db from marshmallow import Schema, ValidationError, fields from sqlalchemy import Column, DateTime +from xppl.database import db authors = db.Table( diff --git a/xppl/socketio.py b/xppl/socketio.py new file mode 100644 index 0000000..8d9acee --- /dev/null +++ b/xppl/socketio.py @@ -0,0 +1,5 @@ +"""SocketIO initialisation.""" + +from flask_socketio import SocketIO + +socketio = SocketIO() diff --git a/xppl/views.py b/xppl/views.py index c805e22..342cf56 100755 --- a/xppl/views.py +++ b/xppl/views.py @@ -8,7 +8,8 @@ from functools import wraps from urllib.parse import quote as urlquote import autocomplete -from xppl import DOMAIN, app, db, light, socketio +from xppl.database import db +from xppl.socketio import socketio from xppl.cover import get_cover from xppl.extractText import extract_text from xppl.forms import ( @@ -24,7 +25,7 @@ from xppl.models import ( from flask import ( Response, abort, flash, jsonify, redirect, render_template, request, send_from_directory, - url_for + url_for, current_app as app ) from flask_socketio import emit from flask_weasyprint import HTML, render_pdf @@ -32,7 +33,6 @@ from sqlalchemy.inspection import inspect from sqlalchemy.sql.expression import func from werkzeug.utils import secure_filename -# import sqlite3 ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'epub', 'chm', 'mobi']) author_schema = AuthorSchema() @@ -46,8 +46,9 @@ chats_schema = ChatSchema(many=True) def allowed_file(filename): - return '.' in filename and \ - filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + extension = filename.rsplit('.', 1)[1].lower() + return '.' in filename and extension in ALLOWED_EXTENSIONS + ### # Routing for your application. ### @@ -56,7 +57,7 @@ def allowed_file(filename): def check_light(func): @wraps(func) def decorated_function(*args, **kwargs): - if not light: + if not app.config['LIGHT']: return func(*args, **kwargs) else: flash("Your account has expired. Update your billing info.") @@ -67,8 +68,6 @@ def check_light(func): @app.route('/', methods=['POST', 'GET']) def home(): - print("/////////////") - print(light) chat_messages = db.session.query(Chat).all() username = 'librarian' server = request.host @@ -79,13 +78,13 @@ def home(): return render_template( 'home.html', - domain=DOMAIN, + domain=app.config['DOMAIN'], chat=chat_messages, channel=1, username=username, client=client, server=server, - light=light + light=app.config['LIGHT'], ) @@ -97,7 +96,7 @@ def hello(name): @app.route('/about/') def about(): """Render the website's about page.""" - return render_template('about.html', light=light) + return render_template('about.html', light=app.config['LIGHT']) @app.route('/uploads/') @@ -124,7 +123,7 @@ def annotations(): 'annotations.html', annot=annot, books=books, - light=light + light=app.config['LIGHT'] ) @@ -133,7 +132,11 @@ def annotations(): def annotations_pdf(): annot = get_annotations() # Make a PDF straight from HTML in a string. - html = render_template(('annotations.html'), annot=annot, light=light) + html = render_template( + 'annotations.html', + annot=annot, + light=app.config['LIGHT'], + ) return render_pdf(HTML(string=html)) @@ -179,7 +182,6 @@ def scape(): if request.method == 'POST': data = request.form book = Book.query.get(data['id']) - print(book.scapeX) book.scapeX = data['x'] book.scapeY = data['y'] db.session.commit() @@ -207,7 +209,11 @@ def scape_new(): @app.route('/books_grid') def show_books_grid(): books = db.session.query(Book).all() - return render_template('show_books_grid.html', books=books, light=light) + return render_template( + 'show_books_grid.html', + books=books, + light=app.config['LIGHT'] + ) @app.route('/books/') @@ -236,7 +242,11 @@ def show_book_by_id(id): db.session.add(user_info) db.session.commit() if not book: - return render_template('red_link.html', id=id, light=light) + return render_template( + 'red_link.html', + id=id, + light=app.config['LIGHT'] + ) else: return render_template( 'show_book_detail.html', @@ -247,7 +257,7 @@ def show_book_by_id(id): name=name, annot=annot, res=res, - light=light + light=app.config['LIGHT'] ) @@ -359,7 +369,7 @@ def edit_book_by_id(id): 'edit_book_detail.html', book=book_to_edit, form=user_form, - light=light + light=app.config['LIGHT'] ) @@ -505,7 +515,7 @@ def add_book(): books_potential=books_potential, earliest=earliest, latest=latest, - light=light + light=app.config['LIGHT'] ) @@ -530,7 +540,7 @@ def show_author_by_id(id): return render_template( 'show_author_detail.html', author=author, - light=light + light=app.config['LIGHT'] ) @@ -545,7 +555,11 @@ def edit_author_by_id(id): @app.route('/stacks') def show_stacks(): stacks = db.session.query(Stack).all() - return render_template('show_stacks.html', stacks=stacks, light=light) + return render_template( + 'show_stacks.html', + stacks=stacks, + light=app.config['LIGHT'] + ) @app.route('/stacks/add_stack', methods=['POST', 'GET']) @@ -569,7 +583,7 @@ def add_stack(): 'add_stack.html', stacks=stacks, form=form, - light=light + light=app.config['LIGHT'] ) @@ -589,13 +603,13 @@ def show_stack_by_id(id, is_tab=False): return render_template( 'show_stack_detail.html', stack=stack, - light=light + light=app.config['LIGHT'] ) else: return render_template( 'show_stack_detail_tab.html', stack=stack, - light=light + light=app.config['LIGHT'] ) @@ -629,7 +643,7 @@ def edit_stack_by_id(id): 'edit_stack_detail.html', stack=stack, form=form, - light=light + light=app.config['LIGHT'] ) @@ -672,7 +686,7 @@ def show_instances(): return render_template( 'show_instances.html', instances=instances, - light=light + light=app.config['LIGHT'] ) @@ -685,10 +699,18 @@ def remove_from_stack(bookid, stackid): book = Book.query.get(bookid) stack = Stack.query.get(stackid) if book not in stack.books: - return render_template('show_book_detail.html', book=book, light=light) + return render_template( + 'show_book_detail.html', + book=book, + light=app.config['LIGHT'] + ) stack.books.remove(book) db.session.commit() - return render_template('show_book_detail.html', book=book, light=light) + return render_template( + 'show_book_detail.html', + book=book, + light=app.config['LIGHT'] + ) # Search @@ -710,7 +732,7 @@ def show_books(): 'show_books_grid.html', books=books, form=search, - light=light + light=app.config['LIGHT'] ) if search.listview.data: @@ -720,7 +742,7 @@ def show_books(): 'show_books.html', books=books, form=search, - light=light + light=app.config['LIGHT'] ) if request.method == 'POST': @@ -750,7 +772,7 @@ def show_books(): 'show_books.html', books=books, form=search, - light=light + light=app.config['LIGHT'] ) @@ -823,7 +845,7 @@ def search_results(searchtype, query, viewby): form=upload_form, title=query, books=books, - light=light + light=app.config['LIGHT'] ) count = results.count() @@ -844,7 +866,7 @@ def search_results(searchtype, query, viewby): count=count, whole=whole, percentage=percentage, - light=light + light=app.config['LIGHT'] ) if search.grid.data: @@ -859,7 +881,7 @@ def search_results(searchtype, query, viewby): count=count, whole=whole, percentage=percentage, - light=light + light=app.config['LIGHT'] ) if request.method == 'POST': @@ -907,7 +929,7 @@ def search_results(searchtype, query, viewby): count=count, whole=whole, percentage=percentage, - light=light + light=app.config['LIGHT'] ) else: @@ -921,7 +943,7 @@ def search_results(searchtype, query, viewby): count=count, whole=whole, percentage=percentage, - light=light + light=app.config['LIGHT'] ) @@ -981,7 +1003,7 @@ def add_to_stack(id): stacks=stacks, book=book, add_form=add_form, - light=light + light=app.config['LIGHT'] ) else: stack = Stack.query.get(int(add_form.select_stack.data)) @@ -991,7 +1013,7 @@ def add_to_stack(id): return render_template( 'show_stack_detail.html', stack=stack, - light=light + light=app.config['LIGHT'] ) @@ -1023,7 +1045,6 @@ def export_csv(): for book in Book.query.order_by("title"): row = {} for col in fieldnames: - print(getattr(book, col)) row[col] = getattr(book, col) for col in referred_classes: subattr = [] @@ -1128,7 +1149,7 @@ def import_csv(): return render_template( 'import_csv.html', numberadded=numberadded, - light=light + light=app.config['LIGHT'] ) @@ -1140,7 +1161,6 @@ def empty_catalogue(): if (str(table) == "books" or str(table) == "authors" or str(table) == "books_authors"): - print('Clear table %s' % table) db.session.execute(table.delete()) db.create_all() db.session.commit() @@ -1193,7 +1213,7 @@ def add_header(response): @app.errorhandler(404) def page_not_found(error): """Custom 404 page.""" - return render_template('404.html', light=light), 404 + return render_template('404.html', light=app.config['LIGHT']), 404 # SOCKET for the chat