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 \
import settings
import talk
+from . import blog
from . import zeiterfassung
DEFAULT_CONFIG = StringIO.StringIO("""
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):
--- /dev/null
+#!/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
#!/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.
"""
<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>
<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>
<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>
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)
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")
# 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,
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")
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))