Drag this branch up into the current century
This commit is contained in:
parent
50ceeba973
commit
045e452f5b
@ -3,3 +3,6 @@ dist
|
||||
*.pyc
|
||||
__pycache__
|
||||
*.egg-info
|
||||
config.ini
|
||||
scribeengine.sqlite
|
||||
.env
|
||||
|
14
README.rst
14
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
|
||||
|
||||
|
@ -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
|
||||
|
@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
from scribeengine import application
|
||||
application.run(host='0.0.0.0', port=8080, debug=True)
|
@ -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
|
||||
|
@ -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
3
scribeengine/__main__.py
Normal 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)
|
@ -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)
|
@ -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
|
||||
|
12
scribeengine/themes/quill/info.json
Normal file
12
scribeengine/themes/quill/info.json
Normal 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"
|
||||
}
|
141
scribeengine/themes/quill/static/css/quill.css
Normal file
141
scribeengine/themes/quill/static/css/quill.css
Normal 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;
|
||||
}
|
||||
}
|
4
scribeengine/themes/quill/templates/about.html
Normal file
4
scribeengine/themes/quill/templates/about.html
Normal 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>
|
8
scribeengine/themes/quill/templates/archives.html
Normal file
8
scribeengine/themes/quill/templates/archives.html
Normal 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>
|
17
scribeengine/themes/quill/templates/base.html
Normal file
17
scribeengine/themes/quill/templates/base.html
Normal 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>
|
||||
|
30
scribeengine/themes/quill/templates/blog.html
Normal file
30
scribeengine/themes/quill/templates/blog.html
Normal 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 %}
|
6
scribeengine/themes/quill/templates/footer.html
Normal file
6
scribeengine/themes/quill/templates/footer.html
Normal 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>
|
9
scribeengine/themes/quill/templates/header.html
Normal file
9
scribeengine/themes/quill/templates/header.html
Normal 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>
|
5
scribeengine/themes/quill/templates/post-teaser.html
Normal file
5
scribeengine/themes/quill/templates/post-teaser.html
Normal 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>
|
5
scribeengine/themes/quill/templates/post.html
Normal file
5
scribeengine/themes/quill/templates/post.html
Normal 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>
|
8
scribeengine/themes/quill/templates/social_links.html
Normal file
8
scribeengine/themes/quill/templates/social_links.html
Normal 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>
|
@ -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)
|
||||
|
42
scribeengine/views/blog.py
Normal file
42
scribeengine/views/blog.py
Normal 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)
|
Reference in New Issue
Block a user