Wednesday, June 28, 2017

Flask-Blogging¶

Flask-Blogging is a Flask extension for adding Markdown based blog support to your site. It provides a flexible mechanism to store the data in the database of your choice. It is meant to work with the authentication provided by packages such as Flask-Login or Flask-Security.

The philosophy behind this extension is to provide a lean app based on Markdown to provide blog support to your existing web application. This is contrary to some other packages such as Flask-Blog that are just blogs. If you already have a web app and you need to have a blog to communicate with your user or to promote your site through content based marketing, then Flask-Blogging would help you quickly get a blog up and running.

Quick Start Example from flask import Flask, render_template_string, redirect from sqlalchemy import create_engine, MetaData from flask_login import UserMixin, LoginManager, login_user, logout_user from flask_blogging import SQLAStorage, BloggingEngine app = Flask(__name__) app.config["SECRET_KEY"] = "secret" # for WTF-forms and login app.config["BLOGGING_URL_PREFIX"] = "/blog" app.config["BLOGGING_DISQUS_SITENAME"] = "test" app.config["BLOGGING_SITEURL"] = "http://localhost:8000" app.config["BLOGGING_SITENAME"] = "My Site" app.config["BLOGGING_KEYWORDS"] = ["blog", "meta", "keywords"] app.config["FILEUPLOAD_IMG_FOLDER"] = "fileupload" app.config["FILEUPLOAD_PREFIX"] = "/fileupload" app.config["FILEUPLOAD_ALLOWED_EXTENSIONS"] = ["png", "jpg", "jpeg", "gif"] # extensions engine = create_engine('sqlite:////tmp/blog.db') meta = MetaData() sql_storage = SQLAStorage(engine, metadata=meta) blog_engine = BloggingEngine(app, sql_storage) login_manager = LoginManager(app) meta.create_all(bind=engine) class User(UserMixin): def __init__(self, user_id): self.id = user_id def get_name(self): return "Paul Dirac" # typically the user's name @login_manager.user_loader @blog_engine.user_loader def load_user(user_id): return User(user_id) index_template = """ <!DOCTYPE html> <html> <head> </head> <body> {% if current_user.is_authenticated %} <a href="/logout/"> Logout </a> {% else %} <a href="/login/"> Login </a> {% endif %} &nbsp&nbsp<a href="/blog/"> Blog </a> &nbsp&nbsp<a href="/blog/sitemap.xml">Sitemap</a> &nbsp&nbsp<a href="/blog/feeds/all.atom.xml">ATOM</a> &nbsp&nbsp<a href="/fileupload/">FileUpload</a> </body> </html> """ @app.route("/") def index(): return render_template_string(index_template) @app.route("/login/") def login(): user = User("testuser") login_user(user) return redirect("/blog") @app.route("/logout/") def logout(): logout_user() return redirect("/") if __name__ == "__main__": app.run(debug=True, port=8000, use_reloader=True)

The key components required to get the blog hooked is explained below. Please note that as of Flask-Login 0.3.0 the is_authenticated attribute in the UserMixin is a property and not a method. Please use the appropriate option based on your Flask-Login version.

Configuring your Application

The BloggingEngine class is the gateway to configure blogging support to your web app. You should create the BloggingEngine instance like this:

blogging_engine = BloggingEngine() blogging_engine.init_app(app, storage)

You also need to pick the storage for blog. That can be done as:

from sqlalchemy import create_engine, MetaData engine = create_engine("sqlite:////tmp/sqlite.db") meta = MetaData() storage = SQLAStorage(engine, metadata=meta) meta.create_all(bind=engine)

Here we have created the storage, and created all the tables in the metadata. Once you have created the blogging engine, storage, and all the tables in the storage, you can connect with your app using the init_app method as shown below:

blogging_engine.init_app(app, storage)

If you are using Flask-Sqlalchemy, you can do the following:

from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy(app) storage = SQLAStorage(db=db) db.create_all()

One of the changes in version 0.3.1 is the ability for the user to provide the metadata object. This has the benefit of the table creation being passed to the user. Also, this gives the user the ability to use the common metadata object, and hence helps with the tables showing up in migrations while using Alembic.

As of version 0.5.2, support for the multi database scenario under Flask-SQLAlchemy was added. When we have a multiple database scenario, one can use the bind keyword in SQLAStorage to specify the database to bind to, as shown below:

# config value SQLALCHEMY_BINDS = { 'blog': "sqlite:////tmp/blog.db"), 'security': "sqlite:////tmp/security.db") }

The storage can be initialised as:

db = SQLAlchemy(app) storage = SQLAStorage(db=db, bind="blog") db.create_all()

As of version 0.4.0, Flask-Cache integration is supported. In order to use caching in the blogging engine, you need to pass the Cache instance to the BloggingEngine as:

from flask_cache import Cache from flask_blogging import BloggingEngine blogging_engine = BloggingEngine(app, storage, cache)

Flask-Blogging lets the developer pick the authentication that is suitable, and hence requires her to provide a way to load user information. You will need to provide a BloggingEngine.user_loader callback. This callback is used to load the user from the user_id that is stored for each blog post. Just as in Flask-Login, it should take the unicode user_id of a user, and return the corresponding user object. For example:

@blogging_engine.user_loader def load_user(userid): return User.get(userid)

For the blog to have a readable display name, the User class must implement either the get_name method or the __str__ method.

The BloggingEngine accepts an optional extensions argument. This is a list of Markdown extensions objects to be used during the markdown processing step.

As of version 0.6.0, a plugin interface is available to add new functionality. Custom processes can be added to the posts by subscribing to the post_process_before and post_process_after signals, and adding new functionality to it.

The BloggingEngine also accepts post_processor argument, which can be used to provide a custom post processor object to handle the processing of Markdown text. One way to do this would be to inherit the default PostProcessor object and override process method.

In version 0.4.1 and onwards, the BloggingEngine object can be accessed from your app as follows:

engine = app.extensions["blogging"]

The engine method also exposes a get_posts method to get the recent posts for display of posts in other views.

In earlier versions the same can be done using the key FLASK_BLOGGING_ENGINE instead of blogging. The use of FLASK_BLOGGING_ENGINE key will be deprecated moving forward.

Models from SQLAStorage

SQLAlchemy ORM models for the SQLAStorage can be accessed after configurtion of the SQLAStorage object. Here is a quick example:

storage = SQLAStorage(db=db) from flask_blogging.sqlastorage import Post, Tag # Has to be after SQLAStorage initialization

These ORM models can be extremely convenient to use with Flask-Admin.

Adding Custom Markdown Extensions

One can provide additional MarkDown extensions to the blogging engine. One example usage is adding the codehilite MarkDown extension. Additional extensions should be passed as a list while initializing the BlogggingEngine as shown:

from markdown.extensions.codehilite import CodeHiliteExtension extn1 = CodeHiliteExtension({}) blogging_engine = BloggingEngine(app, storage,extensions=[extn1])

This allows for the MarkDown to be processed using CodeHilite along with the default extensions. Please note that one would also need to include necessary static files in the view, such as for code highlighting to work.

Extending using the plugin framework

The plugin framework is a very powerful way to modify the behavior of the blogging engine. Lets say you want to show the top 10 most popular tag in the post. Lets show how one can do that using the plugins framework. As a first step, we create our plugin:

# plugins/tag_cloud/__init__.py from flask_blogging import signals from flask_blogging.sqlastorage import SQLAStorage import sqlalchemy as sqla from sqlalchemy import func def get_tag_data(sqla_storage): engine = sqla_storage.engine with engine.begin() as conn: tag_posts_table = sqla_storage.tag_posts_table tag_table = sqla_storage.tag_table tag_cloud_stmt = sqla.select([ tag_table.c.text,func.count(tag_posts_table.c.tag_id)]).group_by( tag_posts_table.c.tag_id ).where(tag_table.c.id == tag_posts_table.c.tag_id).limit(10) tag_cloud = conn.execute(tag_cloud_stmt).fetchall() return tag_cloud def get_tag_cloud(app, engine, posts, meta): if isinstance(engine.storage, SQLAStorage): tag_cloud = get_tag_data(engine.storage) meta["tag_cloud"] = tag_cloud else: raise RuntimeError("Plugin only supports SQLAStorage. Given storage" "not supported") return def register(app): signals.index_posts_fetched.connect(get_tag_cloud) return

The register method is what is invoked in order to register the plugin. We have connected this plugin to the index_posts_fetched signal. So when the posts are fetched to show on the index page, this signal will be fired, and this plugin will be invoked. The plugin basically queries the table that stores the tags, and returns the tag text and the number of times it is referenced. The data about the tag cloud we are storing in the meta["tag_cloud"] which corresponds to the metadata variable.

Now in the index.html template, one can reference the meta.tag_cloud to access this data for display. The plugin can be registered by setting the config variable as shown:

app.config["BLOGGING_PLUGINS"] = ["plugins.tag_cloud"] Blog Views

There are various views that are exposed through Flask-Blogging. The URL for the various views are:

  • url_for('blogging.index') (GET): The index blog posts with the first page of articles. The meta variable passed into the view holds values for the keys is_user_blogger, count and page.
  • url_for('blogging.page_by_id', post_id=<post_id>) (GET): The blog post corresponding to the post_id is retrieved. The meta variable passed into the view holds values for the keys is_user_blogger, post_id and slug.
  • url_for('blogging.posts_by_tag', tag=<tag_name>) (GET): The list of blog posts corresponding to tag_name is returned. The meta variable passed into the view holds values for the keys is_user_blogger, tag, count and page.
  • url_for('blogging.posts_by_author', user_id=<user_id>) (GET): The list of blog posts written by the author user_id is returned. The meta variable passed into the view holds values for the keys is_user_blogger, count, user_id and pages.
  • url_for('blogging.editor') (GET, POST): The blog editor is shown. This view needs authentication and permissions (if enabled).
  • url_for('blogging.delete', post_id=<post_id>) (POST): The blog post given by post_id is deleted. This view needs authentication and permissions (if enabled).
  • url_for('blogging.sitemap') (GET): The sitemap with a link to all the posts is returned.
  • url_for('blogging.feed') (GET): Returns ATOM feed URL.
  • The view can be easily customised by the user by overriding with their own templates. The template pages that need to be customized are:

  • blogging/index.html: The blog index page used to serve index of posts, posts by tag, and posts by author
  • blogging/editor.html: The blog editor page.
  • blogging/page.html: The page that shows the given article.
  • blogging/sitemap.xml: The sitemap for the blog posts.

  • Source: Flask-Blogging¶

    No comments:

    Post a Comment