diff --git a/scribeengine/__init__.py b/scribeengine/__init__.py index 678d7cd..388d520 100644 --- a/scribeengine/__init__.py +++ b/scribeengine/__init__.py @@ -1,23 +1,23 @@ # -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 - ############################################################################### -# ScribeEngine - Open Source Blog Software # +# ScribeEngine - Open Source Content Management System # # --------------------------------------------------------------------------- # -# Copyright (c) 2010-2017 Raoul Snyman # +# Copyright (c) 2010-2021 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 file is part of ScribeEngine. # # # -# This program is distributed in the hope that it will be useful, but WITHOUT # +# ScribeEngine 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, either version 3 of the License, or (at your option) # +# any later version. # +# # +# ScribeEngine 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 # +# 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 # +# with ScribeEngine. If not, see . # ############################################################################### """ The :mod:`~scribeengine` module sets up and runs ScribeEngine @@ -34,7 +34,7 @@ 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 +from scribeengine.views.node import node_views def _scribeengine_themes_loader(app): @@ -69,7 +69,7 @@ def create_app(config_file=None): UserManager(application, db, User) # Register all the blueprints application.register_blueprint(admin) - application.register_blueprint(blog) + application.register_blueprint(node_views) application.register_blueprint(account) # Return the application object return application diff --git a/scribeengine/db.py b/scribeengine/db.py index 18af498..572a02e 100644 --- a/scribeengine/db.py +++ b/scribeengine/db.py @@ -1,23 +1,23 @@ # -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 - ############################################################################### -# ScribeEngine - Open Source Blog Software # +# ScribeEngine - Open Source Content Management System # # --------------------------------------------------------------------------- # -# Copyright (c) 2010-2017 Raoul Snyman # +# Copyright (c) 2010-2021 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 file is part of ScribeEngine. # # # -# This program is distributed in the hope that it will be useful, but WITHOUT # +# ScribeEngine 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, either version 3 of the License, or (at your option) # +# any later version. # +# # +# ScribeEngine 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 # +# 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 # +# with ScribeEngine. If not, see . # ############################################################################### """ The :mod:`~scribeengine.db` module sets up SQLAlchemy @@ -27,7 +27,7 @@ from flask_sqlalchemy import SQLAlchemy # Get the db object db = SQLAlchemy() -# Extract the classes to make them prettier +# Extract the objects to make them prettier Model = db.Model Table = db.Table Column = db.Column @@ -39,4 +39,5 @@ Boolean = db.Boolean DateTime = db.DateTime relationship = db.relationship backref = db.backref +inspect = db.inspect session = db.session diff --git a/scribeengine/helpers.py b/scribeengine/helpers.py index 6c53cd2..8eb5a98 100644 --- a/scribeengine/helpers.py +++ b/scribeengine/helpers.py @@ -1,40 +1,76 @@ # -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 - ############################################################################### -# ScribeEngine - Open Source Blog Software # +# ScribeEngine - Open Source Content Management System # # --------------------------------------------------------------------------- # -# Copyright (c) 2010-2017 Raoul Snyman # +# Copyright (c) 2010-2021 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 file is part of ScribeEngine. # # # -# This program is distributed in the hope that it will be useful, but WITHOUT # +# ScribeEngine 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, either version 3 of the License, or (at your option) # +# any later version. # +# # +# ScribeEngine 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 # +# 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 # +# with ScribeEngine. If not, see . # ############################################################################### """ The :mod:`~scribeengine.helpers` module contains some theme helper methods """ from flask import current_app -from flask_themes2 import get_theme, render_theme_template +from flask_themes2 import get_theme, render_theme_template, template_exists as ft2_template_exists +from jinja2.loaders import TemplateNotFound, contextfunction -from scribeengine.models import Site +from scribeengine.models import Menu, Site, Variable + + +@contextfunction +def template_exists(context, template_name): + """ + Add the template_exists() method into the template context + """ + return ft2_template_exists(template_name) + + +def get_variable(name, default_value=None): + def _type_cast(type_, value, default_value): + try: + return type_(var.value) + except (TypeError, ValueError): + return default_value + + var = Variable.get(name) + if var: + if var.type.lower().startswith('int'): + return _type_cast(int, var.value, default_value) + elif var.type.lower().startswith('float'): + return _type_cast(float, var.value, default_value) + elif var.type.lower().startswith('bool'): + return var.value.lower().strip() in ['yes', 'y', '1', 'true', 't'] + return var.value + else: + return default_value 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', '')) + return Site(get_variable('site-name', 'Example Site'), + get_variable('site-slogan', 'From the Firehose'), + get_variable('site-about', '')) + + +def get_navigation(block='primary'): + """ + Get the navigation + """ + return Menu.query.filter(Menu.block == block).first() def get_current_theme(): @@ -49,10 +85,36 @@ def render(template, **context): """ Render a template, after selecting a theme """ - context.update({'site': get_site_details(), 'archives': []}) + context.update({'site': get_site_details(), 'navigation': get_navigation()}) return render_theme_template(get_current_theme(), template, **context) +def render_node(node): + """ + Render a template, after selecting a theme + """ + context = {'node': node.complete} + template_names = ['/node-{}.html'.format(node.type), '/node.html'] + for template in template_names: + if ft2_template_exists(template): + return render(template, **context) + raise TemplateNotFound('Could not find a node template') + + +def render_node_list(nodes): + """ + Render a list of nodes + """ + context = {'nodes': [node.complete for node in nodes]} + template_names = ['/node-list.html'] + if nodes: + template_names.insert(0, '/node-{}-list.html'.format(nodes[0].type)) + for template in template_names: + if ft2_template_exists(template): + return render(template, **context) + raise TemplateNotFound('Could not find a node-list template') + + def render_admin(template, **context): """ Render a template, after selecting a theme diff --git a/scribeengine/models.py b/scribeengine/models.py index d3106e4..89a6677 100644 --- a/scribeengine/models.py +++ b/scribeengine/models.py @@ -1,23 +1,23 @@ # -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 - ############################################################################### -# ScribeEngine - Open Source Blog Software # +# ScribeEngine - Open Source Content Management System # # --------------------------------------------------------------------------- # -# Copyright (c) 2010-2017 Raoul Snyman # +# Copyright (c) 2010-2021 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 file is part of ScribeEngine. # # # -# This program is distributed in the hope that it will be useful, but WITHOUT # +# ScribeEngine 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, either version 3 of the License, or (at your option) # +# any later version. # +# # +# ScribeEngine 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 # +# 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 # +# with ScribeEngine. If not, see . # ############################################################################### """ The :mod:`~scribeengine.models` module contains all the database models @@ -28,7 +28,7 @@ from box import Box from flask_user import UserMixin from scribeengine.db import Model, Table, Column, ForeignKey, String, Text, Integer, Boolean, DateTime, \ - relationship + backref, inspect, relationship permissions_roles = Table( @@ -52,7 +52,15 @@ roles_users = Table( ) -class Taxonomy(Model): +class DictMixin(object): + """ + A mixin class to add a "to_dict" method to each model + """ + def to_dict(self): + return Box({c.key: getattr(self, c.key) for c in inspect(self).mapper.column_attrs}) + + +class Taxonomy(Model, DictMixin): """ This is a grouping of related terms, like a tags or categories """ @@ -67,7 +75,7 @@ class Taxonomy(Model): return self.name -class Node(Model): +class Node(Model, DictMixin): """ Nodes are the basic content type """ @@ -85,27 +93,18 @@ class Node(Model): @property def complete(self): """Return a "full" or "complete" node, with all fields and data""" - node_dict = Box({ - 'id': self.id, - 'slug': self.slug, - 'type': self.type, - 'created': self.created, - 'modified': self.modified, - 'title': self.current_revision.title, - 'body': self.current_revision.body, - 'format': self.current_revision.format - }) + node_dict = self.to_dict() for field in self.fields: - node_dict[field.name] = { + node_dict[field.name] = Box({ 'name': field.name, 'type': field.type, 'title': field.field.title, 'data': field.data - } + }) return node_dict -class Field(Model): +class Field(Model, DictMixin): """ A field is a model for field types on nodes """ @@ -120,7 +119,7 @@ class Field(Model): modified = Column(DateTime, nullable=False, default=datetime.utcnow) -class FieldRevision(Model): +class FieldRevision(Model, DictMixin): """ A revision of a field on a node """ @@ -136,7 +135,7 @@ class FieldRevision(Model): node_id = Column(Integer, ForeignKey('nodes.id'), nullable=False) -class NodeField(Model): +class NodeField(Model, DictMixin): """ A node field is a field on a particular node """ @@ -155,7 +154,7 @@ class NodeField(Model): field = relationship('Field', backref='node_field') -class File(Model): +class File(Model, DictMixin): """ A file in the media library. """ @@ -173,7 +172,7 @@ class File(Model): return self.filename -class MediaType(Model): +class MediaType(Model, DictMixin): """ Distinguishes between different types of media. """ @@ -188,7 +187,7 @@ class MediaType(Model): return self.title -class Permission(Model): +class Permission(Model, DictMixin): """ A single permission. """ @@ -202,7 +201,7 @@ class Permission(Model): return self.name -class Role(Model): +class Role(Model, DictMixin): """ A role defines a set of permissions. """ @@ -218,7 +217,7 @@ class Role(Model): return self.name -class Term(Model): +class Term(Model, DictMixin): """ A term is an item in a taxonomy. A term can be a category name, or a tag in a blog post. """ @@ -233,7 +232,7 @@ class Term(Model): return self.name -class User(Model, UserMixin): +class User(Model, UserMixin, DictMixin): """ The user. """ @@ -283,15 +282,15 @@ class User(Model, UserMixin): return str(id(self)) -class Variable(Model): +class Variable(Model, DictMixin): """ System variables. """ __tablename__ = 'variables' - key = Column(String(100), primary_key=True, index=True) - value = Column(String(100), nullable=False) - type = Column(String(10), default='string') + key = Column(String, primary_key=True, index=True) + value = Column(Text, nullable=False) + type = Column(String, default='string') class Site(object): @@ -306,3 +305,33 @@ class Site(object): self.name = name self.slogan = slogan self.about = about + + +class Menu(Model, DictMixin): + """ + A menu + """ + __tablename__ = 'menus' + + id = Column(Integer, primary_key=True) + name = Column(String, nullable=False) + description = Column(Text) + block = Column(String) + + items = relationship('MenuItem', backref='menu') + + +class MenuItem(Model, DictMixin): + """ + A menu item + """ + __tablename__ = 'menu_items' + + id = Column(Integer, primary_key=True) + title = Column(String, nullable=False) + url = Column(String, nullable=False) + weight = Column(Integer, default=0) + menu_id = Column(Integer, ForeignKey('menus.id')) + parent_id = Column(Integer, ForeignKey('menu_items.id')) + + items = relationship('MenuItem', backref=backref('parent', remote_side=[id])) diff --git a/scribeengine/views/blog.py b/scribeengine/views/blog.py deleted file mode 100644 index 6264bab..0000000 --- a/scribeengine/views/blog.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- 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('//', methods=['GET']) -def archive(year, month=None): - posts = Post.query.limit(10).all() - return render('/blog.html', title='ScribeEngine', posts=posts) diff --git a/scribeengine/views/node.py b/scribeengine/views/node.py new file mode 100644 index 0000000..9fd43f3 --- /dev/null +++ b/scribeengine/views/node.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +############################################################################### +# ScribeEngine - Open Source Content Management System # +# --------------------------------------------------------------------------- # +# Copyright (c) 2010-2021 Raoul Snyman # +# --------------------------------------------------------------------------- # +# This file is part of ScribeEngine. # +# # +# ScribeEngine 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, either version 3 of the License, or (at your option) # +# any later version. # +# # +# ScribeEngine 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 ScribeEngine. If not, see . # +############################################################################### +""" +The :mod:`~scribeengine.views.node` module contains methods that actually render content nodes +""" +from flask import Blueprint + +from scribeengine.helpers import render +from scribeengine.models import Node + +node_views = Blueprint('node', __name__, url_prefix='/') + + +@node_views.route('', methods=['GET']) +def index(): + nodes = Node.query.limit(10).all() + return render('/node-list.html', title='ScribeEngine', nodes=[n.complete for n in nodes]) + + +@node_views.route('/node/', methods=['GET']) +def view(node_id): + node = Node.get(node_id) + if not node: + return render('/404.html', title='ScribeEngine'), 404 + return render('/blog.html', title='ScribeEngine', node=node.complete)