Drag this branch up into the current century

This commit is contained in:
Raoul Snyman 2019-02-14 16:01:27 -07:00
parent 50ceeba973
commit 045e452f5b
22 changed files with 370 additions and 32 deletions

View File

@ -3,3 +3,6 @@ dist
*.pyc
__pycache__
*.egg-info
config.ini
scribeengine.sqlite
.env

View File

@ -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

View File

@ -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

View File

@ -1,3 +0,0 @@
#!/usr/bin/env python3
from scribeengine import application
application.run(host='0.0.0.0', port=8080, debug=True)

View File

@ -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

View File

@ -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

3
scribeengine/__main__.py Normal file
View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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"
}

View File

@ -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;
}
}

View File

@ -0,0 +1,4 @@
<div class="p-4 mb-3 bg-light rounded">
<h4 class="font-italic">About</h4>
<p class="mb-0">{{site.about}}</p>
</div>

View File

@ -0,0 +1,8 @@
<div class="p-4">
<h4 class="font-italic">Archives</h4>
<ol class="list-unstyled mb-0">
{% for archive in archives %}
<li><a href="/{{archive.year}}/{{archive.month}}">{{archive.month_name}} {{archive.year}}</a></li>
{% endfor %}
</ol>
</div>

View File

@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{{title}}</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Playfair+Display:700,900">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="{{ theme_static('css/quill.css') }}">
</head>
<body>
{% include theme("header.html") %}
{% block content %}{% endblock %}
{% include theme("footer.html") %}
</body>
</html>

View File

@ -0,0 +1,30 @@
{% extends theme("base.html") %}
{% block content %}
<main role="main" class="container">
<div class="row">
<div class="col-md-8 blog-main">
{% if site.slogan %}
<h3 class="pb-4 mb-4 font-italic border-bottom">{{site.slogan}}</h3>
{% endif %}
{% for post in posts %}
{% include theme("post-teaser.html") %}
{% endfor %}
<nav class="blog-pagination">
<a class="btn btn-outline-primary" href="{{request.base_url}}?page={{'1'}}">Older</a>
<a class="btn btn-outline-secondary disabled" href="{{request.base_url}}?page={{'1'}}" tabindex="-1" aria-disabled="true">Newer</a>
</nav>
</div>
<aside class="col-md-4 blog-sidebar">
{% if site.about %}
{% include theme("about.html") %}
{% endif %}
{% if archives %}
{% include theme("archives.html") %}
{% endif %}
{% if site.social_links %}
{% include theme("social_links.html") %}
{% endif %}
</aside>
</div>
</main>
{% endblock %}

View File

@ -0,0 +1,6 @@
<footer class="blog-footer">
<p>Blog template built for <a href="https://getbootstrap.com/">Bootstrap</a> by <a href="https://twitter.com/mdo">@mdo</a>.</p>
<p>
<a href="#">Back to top</a>
</p>
</footer>

View File

@ -0,0 +1,9 @@
<div class="container">
<header class="blog-header py-3">
<div class="row flex-nowrap justify-content-between align-items-center">
<div class="col-12 text-center">
<a class="blog-header-logo text-dark" href="#">{{ site.name }}</a>
</div>
</div>
</header>
</div>

View File

@ -0,0 +1,5 @@
<div class="blog-post">
<h2 class="blog-post-title">{{post.title}}</h2>
<p class="blog-post-meta">{{post.created.strftime('%A %d, %B %Y')}} by <a href="/authors/{{post.author.id}}">{{post.user.name}}</a></p>
{{post.content}}
</div>

View File

@ -0,0 +1,5 @@
<div class="blog-post">
<h2 class="blog-post-title">{{post.title}}</h2>
<p class="blog-post-meta">{{post.created.strftime('%A %d, %B %Y')}} by <a href="/authors/{{post.author.id}}">{{post.user.name}}</a></p>
{{post.content}}
</div>

View File

@ -0,0 +1,8 @@
<div class="p-4">
<h4 class="font-italic">Elsewhere</h4>
<ol class="list-unstyled">
{% for link in social_links %}
<li><a href="{{link.url}}">{{link.name}}</a></li>
{% endfor %}
</ol>
</div>

View File

@ -25,11 +25,11 @@ The :mod:`~scribeengine.views.account` module contains the account views
from flask import Blueprint
from pytz import all_timezones
from scribeengine.themes import render
from scribeengine.helpers import render
account = Blueprint('account', __file__, prefix='/account')
account = Blueprint('account', __name__, url_prefix='/account')
@account.route('', methods=['GET'])
def index(self):
def index():
return render('/account/index.html', page_title='My Account', timezones=all_timezones)

View File

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# ScribeEngine - Open Source Blog Software #
# --------------------------------------------------------------------------- #
# Copyright (c) 2010-2017 Raoul Snyman #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`~scribeengine.views.blog` module contains the blog post views
"""
from flask import Blueprint
from scribeengine.helpers import render
from scribeengine.models import Post
blog = Blueprint('blog', __name__, url_prefix='/')
@blog.route('', methods=['GET'])
def index():
posts = Post.query.limit(10).all()
return render('/blog.html', title='ScribeEngine', posts=posts)
@blog.route('/<int:year>/<int:month>', methods=['GET'])
def archive(year, month=None):
posts = Post.query.limit(10).all()
return render('/blog.html', title='ScribeEngine', posts=posts)