]> git.ipfire.org Git - ipfire.org.git/blobdiff - src/backend/blog.py
blog: Allow deleting posts
[ipfire.org.git] / src / backend / blog.py
index 3a0e2e098d45264522fdfe62da3853486625025c..78a2c6d8e30eb1961e3a5b09c20274346a3ad306 100644 (file)
@@ -2,11 +2,25 @@
 
 import datetime
 import feedparser
+import markdown2
 import re
 import textile
 import unicodedata
 
 from . import misc
+from .decorators import *
+
+# Used to automatically link some things
+link_patterns = (
+       # Find bug reports
+       (re.compile(r"(?:#(\d+))", re.I), r"https://bugzilla.ipfire.org/show_bug.cgi?id=\1"),
+
+       # Email Addresses
+       (re.compile(r"([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)"), r"mailto:\1"),
+
+       # CVE Numbers
+       (re.compile(r"(?:CVE)[\s\-](\d{4}\-\d+)"), r"http://cve.mitre.org/cgi-bin/cvename.cgi?name=\1"),
+)
 
 class Blog(misc.Object):
        def _get_post(self, query, *args):
@@ -74,19 +88,43 @@ class Blog(misc.Object):
                        ORDER BY COALESCE(updated_at, created_at) DESC LIMIT %s", limit)
 
        def search(self, query, limit=None):
+               query = self._parse_search_query(query)
+
                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 create_post(self, title, text, author, tags=[]):
+       def _parse_search_query(self, query):
+               q = []
+               for word in query.split():
+                       # Is this lexeme negated?
+                       negated = word.startswith("!")
+
+                       # Remove any special characters
+                       word = re.sub(r"\W+", "", word, flags=re.UNICODE)
+                       if not word:
+                               continue
+
+                       # Restore negation
+                       if negated:
+                               word = "!%s" % word
+
+                       q.append(word)
+
+               return " & ".join(q)
+
+       def create_post(self, title, text, author, tags=[], lang="markdown"):
                """
                        Creates a new post and returns the resulting Post object
                """
-               return self._get_post("INSERT INTO blog(title, slug, text, author_uid, tags) \
-                       VALUES(%s, %s, %s, %s, %s) RETURNING *", title, self._make_slug(title),
-                       text, author.uid, list(tags))
+               # Pre-render HTML
+               html = self._render_text(text, lang=lang)
+
+               return self._get_post("INSERT INTO blog(title, slug, text, html, lang, author_uid, tags) \
+                       VALUES(%s, %s, %s, %s, %s, %s, %s) RETURNING *", title, self._make_slug(title), text,
+                       html, lang, author.uid, list(tags))
 
        def _make_slug(self, s):
                # Remove any non-ASCII characters
@@ -109,6 +147,16 @@ class Blog(misc.Object):
 
                return slug
 
+       def _render_text(self, text, lang="markdown"):
+               if lang == "markdown":
+                       return markdown2.markdown(text, link_patterns=link_patterns,
+                               extras=["footnotes", "link-patterns", "wiki-tables"])
+
+               elif lang == "textile":
+                       return textile.textile(text)
+
+               return text
+
        def refresh(self):
                """
                        Needs to be called after a post has been changed
@@ -194,26 +242,15 @@ class Post(misc.Object):
 
        # Title
 
-       def get_title(self):
+       @property
+       def title(self):
                return self.data.title
 
-       def set_title(self, title):
-               self.db.execute("UPDATE blog SET title = %s \
-                       WHERE id = %s", title, self.id)
-
-               # Update slug if post is not published, yet
-               if not self.is_published():
-                       self.db.execute("UPDATE blog SET slug = %s \
-                               WHERE id = %s", self.backend.blog._make_slug(title), self.id)
-
-       title = property(get_title, set_title)
-
        @property
        def slug(self):
                return self.data.slug
 
-       # XXX needs caching
-       @property
+       @lazy_property
        def author(self):
                if self.data.author_uid:
                        return self.backend.accounts.get_by_uid(self.data.author_uid)
@@ -224,6 +261,10 @@ class Post(misc.Object):
        def created_at(self):
                return self.data.created_at
 
+       @property
+       def lang(self):
+               return self.data.lang
+
        # Published?
 
        @property
@@ -236,12 +277,12 @@ class Post(misc.Object):
                """
                return self.published_at and self.published_at <= datetime.datetime.now()
 
-       def publish(self):
+       def publish(self, when=None):
                if self.is_published():
                        return
 
-               self.db.execute("UPDATE blog SET published_at = NOW() \
-                       WHERE id = %s", self.id)
+               self.db.execute("UPDATE blog SET published_at = COALESCE(%s, CURRENT_TIMESTAMP) \
+                       WHERE id = %s", when, self.id)
 
                # Update search indices
                self.backend.blog.refresh()
@@ -252,50 +293,34 @@ class Post(misc.Object):
        def updated_at(self):
                return self.data.updated_at
 
-       def updated(self):
-               self.db.execute("UPDATE blog SET updated_at = NOW() \
-                       WHERE id = %s", self.id)
-
-               # Update search indices
-               self.backend.blog.refresh()
-
        # Text
 
-       def get_text(self):
+       @property
+       def text(self):
                return self.data.text
 
-       def set_text(self, text):
-               self.db.execute("UPDATE blog SET text = %s \
-                       WHERE id = %s", text, self.id)
-
-       text = property(get_text, set_text)
-
        # HTML
 
-       @property
+       @lazy_property
        def html(self):
                """
                        Returns this post as rendered HTML
                """
-               return self.data.html or textile.textile(self.text.decode("utf-8"))
+               return self.data.html or self.backend.blog._render_text(self.text, lang=self.lang)
 
        # Tags
 
-       def get_tags(self):
+       @property
+       def tags(self):
                return self.data.tags
 
-       def set_tags(self, tags):
-               self.db.execute("UPDATE blog SET tags = %s \
-                       WHERE id = %s", list(tags), self.id)
-
-       tags = property(get_tags, set_tags)
+       # Link
 
        @property
        def link(self):
                return self.data.link
 
-       # XXX needs caching
-       @property
+       @lazy_property
        def release(self):
                return self.backend.releases._get_release("SELECT * FROM releases \
                        WHERE published IS NOT NULL AND published <= NOW() AND blog_id = %s", self.id)
@@ -303,3 +328,37 @@ class Post(misc.Object):
        def is_editable(self, editor):
                # Authors can edit their own posts
                return self.author == editor
+
+       def update(self, title, text, tags=[]):
+               """
+                       Called to update the content of this post
+               """
+               # Update slug when post isn't published yet
+               slug = self.backend.blog._make_slug(title) \
+                       if not self.is_published() and not self.title == title else self.slug
+
+               # Render and cache HTML
+               html = self.backend.blog._render_text(text, lang=self.lang)
+
+               self.db.execute("UPDATE blog SET title = %s, slug = %s, text = %s, html = %s, \
+                       tags = %s, updated_at = CURRENT_TIMESTAMP WHERE id = %s",
+                       title, slug, text, html, list(tags), self.id)
+
+               # Update cache
+               self.data.update({
+                       "title" : title,
+                       "slug"  : slug,
+                       "text"  : text,
+                       "html"  : html,
+                       "tags"  : tags,
+               })
+
+               # Update search index if post is published
+               if self.is_published():
+                       self.backend.blog.refresh()
+
+       def delete(self):
+               self.db.execute("DELETE FROM blog WHERE id = %s", self.id)
+
+               # Update search indices
+               self.backend.blog.refresh()