]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/blog.py
11 from .decorators
import *
13 # Used to automatically link some things
16 (re
.compile(r
"(?:#(\d+))", re
.I
), r
"https://bugzilla.ipfire.org/show_bug.cgi?id=\1"),
19 (re
.compile(r
"([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)"), r
"mailto:\1"),
22 (re
.compile(r
"(?:CVE)[\s\-](\d{4}\-\d+)"), r
"http://cve.mitre.org/cgi-bin/cvename.cgi?name=\1"),
25 class Blog(misc
.Object
):
26 def _get_post(self
, query
, *args
):
27 res
= self
.db
.get(query
, *args
)
30 return Post(self
.backend
, res
.id, data
=res
)
32 def _get_posts(self
, query
, *args
):
33 res
= self
.db
.query(query
, *args
)
36 yield Post(self
.backend
, row
.id, data
=row
)
38 def get_by_id(self
, id):
39 return self
._get
_post
("SELECT * FROM blog \
42 def get_by_slug(self
, slug
, published
=True):
44 return self
._get
_post
("SELECT * FROM blog \
45 WHERE slug = %s AND published_at <= NOW()", slug
)
47 return self
._get
_post
("SELECT * FROM blog \
48 WHERE slug = %s", slug
)
50 def get_newest(self
, limit
=None):
51 return self
._get
_posts
("SELECT * FROM blog \
52 WHERE published_at IS NOT NULL \
53 AND published_at <= NOW() \
54 ORDER BY published_at DESC LIMIT %s", limit
)
56 def get_by_tag(self
, tag
, limit
=None):
57 return self
._get
_posts
("SELECT * FROM blog \
58 WHERE published_at IS NOT NULL \
59 AND published_at <= NOW() \
61 ORDER BY published_at DESC LIMIT %s", tag
, limit
)
63 def get_by_author(self
, author
, limit
=None):
64 return self
._get
_posts
("SELECT * FROM blog \
65 WHERE (author = %s OR author_uid = %s) \
66 AND published_at IS NOT NULL \
67 AND published_at <= NOW() \
68 ORDER BY published_at DESC LIMIT %s",
69 author
.name
, author
.uid
, limit
)
71 def get_by_year(self
, year
):
72 return self
._get
_posts
("SELECT * FROM blog \
73 WHERE EXTRACT(year FROM published_at) = %s \
74 AND published_at IS NOT NULL \
75 AND published_at <= NOW() \
76 ORDER BY published_at DESC", year
)
78 def get_drafts(self
, author
=None, limit
=None):
80 return self
._get
_posts
("SELECT * FROM blog \
81 WHERE author_uid = %s \
82 AND (published_at IS NULL OR published_at > NOW()) \
83 ORDER BY COALESCE(updated_at, created_at) DESC LIMIT %s",
86 return self
._get
_posts
("SELECT * FROM blog \
87 WHERE (published_at IS NULL OR published_at > NOW()) \
88 ORDER BY COALESCE(updated_at, created_at) DESC LIMIT %s", limit
)
90 def search(self
, query
, limit
=None):
91 query
= self
._parse
_search
_query
(query
)
93 return self
._get
_posts
("SELECT blog.* FROM blog \
94 LEFT JOIN blog_search_index search_index ON blog.id = search_index.post_id \
95 WHERE search_index.document @@ to_tsquery('english', %s) \
96 ORDER BY ts_rank(search_index.document, to_tsquery('english', %s)) DESC \
97 LIMIT %s", query
, query
, limit
)
99 def _parse_search_query(self
, query
):
101 for word
in query
.split():
102 # Is this lexeme negated?
103 negated
= word
.startswith("!")
105 # Remove any special characters
106 word
= re
.sub(r
"\W+", "", word
, flags
=re
.UNICODE
)
118 def create_post(self
, title
, text
, author
, tags
=[], lang
="markdown"):
120 Creates a new post and returns the resulting Post object
123 html
= self
._render
_text
(text
, lang
=lang
)
125 return self
._get
_post
("INSERT INTO blog(title, slug, text, html, lang, author_uid, tags) \
126 VALUES(%s, %s, %s, %s, %s, %s, %s) RETURNING *", title
, self
._make
_slug
(title
), text
,
127 html
, lang
, author
.uid
, list(tags
))
129 def _make_slug(self
, s
):
130 # Remove any non-ASCII characters
132 s
= unicodedata
.normalize("NFKD", s
)
136 # Remove excessive whitespace
137 s
= re
.sub(r
"[^\w]+", " ", s
)
139 slug
= "-".join(s
.split()).lower()
142 e
= self
.db
.get("SELECT 1 FROM blog WHERE slug = %s", slug
)
150 def _render_text(self
, text
, lang
="markdown"):
151 if lang
== "markdown":
152 return markdown2
.markdown(text
, link_patterns
=link_patterns
,
153 extras
=["footnotes", "link-patterns", "wiki-tables"])
155 elif lang
== "textile":
156 return textile
.textile(text
)
162 Needs to be called after a post has been changed
163 and updates the search index.
165 self
.db
.execute("REFRESH MATERIALIZED VIEW blog_search_index")
169 res
= self
.db
.query("SELECT DISTINCT EXTRACT(year FROM published_at)::integer AS year \
170 FROM blog WHERE published_at IS NOT NULL AND published_at <= NOW() \
176 def update_feeds(self
):
178 Updates all enabled feeds
180 for feed
in self
.db
.query("SELECT * FROM blog_feeds WHERE enabled IS TRUE"):
182 f
= feedparser
.parse(feed
.url
)
183 except Exception as e
:
186 with self
.db
.transaction():
188 self
.db
.execute("UPDATE blog_feeds SET name = %s \
189 WHERE id = %s", f
.feed
.title
, feed
.id)
191 # Walk through all entries
192 for entry
in f
.entries
:
193 # Skip everything without the "blog.ipfire.org" tag
195 tags
= list((t
.term
for t
in entry
.tags
))
197 if not "blog.ipfire.org" in tags
:
199 except AttributeError:
202 # Get link to the posting site
203 link
= entry
.links
[0].href
205 # Check if the entry has already been imported
206 res
= self
.db
.get("SELECT id, (updated_at < %s) AS needs_update \
207 FROM blog WHERE feed_id = %s AND foreign_id = %s",
208 entry
.updated
, feed
.id, entry
.id)
210 # If the post needs to be updated, we do so
212 self
.db
.execute("UPDATE blog SET title = %s, author = %s, \
213 published_at = %s, updated_at = %s, html = %s, link = %s, \
214 tags = %s WHERE id = %s", entry
.title
, entry
.author
,
215 entry
.published
, entry
.updated
, entry
.summary
, link
,
216 feed
.tags
+ tags
, res
.id)
221 # Insert the new post
222 self
.db
.execute("INSERT INTO blog(title, slug, author, \
223 published_at, html, link, tags, updated_at, feed_id, foreign_id) \
224 VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
225 entry
.title
, self
._make
_slug
(entry
.title
), entry
.author
,
226 entry
.published
, entry
.summary
, link
, feed
.tags
+ tags
,
227 entry
.updated
, feed
.id, entry
.id)
229 # Mark feed as updated
230 self
.db
.execute("UPDATE blog_feeds SET last_updated_at = CURRENT_TIMESTAMP \
231 WHERE id = %s" % feed
.id)
233 # Refresh the search index
234 with self
.db
.transaction():
238 class Post(misc
.Object
):
239 def init(self
, id, data
=None):
247 return self
.data
.title
251 return self
.data
.slug
255 if self
.data
.author_uid
:
256 return self
.backend
.accounts
.get_by_uid(self
.data
.author_uid
)
258 return self
.data
.author
261 def created_at(self
):
262 return self
.data
.created_at
266 return self
.data
.lang
271 def published_at(self
):
272 return self
.data
.published_at
274 def is_published(self
):
276 Returns True if the post is already published
278 return self
.published_at
and self
.published_at
<= datetime
.datetime
.now()
280 def publish(self
, when
=None):
281 if self
.is_published():
284 self
.db
.execute("UPDATE blog SET published_at = COALESCE(%s, CURRENT_TIMESTAMP) \
285 WHERE id = %s", when
, self
.id)
287 # Update search indices
288 self
.backend
.blog
.refresh()
293 def updated_at(self
):
294 return self
.data
.updated_at
300 return self
.data
.text
307 Returns this post as rendered HTML
309 return self
.data
.html
or self
.backend
.blog
._render
_text
(self
.text
, lang
=self
.lang
)
315 return self
.data
.tags
321 return self
.data
.link
325 return self
.backend
.releases
._get
_release
("SELECT * FROM releases \
326 WHERE published IS NOT NULL AND published <= NOW() AND blog_id = %s", self
.id)
328 def is_editable(self
, editor
):
329 # Authors can edit their own posts
330 return self
.author
== editor
332 def update(self
, title
, text
, tags
=[]):
334 Called to update the content of this post
336 # Update slug when post isn't published yet
337 slug
= self
.backend
.blog
._make
_slug
(title
) \
338 if not self
.is_published() and not self
.title
== title
else self
.slug
340 # Render and cache HTML
341 html
= self
.backend
.blog
._render
_text
(text
, lang
=self
.lang
)
343 self
.db
.execute("UPDATE blog SET title = %s, slug = %s, text = %s, html = %s, \
344 tags = %s, updated_at = CURRENT_TIMESTAMP WHERE id = %s",
345 title
, slug
, text
, html
, list(tags
), self
.id)
356 # Update search index if post is published
357 if self
.is_published():
358 self
.backend
.blog
.refresh()
361 self
.db
.execute("DELETE FROM blog WHERE id = %s", self
.id)
363 # Update search indices
364 self
.backend
.blog
.refresh()