from __future__ import division try: from builtins import str except ImportError: pass from flask import escape from .processor import PostProcessor from flask_login import login_required, current_user from flask import Blueprint, current_app, render_template, request, redirect, \ url_for, flash, make_response from flask_blogging.forms import BlogEditor import math from werkzeug.contrib.atom import AtomFeed import datetime from flask_principal import PermissionDenied from .signals import page_by_id_fetched, page_by_id_processed, \ posts_by_tag_fetched, posts_by_tag_processed, \ posts_by_author_fetched, posts_by_author_processed, \ index_posts_fetched, index_posts_processed, \ feed_posts_fetched, feed_posts_processed, \ sitemap_posts_fetched, sitemap_posts_processed, editor_post_saved, \ post_deleted, editor_get_fetched from .utils import ensureUtf def _get_blogging_engine(app): return app.extensions["FLASK_BLOGGING_ENGINE"] def _get_user_name(user): user_name = user.get_name() if hasattr(user, "get_name") else str(user) return user_name def _clear_cache(cache): cache.delete_memoized(index) cache.delete_memoized(page_by_id) cache.delete_memoized(posts_by_author) cache.delete_memoized(posts_by_tag) cache.delete_memoized(sitemap) cache.delete_memoized(feed) def _store_form_data(blog_form, storage, user, post, escape_text=True): title = blog_form.title.data text = escape(blog_form.text.data) if escape_text \ else blog_form.text.data tags = blog_form.tags.data.split(",") draft = blog_form.draft.data user_id = user.get_id() current_datetime = datetime.datetime.utcnow() post_date = post.get("post_date", current_datetime) last_modified_date = datetime.datetime.utcnow() post_id = post.get("post_id") pid = storage.save_post(title, text, user_id, tags, draft=draft, post_date=post_date, last_modified_date=last_modified_date, post_id=post_id) return pid def _get_meta(storage, count, page, tag=None, user_id=None): max_posts = storage.count_posts(tag=tag, user_id=user_id) max_pages = math.ceil(float(max_posts)/float(count)) max_offset = (max_pages-1)*count offset = min(max(0, (page-1)*count), max_offset) offset = offset if offset >= 0 else 0 if (tag is None) and (user_id is None): prev_page = None if page <= 1 else url_for( "blogging.index", count=count, page=page-1) next_page = None if page >= max_pages else url_for( "blogging.index", count=count, page=page+1) elif tag: prev_page = None if page <= 1 else url_for( "blogging.posts_by_tag", tag=tag, count=count, page=page-1) next_page = None if page >= max_pages else url_for( "blogging.posts_by_tag", tag=tag, count=count, page=page+1) elif user_id: prev_page = None if page <= 1 else url_for( "blogging.posts_by_author", user_id=user_id, count=count, page=page-1) next_page = None if page >= max_pages else url_for( "blogging.posts_by_author", user_id=user_id, count=count, page=page+1) else: prev_page = next_page = None pagination = dict(prev_page=prev_page, next_page=next_page) meta = dict(max_posts=max_posts, max_pages=max_pages, page=page, max_offset=max_offset, offset=offset, count=count, pagination=pagination) return meta def _is_blogger(blogger_permission): authenticated = current_user.is_authenticated() if \ callable(current_user.is_authenticated) \ else current_user.is_authenticated is_blogger = authenticated and \ blogger_permission.require().can() return is_blogger def index(count, page): """ Serves the page with a list of blog posts :param count: :param offset: :return: """ blogging_engine = _get_blogging_engine(current_app) storage = blogging_engine.storage config = blogging_engine.config count = count or config.get("BLOGGING_POSTS_PER_PAGE", 10) meta = _get_meta(storage, count, page) offset = meta["offset"] meta["is_user_blogger"] = _is_blogger(blogging_engine.blogger_permission) meta["count"] = count meta["page"] = page render = config.get("BLOGGING_RENDER_TEXT", True) posts = storage.get_posts(count=count, offset=offset, include_draft=False, tag=None, user_id=None, recent=True) index_posts_fetched.send(blogging_engine.app, engine=blogging_engine, posts=posts, meta=meta) for post in posts: blogging_engine.process_post(post, render=render) index_posts_processed.send(blogging_engine.app, engine=blogging_engine, posts=posts, meta=meta) return render_template("blogging/index.html", posts=posts, meta=meta, config=config) def page_by_id(post_id, slug): blogging_engine = _get_blogging_engine(current_app) storage = blogging_engine.storage config = blogging_engine.config post = storage.get_post_by_id(post_id) meta = {} meta["is_user_blogger"] = _is_blogger(blogging_engine.blogger_permission) render = config.get("BLOGGING_RENDER_TEXT", True) meta["post_id"] = post_id meta["slug"] = slug page_by_id_fetched.send(blogging_engine.app, engine=blogging_engine, post=post, meta=meta) if post is not None: blogging_engine.process_post(post, render=render) page_by_id_processed.send(blogging_engine.app, engine=blogging_engine, post=post, meta=meta) return render_template("blogging/page.html", post=post, config=config, meta=meta) else: flash("The page you are trying to access is not valid!", "warning") return redirect(url_for("blogging.index")) def posts_by_tag(tag, count, page): blogging_engine = _get_blogging_engine(current_app) storage = blogging_engine.storage config = blogging_engine.config count = count or config.get("BLOGGING_POSTS_PER_PAGE", 10) meta = _get_meta(storage, count, page, tag=tag) offset = meta["offset"] meta["is_user_blogger"] = _is_blogger(blogging_engine.blogger_permission) meta["tag"] = tag meta["count"] = count meta["page"] = page render = config.get("BLOGGING_RENDER_TEXT", True) posts = storage.get_posts(count=count, offset=offset, tag=tag, include_draft=False, user_id=None, recent=True) posts_by_tag_fetched.send(blogging_engine.app, engine=blogging_engine, posts=posts, meta=meta) if len(posts): for post in posts: blogging_engine.process_post(post, render=render) posts_by_tag_processed.send(blogging_engine.app, engine=blogging_engine, posts=posts, meta=meta) return render_template("blogging/index.html", posts=posts, meta=meta, config=config) else: flash("No posts found for this tag!", "warning") return redirect(url_for("blogging.index", post_id=None)) def posts_by_author(user_id, count, page): blogging_engine = _get_blogging_engine(current_app) storage = blogging_engine.storage config = blogging_engine.config count = count or config.get("BLOGGING_POSTS_PER_PAGE", 10) meta = _get_meta(storage, count, page, user_id=user_id) offset = meta["offset"] meta["is_user_blogger"] = _is_blogger(blogging_engine.blogger_permission) meta["user_id"] = user_id meta["count"] = count meta["page"] = page posts = storage.get_posts(count=count, offset=offset, user_id=user_id, include_draft=False, tag=None, recent=True) render = config.get("BLOGGING_RENDER_TEXT", True) posts_by_author_fetched.send(blogging_engine.app, engine=blogging_engine, posts=posts, meta=meta) if len(posts): for post in posts: blogging_engine.process_post(post, render=render) posts_by_author_processed.send(blogging_engine.app, engine=blogging_engine, posts=posts, meta=meta) return render_template("blogging/index.html", posts=posts, meta=meta, config=config) else: flash("No posts found for this user!", "warning") return redirect(url_for("blogging.index", post_id=None)) @login_required def editor(post_id): blogging_engine = _get_blogging_engine(current_app) cache = blogging_engine.cache if cache: _clear_cache(cache) try: with blogging_engine.blogger_permission.require(): post_processor = blogging_engine.post_processor config = blogging_engine.config storage = blogging_engine.storage if request.method == 'POST': form = BlogEditor(request.form) if form.validate(): post = storage.get_post_by_id(post_id) if (post is not None) and \ (post_processor.is_author(post, current_user)) and \ (str(post["post_id"]) == post_id): pass else: post = {} escape_text = config.get("BLOGGING_ESCAPE_MARKDOWN", False) pid = _store_form_data(form, storage, current_user, post, escape_text) editor_post_saved.send(blogging_engine.app, engine=blogging_engine, post_id=pid, user=current_user, post=post) flash("Blog posted successfully!", "info") slug = post_processor.create_slug(form.title.data) return redirect(url_for("blogging.page_by_id", post_id=pid, slug=slug)) else: flash("There were errors in blog submission", "warning") return render_template("blogging/editor.html", form=form, post_id=post_id, config=config) else: if post_id is not None: post = storage.get_post_by_id(post_id) if (post is not None) and \ (post_processor.is_author(post, current_user)): tags = ", ".join(post["tags"]) form = BlogEditor(title=post["title"], text=post["text"], tags=tags) editor_get_fetched.send(blogging_engine.app, engine=blogging_engine, post_id=post_id, form=form) return render_template("blogging/editor.html", form=form, post_id=post_id, config=config) else: flash("You do not have the rights to edit this post", "warning") return redirect(url_for("blogging.index", post_id=None)) form = BlogEditor() return render_template("blogging/editor.html", form=form, post_id=post_id, config=config) except PermissionDenied: flash("You do not have permissions to create or edit posts", "warning") return redirect(url_for("blogging.index", post_id=None)) @login_required def delete(post_id): blogging_engine = _get_blogging_engine(current_app) cache = blogging_engine.cache if cache: _clear_cache(cache) try: post_processor = blogging_engine.post_processor with blogging_engine.blogger_permission.require(): storage = blogging_engine.storage post = storage.get_post_by_id(post_id) if (post is not None) and \ (post_processor.is_author(post, current_user)): success = storage.delete_post(post_id) if success: flash("Your post was successfully deleted", "info") post_deleted.send(blogging_engine.app, engine=blogging_engine, post_id=post_id, post=post) else: flash("There were errors while deleting your post", "warning") else: flash("You do not have the rights to delete this post", "warning") return redirect(url_for("blogging.index")) except PermissionDenied: flash("You do not have permissions to delete posts", "warning") return redirect(url_for("blogging.index", post_id=None)) def sitemap(): blogging_engine = _get_blogging_engine(current_app) storage = blogging_engine.storage config = blogging_engine.config posts = storage.get_posts(count=None, offset=None, recent=True, user_id=None, tag=None, include_draft=False) sitemap_posts_fetched.send(blogging_engine.app, engine=blogging_engine, posts=posts) if len(posts): for post in posts: blogging_engine.process_post(post, render=False) sitemap_posts_processed.send(blogging_engine.app, engine=blogging_engine, posts=posts) sitemap_xml = render_template("blogging/sitemap.xml", posts=posts, config=config) response = make_response(sitemap_xml) response.headers["Content-Type"] = "application/xml" return response def feed(): blogging_engine = _get_blogging_engine(current_app) storage = blogging_engine.storage config = blogging_engine.config count = config.get("BLOGGING_FEED_LIMIT") posts = storage.get_posts(count=count, offset=None, recent=True, user_id=None, tag=None, include_draft=False) feed = AtomFeed( '%s - All Articles' % config.get("BLOGGING_SITENAME", "Flask-Blogging"), feed_url=request.url, url=request.url_root, generator=None) feed_posts_fetched.send(blogging_engine.app, engine=blogging_engine, posts=posts) if len(posts): for post in posts: blogging_engine.process_post(post, render=True) feed.add(post["title"], ensureUtf(post["rendered_text"]), content_type='html', author=post["user_name"], url=config.get("BLOGGING_SITEURL", "")+post["url"], updated=post["last_modified_date"], published=post["post_date"]) feed_posts_processed.send(blogging_engine.app, engine=blogging_engine, feed=feed) response = feed.get_response() response.headers["Content-Type"] = "application/xml" return response def unless(blogging_engine): # disable caching for bloggers. They can change state! def _unless(): return _is_blogger(blogging_engine.blogger_permission) return _unless def cached_func(blogging_engine, func): cache = blogging_engine.cache if cache is None: return func else: unless_func = unless(blogging_engine) config = blogging_engine.config cache_timeout = config.get("BLOGGING_CACHE_TIMEOUT", 60) # 60 seconds memoized_func = cache.memoize( timeout=cache_timeout, unless=unless_func)(func) return memoized_func def create_blueprint(import_name, blogging_engine): blog_app = Blueprint("blogging", import_name, template_folder='templates') # register index index_func = cached_func(blogging_engine, index) blog_app.add_url_rule("/", defaults={"count": None, "page": 1}, view_func=index_func) blog_app.add_url_rule("/<int:count>/", defaults={"page": 1}, view_func=index_func) blog_app.add_url_rule("/<int:count>/<int:page>/", view_func=index_func) # register page_by_id page_by_id_func = cached_func(blogging_engine, page_by_id) blog_app.add_url_rule("/page/<post_id>/", defaults={"slug": ""}, view_func=page_by_id_func) blog_app.add_url_rule("/page/<post_id>/<slug>/", view_func=page_by_id_func) # register posts_by_tag posts_by_tag_func = cached_func(blogging_engine, posts_by_tag) blog_app.add_url_rule("/tag/<tag>/", defaults=dict(count=None, page=1), view_func=posts_by_tag_func) blog_app.add_url_rule("/tag/<tag>/<int:count>/", defaults=dict(page=1), view_func=posts_by_tag_func) blog_app.add_url_rule("/tag/<tag>/<int:count>/<int:page>/", view_func=posts_by_tag_func) # register posts_by_author posts_by_author_func = cached_func(blogging_engine, posts_by_author) blog_app.add_url_rule("/author/<user_id>/", defaults=dict(count=None, page=1), view_func=posts_by_author_func) blog_app.add_url_rule("/author/<user_id>/<int:count>/", defaults=dict(page=1), view_func=posts_by_author_func) blog_app.add_url_rule("/author/<user_id>/<int:count>/<int:page>/", view_func=posts_by_author_func) # register editor editor_func = editor # For now lets not cache this blog_app.add_url_rule('/editor/', methods=["GET", "POST"], defaults={"post_id": None}, view_func=editor_func) blog_app.add_url_rule('/editor/<post_id>/', methods=["GET", "POST"], view_func=editor_func) # register delete delete_func = delete # For now lets not cache this blog_app.add_url_rule("/delete/<post_id>/", methods=["POST"], view_func=delete_func) # register sitemap sitemap_func = cached_func(blogging_engine, sitemap) blog_app.add_url_rule("/sitemap.xml", view_func=sitemap_func) # register feed feed_func = cached_func(blogging_engine, feed) blog_app.add_url_rule('/feeds/all.atom.xml', view_func=feed_func) return blog_app