diff --git a/.bzrignore b/.bzrignore index 6433309..3eec00e 100644 --- a/.bzrignore +++ b/.bzrignore @@ -3,3 +3,6 @@ dist *.pyc __pycache__ *.egg-info +config.ini +scribeengine.sqlite +.env diff --git a/README.rst b/README.rst index b2511b5..f99716b 100644 --- a/README.rst +++ b/README.rst @@ -10,19 +10,11 @@ Install ScribeEngine using ``pip``:: pip install ScribeEngine -Create a config file with these options set:: +Run the setup wizard to create a config file:: - [mail] - server = mail.example.com - port = 25 - use_tls = false - username = me - password = secret - - [sqlalchemy] - database_uri = sqlite:///scribeengine.sqlite + $ python -m scribeengine.config Run a local server:: - $ python -m scribeengine.application.run() + $ python -m scribeengine diff --git a/config.ini.default b/config.ini.default index 7ec8ee4..695454d 100644 --- a/config.ini.default +++ b/config.ini.default @@ -1,5 +1,9 @@ -[sqlalchemy] +[flask] +secret_key = changeme +[sqlalchemy] +database_uri = sqlite:///scribeengine.sqlite +track_modifications = false [mail] username = email@example.com @@ -13,3 +17,14 @@ use_tls = false [theme] paths = default = + +[user] +app_name = ScribeEngine +enable_email = true +enable_username = false +email_sender_email = noreply@example.com + +[site] +name = Example Blog +slogan = An example of a blog +about = This is an example of a blog using the ScribeEngine blogging engine diff --git a/devserver.py b/devserver.py deleted file mode 100644 index f2af6e6..0000000 --- a/devserver.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python3 -from scribeengine import application -application.run(host='0.0.0.0', port=8080, debug=True) diff --git a/requirements.txt b/requirements.txt index e7d1a10..c4d8b23 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,10 +3,11 @@ Flask-Admin Flask-Login Flask-Mail Flask-SQLAlchemy -Flask-Themes +Flask-Themes2 Flask-Uploads Flask-User Flask-WTF passlib py-bcrypt pycrypto +pytz diff --git a/scribeengine/__init__.py b/scribeengine/__init__.py index 426794f..6585efa 100644 --- a/scribeengine/__init__.py +++ b/scribeengine/__init__.py @@ -26,12 +26,22 @@ import os from flask import Flask from flask_mail import Mail -from flask_themes import setup_themes -from flask_user import SQLAlchemyAdapter, UserManager +from flask_themes2 import Themes, packaged_themes_loader, theme_paths_loader, load_themes_from +from flask_user import UserManager from scribeengine.config import read_config_from_file from scribeengine.db import db from scribeengine.models import User +from scribeengine.views.account import account +from scribeengine.views.blog import blog + + +def _scribeengine_themes_loader(app): + """ + Loads ScribeEngine's themes + """ + themes_path = os.path.join(os.path.dirname(__file__), 'themes') + return load_themes_from(themes_path) def create_app(config_file=None): @@ -43,19 +53,23 @@ def create_app(config_file=None): if not config_file: if os.environ.get('SCRIBEENGINE_CONFIG'): config_file = os.environ['SCRIBEENGINE_CONFIG'] - elif os.path.exists('sundaybuilder.conf'): - config_file = 'sundaybuilder.conf' + elif os.path.exists('config.ini'): + config_file = 'config.ini' if config_file: application.config.update(read_config_from_file(config_file)) # Set up mail, themes Mail(application) - setup_themes(application, app_identifier='ScribeEngine') + Themes(application, app_identifier='ScribeEngine', loaders=[ + _scribeengine_themes_loader, packaged_themes_loader, theme_paths_loader]) + print(application.root_path) + print(application.theme_manager.themes) # Set up database db.init_app(application) - db.create_all() + db.create_all(app=application) # Setup Flask-User - db_adapter = SQLAlchemyAdapter(db, User) # Register the User model - user_manager = UserManager(db_adapter, application) # Initialize Flask-User + UserManager(application, db, User) # Register all the blueprints + application.register_blueprint(blog) + application.register_blueprint(account) # Return the application object return application diff --git a/scribeengine/__main__.py b/scribeengine/__main__.py new file mode 100644 index 0000000..c61bf3f --- /dev/null +++ b/scribeengine/__main__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 +from scribeengine import create_app +create_app().run(host='0.0.0.0', port=9080, debug=True) diff --git a/scribeengine/themes.py b/scribeengine/helpers.py similarity index 78% rename from scribeengine/themes.py rename to scribeengine/helpers.py index 099f1cd..146f23b 100644 --- a/scribeengine/themes.py +++ b/scribeengine/helpers.py @@ -20,10 +20,21 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -The :mod:`~scribeengine.themes` module contains some theme helper methods +The :mod:`~scribeengine.helpers` module contains some theme helper methods """ from flask import current_app -from flask_themes import get_theme, render_theme_template +from flask_themes2 import get_theme, render_theme_template + +from scribeengine.models import Site + + +def get_site_details(): + """ + Returns an object with the details of the site + """ + return Site(current_app.config.get('SITE_NAME', 'Example Blog'), + current_app.config.get('SITE_SLOGAN', 'From the Firehose'), + current_app.config.get('SITE_ABOUT', '')) def get_current_theme(): @@ -38,4 +49,5 @@ def render(template, **context): """ Render a template, after selecting a theme """ + context.update({'site': get_site_details(), 'archives': []}) return render_theme_template(get_current_theme(), template, **context) diff --git a/scribeengine/models.py b/scribeengine/models.py index c1d9cc8..ee1186b 100644 --- a/scribeengine/models.py +++ b/scribeengine/models.py @@ -202,9 +202,9 @@ class User(Model, UserMixin): confirmed_at = Column(DateTime) active = Column('is_active', Boolean, nullable=False, server_default='0') - comments = relationship('Comment', backref='user'), - files = relationship('File', backref='user'), - posts = relationship('Post', backref='user'), + comments = relationship('Comment', backref='user') + files = relationship('File', backref='user') + posts = relationship('Post', backref='user') roles = relationship('Role', backref='users', secondary=roles_users) def has_permission(self, permission): @@ -239,3 +239,17 @@ class Variable(Model): key = Column(String(100), primary_key=True, index=True) value = Column(String(100), nullable=False) type = Column(String(10), default='string') + + +class Site(object): + """ + Generic model to hold the site-specfic data + """ + name = None + slogan = None + about = None + + def __init__(self, name, slogan=None, about=None): + self.name = name + self.slogan = slogan + self.about = about diff --git a/scribeengine/themes/quill/info.json b/scribeengine/themes/quill/info.json new file mode 100644 index 0000000..d0523ff --- /dev/null +++ b/scribeengine/themes/quill/info.json @@ -0,0 +1,12 @@ +{ + "application": "ScribeEngine", + "identifier": "quill", + "name": "Quill", + "author": "Raoul Snyman", + "description": "A basic theme built using Bootstrap 4 and based on Bootstrap 4's blog example", + "website": "https://scribeengine.org", + "license": "GPL3+", + "preview": "screenshot.png", + "doctype": "html5", + "version": "0.1" +} diff --git a/scribeengine/themes/quill/static/css/quill.css b/scribeengine/themes/quill/static/css/quill.css new file mode 100644 index 0000000..0891f82 --- /dev/null +++ b/scribeengine/themes/quill/static/css/quill.css @@ -0,0 +1,141 @@ +/* stylelint-disable selector-list-comma-newline-after */ + +.blog-header { + line-height: 1; + border-bottom: 1px solid #e5e5e5; +} + +.blog-header-logo { + font-family: "Playfair Display", Georgia, "Times New Roman", serif; + font-size: 2.25rem; +} + +.blog-header-logo:hover { + text-decoration: none; +} + +h1, h2, h3, h4, h5, h6 { + font-family: "Playfair Display", Georgia, "Times New Roman", serif; +} + +.display-4 { + font-size: 2.5rem; +} +@media (min-width: 768px) { + .display-4 { + font-size: 3rem; + } +} + +.nav-scroller { + position: relative; + z-index: 2; + height: 2.75rem; + overflow-y: hidden; +} + +.nav-scroller .nav { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + padding-bottom: 1rem; + margin-top: -1px; + overflow-x: auto; + text-align: center; + white-space: nowrap; + -webkit-overflow-scrolling: touch; +} + +.nav-scroller .nav-link { + padding-top: .75rem; + padding-bottom: .75rem; + font-size: .875rem; +} + +.card-img-right { + height: 100%; + border-radius: 0 3px 3px 0; +} + +.flex-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; +} + +.h-250 { height: 250px; } +@media (min-width: 768px) { + .h-md-250 { height: 250px; } +} + +/* + * Blog name and description + */ +.blog-title { + margin-bottom: 0; + font-size: 2rem; + font-weight: 400; +} +.blog-description { + font-size: 1.1rem; + color: #999; +} + +@media (min-width: 40em) { + .blog-title { + font-size: 3.5rem; + } +} + +/* Pagination */ +.blog-pagination { + margin-bottom: 4rem; +} +.blog-pagination > .btn { + border-radius: 2rem; +} + +/* + * Blog posts + */ +.blog-post { + margin-bottom: 4rem; +} +.blog-post-title { + margin-bottom: .25rem; + font-size: 2.5rem; +} +.blog-post-meta { + margin-bottom: 1.25rem; + color: #999; +} + +/* + * Footer + */ +.blog-footer { + padding: 2.5rem 0; + color: #999; + text-align: center; + background-color: #f9f9f9; + border-top: .05rem solid #e5e5e5; +} +.blog-footer p:last-child { + margin-bottom: 0; +} + + +.bd-placeholder-img { + font-size: 1.125rem; + text-anchor: middle; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +@media (min-width: 768px) { + .bd-placeholder-img-lg { + font-size: 3.5rem; + } +} diff --git a/scribeengine/themes/quill/templates/about.html b/scribeengine/themes/quill/templates/about.html new file mode 100644 index 0000000..c1a9546 --- /dev/null +++ b/scribeengine/themes/quill/templates/about.html @@ -0,0 +1,4 @@ +
{{site.about}}
+