]> git.ipfire.org Git - ipfire.org.git/commitdiff
blog: Implement read-only backend
authorMichael Tremer <michael.tremer@ipfire.org>
Tue, 17 Jul 2018 18:24:00 +0000 (19:24 +0100)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 17 Jul 2018 18:27:43 +0000 (19:27 +0100)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/backend/base.py
src/backend/blog.py [new file with mode: 0644]
src/backend/misc.py
src/templates/blog/author.html
src/templates/blog/feed.xml
src/templates/blog/modules/post.html
src/web/blog.py

index 8761a28316018bbc7f9c9d72901a30387e762e01..ef9dbcc9fba6ac0d73458339e2e387907c060f5b 100644 (file)
@@ -51,6 +51,7 @@ backend_PYTHON = \
        src/backend/accounts.py \
        src/backend/ads.py \
        src/backend/base.py \
+       src/backend/blog.py \
        src/backend/countries.py \
        src/backend/database.py \
        src/backend/fireinfo.py \
index 47d22c8b37573fd08f614edce84044025e8edf5f..9a403faba6dea19b3f8086382560baa62bf2937a 100644 (file)
@@ -19,6 +19,7 @@ import releases
 import settings
 import talk
 
+from . import blog
 from . import zeiterfassung
 
 DEFAULT_CONFIG = StringIO.StringIO("""
@@ -60,6 +61,7 @@ class Backend(object):
                self.releases = releases.Releases(self)
                self.talk = talk.Talk(self)
 
+               self.blog = blog.Blog(self)
                self.zeiterfassung = zeiterfassung.ZeiterfassungClient(self)
 
        def read_config(self, configfile):
diff --git a/src/backend/blog.py b/src/backend/blog.py
new file mode 100644 (file)
index 0000000..f0044c8
--- /dev/null
@@ -0,0 +1,89 @@
+#!/usr/bin/python
+
+from . import misc
+
+class Blog(misc.Object):
+       def _get_post(self, query, *args):
+               res = self.db.get(query, *args)
+
+               if res:
+                       return Post(self.backend, res.id, data=res)
+
+       def _get_posts(self, query, *args):
+               res = self.db.query(query, *args)
+
+               for row in res:
+                       yield Post(self.backend, row.id, data=row)
+
+       def get_by_slug(self, slug):
+               return self._get_post("SELECT * FROM blog \
+                       WHERE slug = %s AND published_at <= NOW()", slug)
+
+       def get_newest(self, limit=None):
+               return self._get_posts("SELECT * FROM blog \
+                       WHERE published_at IS NOT NULL \
+                               AND published_at <= NOW() \
+                       ORDER BY published_at DESC LIMIT %s", limit)
+
+       def get_by_tag(self, tag, limit=None):
+               return self._get_posts("SELECT * FROM blog \
+                       WHERE published_at IS NOT NULL \
+                               AND published_at <= NOW() \
+                               AND %s = ANY(tags) \
+                       ORDER BY published_at DESC LIMIT %s", limit)
+
+       def get_by_author(self, uid, limit=None):
+               return self._get_posts("SELECT * FROM blog \
+                       WHERE author_uid = %s \
+                               AND published_at IS NOT NULL \
+                               AND published_at <= NOW() \
+                       ORDER BY published_at DESC LIMIT %s", uid, limit)
+
+       def search(self, query, limit=None):
+               return self._get_posts("SELECT blog.* FROM blog \
+                       LEFT JOIN blog_search_index search_index ON blog.id = search_index.post_id \
+                       WHERE search_index.document @@ to_tsquery('english', %s) \
+                               ORDER BY ts_rank(search_index.document, to_tsquery('english', %s)) DESC \
+                       LIMIT %s", query, query, limit)
+
+       def refresh(self):
+               """
+                       Needs to be called after a post has been changed
+                       and updates the search index.
+               """
+               self.db.execute("REFRESH MATERIALIZED VIEW blog_search_index")
+
+
+class Post(misc.Object):
+       def init(self, id, data=None):
+               self.id   = id
+               self.data = data
+
+       @property
+       def title(self):
+               return self.data.title
+
+       @property
+       def slug(self):
+               return self.data.slug
+
+       # XXX needs caching
+       @property
+       def author(self):
+               if self.data.author_uid:
+                       return self.backend.accounts.get_by_uid(self.data.author_uid)
+
+       @property
+       def created_at(self):
+               return self.data.created_at
+
+       @property
+       def published_at(self):
+               return self.data.published_at
+
+       @property
+       def html(self):
+               """
+                       Returns this post as rendered HTML
+               """
+               return self.data.html
index 6dc85c88ebd3ca8ca9fc3537db21f3aa55e30dea..c4b0480795c2973990f9566be8e58ca3b39af93d 100644 (file)
@@ -1,12 +1,12 @@
 #!/usr/bin/python
 
 class Object(object):
-       def __init__(self, backend):
+       def __init__(self, backend, *args, **kwargs):
                self.backend = backend
 
-               self.init()
+               self.init(*args, **kwargs)
 
-       def init(self):
+       def init(self, *args, **kwargs):
                """
                        Function for custom initialization.
                """
index 81792aa7d2378a4e2643f278750c5713d1365be5..dc50dcaaf3c40a589ad0c4e74526d3a849a504f6 100644 (file)
@@ -12,7 +12,7 @@
                                        <a href="/post/{{ post.slug }}">{{ post.title }}</a>
                                </strong>
                                <p class="text-muted small">
-                                       {{ locale.format_date(post.published, shorter=True, relative=False) }}
+                                       {{ locale.format_date(post.published_at, shorter=True, relative=False) }}
                                </p>
                        {% end %}
                </div>
index f159bc2a7e6b8630fd6c753810aef5584c0a2551..69eaba3467e0170aa9063ec5062975a106412f56 100644 (file)
@@ -28,9 +28,9 @@
                                <title>{{ post.title }}</title>
                                <link>https://blog.ipfire.org/post/{{ post.slug }}</link>
                                <author>{{ post.author.email }} ({{ post.author.name }})</author>
-                               <pubDate>{{ post.published.strftime("%a, %d %b %Y %H:%M:%S +0200") }}</pubDate>
+                               <pubDate>{{ post.published_at.strftime("%a, %d %b %Y %H:%M:%S +0200") }}</pubDate>
                                <guid isPermaLink="false"></guid>
-                               <description><![CDATA[{% raw post.text %}]]></description>
+                               <description><![CDATA[{% raw post.html %}]]></description>
                        </item>
                {% end %}
        </channel>
index c8ab8e662533c106c7dc1e7d3772aff176ce52d8..5f0bcba6f43106eaf641d41f8b3e50ecc17b2f73 100644 (file)
@@ -3,8 +3,8 @@
 
        <p class="small text-muted">
                {{ _("by") }} <a href="/authors/{{ post.author.uid }}">{{ post.author.name }}</a>,
-               {{ locale.format_date(post.published, shorter=True, relative=False) }}
+               {{ locale.format_date(post.published_at, shorter=True, relative=False) }}
        </p>
 
-       {% raw post.text %}
+       {% raw post.html %}
 </div>
index 635c224d44337890c2901278267c2f48830991e0..f297fdbb729050a5096879e6cbfbc63f23ffc40a 100644 (file)
@@ -9,7 +9,7 @@ from . import ui_modules
 
 class IndexHandler(base.BaseHandler):
        def get(self):
-               posts = self.planet.get_entries(limit=3)
+               posts = self.backend.blog.get_newest(limit=3)
 
                self.render("blog/index.html", posts=posts)
 
@@ -21,7 +21,7 @@ class AuthorHandler(base.BaseHandler):
                        raise tornado.web.HTTPError(404, "User is unknown")
 
                # Get all posts from this author
-               posts = self.planet.get_entries_by_author(author.uid)
+               posts = self.backend.blog.get_by_author(author.uid)
                if not posts:
                        raise tornado.web.HTTPError(404, "User has no posts")
 
@@ -35,7 +35,7 @@ class FeedHandler(base.BaseHandler):
                # Get feed from cache if possible
                feed = self.memcached.get(cache_key)
                if not feed:
-                       posts = self.planet.get_entries(limit=50)
+                       posts = self.backend.blog.get_newest(limit=50)
 
                        # Render the feed
                        feed = self.render_string("blog/feed.xml", posts=posts,
@@ -53,18 +53,18 @@ class FeedHandler(base.BaseHandler):
 
 class PostHandler(base.BaseHandler):
        def get(self, slug):
-               entry = self.planet.get_entry_by_slug(slug)
-               if not entry:
+               post = self.backend.blog.get_by_slug(slug)
+               if not post:
                        raise tornado.web.HTTPError(404)
 
-               self.render("blog/post.html", post=entry)
+               self.render("blog/post.html", post=post)
 
 
 class SearchHandler(base.BaseHandler):
        def get(self):
                q = self.get_argument("q")
 
-               posts = self.planet.search(q)
+               posts = self.backend.blog.search(q, limit=50)
                if not posts:
                        raise tornado.web.HTTPError(404, "Nothing found")
 
@@ -78,4 +78,4 @@ class PostModule(ui_modules.UIModule):
 
 class PostsModule(ui_modules.UIModule):
        def render(self, posts):
-               return self.render_string("blog/modules/posts.html", posts=posts)
+               return self.render_string("blog/modules/posts.html", posts=list(posts))