]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/blog.py
597b6308e454c52cba7d1d46cba081163169d85e
12 from .decorators
import *
14 class Blog(misc
.Object
):
15 def _get_post(self
, query
, *args
):
16 res
= self
.db
.get(query
, *args
)
19 return Post(self
.backend
, res
.id, data
=res
)
21 def _get_posts(self
, query
, *args
):
22 res
= self
.db
.query(query
, *args
)
25 yield Post(self
.backend
, row
.id, data
=row
)
27 def get_by_id(self
, id):
28 return self
._get
_post
("SELECT * FROM blog \
31 def get_by_slug(self
, slug
):
32 return self
._get
_post
("SELECT * FROM blog \
33 WHERE slug = %s", slug
)
35 def get_newest(self
, limit
=None):
36 posts
= self
._get
_posts
("SELECT * FROM blog \
37 WHERE published_at IS NOT NULL \
38 AND published_at <= NOW() \
39 ORDER BY published_at DESC LIMIT %s", limit
)
43 def get_by_tag(self
, tag
, limit
=None):
44 return self
._get
_posts
("SELECT * FROM blog \
45 WHERE published_at IS NOT NULL \
46 AND published_at <= NOW() \
48 ORDER BY published_at DESC LIMIT %s", tag
, limit
)
50 def get_by_year(self
, year
):
51 return self
._get
_posts
("SELECT * FROM blog \
52 WHERE EXTRACT(year FROM published_at) = %s \
53 AND published_at IS NOT NULL \
54 AND published_at <= NOW() \
55 ORDER BY published_at DESC", year
)
57 def get_drafts(self
, author
, limit
=None):
58 return self
._get
_posts
("SELECT * FROM blog \
59 WHERE author_uid = %s \
60 AND (published_at IS NULL OR published_at > NOW()) \
61 ORDER BY COALESCE(updated_at, created_at) DESC LIMIT %s",
64 def search(self
, query
, limit
=None):
65 posts
= self
._get
_posts
("SELECT blog.* FROM blog \
66 LEFT JOIN blog_search_index search_index ON blog.id = search_index.post_id \
67 WHERE search_index.document @@ websearch_to_tsquery('english', %s) \
68 ORDER BY ts_rank(search_index.document, websearch_to_tsquery('english', %s)) DESC \
69 LIMIT %s", query
, query
, limit
)
73 def has_had_recent_activity(self
, **kwargs
):
74 t
= datetime
.timedelta(**kwargs
)
76 res
= self
.db
.get("SELECT COUNT(*) AS count FROM blog \
77 WHERE published_at IS NOT NULL AND published_at BETWEEN NOW() - %s AND NOW()", t
)
79 if res
and res
.count
> 0:
84 def create_post(self
, title
, text
, author
, tags
=[], lang
="markdown"):
86 Creates a new post and returns the resulting Post object
89 html
= self
._render
_text
(text
, lang
=lang
)
91 return self
._get
_post
("INSERT INTO blog(title, slug, text, html, lang, author_uid, tags) \
92 VALUES(%s, %s, %s, %s, %s, %s, %s) RETURNING *", title
, self
._make
_slug
(title
), text
,
93 html
, lang
, author
.uid
, list(tags
))
95 def _make_slug(self
, s
):
96 # Remove any non-ASCII characters
98 s
= unicodedata
.normalize("NFKD", s
)
102 # Remove excessive whitespace
103 s
= re
.sub(r
"[^\w]+", " ", s
)
105 slug
= "-".join(s
.split()).lower()
108 e
= self
.db
.get("SELECT 1 FROM blog WHERE slug = %s", slug
)
116 def _render_text(self
, text
, lang
="markdown"):
117 if lang
== "markdown":
118 renderer
= wiki
.Markdown(
121 wiki
.PrettyLinksExtension(),
132 return renderer
.convert(text
)
134 elif lang
== "textile":
135 return textile
.textile(text
)
142 Needs to be called after a post has been changed
143 and updates the search index.
145 self
.db
.execute("REFRESH MATERIALIZED VIEW CONCURRENTLY blog_search_index")
149 res
= self
.db
.query("SELECT DISTINCT EXTRACT(year FROM published_at)::integer AS year \
150 FROM blog WHERE published_at IS NOT NULL AND published_at <= NOW() \
156 async def announce(self
):
157 posts
= self
._get
_posts
("SELECT * FROM blog \
158 WHERE (published_at IS NOT NULL AND published_at <= NOW()) \
159 AND announced_at IS NULL")
162 await post
.announce()
164 async def update_feeds(self
):
166 Updates all enabled feeds
168 for feed
in self
.db
.query("SELECT * FROM blog_feeds WHERE enabled IS TRUE"):
170 f
= feedparser
.parse(feed
.url
)
171 except Exception as e
:
174 with self
.db
.transaction():
176 self
.db
.execute("UPDATE blog_feeds SET name = %s \
177 WHERE id = %s", f
.feed
.title
, feed
.id)
179 # Walk through all entries
180 for entry
in f
.entries
:
181 # Skip everything without the "blog.ipfire.org" tag
183 tags
= list((t
.term
for t
in entry
.tags
))
185 if not "blog.ipfire.org" in tags
:
187 except AttributeError:
190 # Get link to the posting site
191 link
= entry
.links
[0].href
193 # Check if the entry has already been imported
194 res
= self
.db
.get("SELECT id, (updated_at < %s) AS needs_update \
195 FROM blog WHERE feed_id = %s AND foreign_id = %s",
196 entry
.updated
, feed
.id, entry
.id)
198 # If the post needs to be updated, we do so
200 self
.db
.execute("UPDATE blog SET title = %s, author = %s, \
201 published_at = %s, updated_at = %s, html = %s, link = %s, \
202 tags = %s WHERE id = %s", entry
.title
, entry
.author
,
203 entry
.published
, entry
.updated
, entry
.summary
, link
,
204 feed
.tags
+ tags
, res
.id)
209 # Insert the new post
210 self
.db
.execute("INSERT INTO blog(title, slug, author, \
211 published_at, html, link, tags, updated_at, feed_id, foreign_id) \
212 VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
213 entry
.title
, self
._make
_slug
(entry
.title
), entry
.author
,
214 entry
.published
, entry
.summary
, link
, feed
.tags
+ tags
,
215 entry
.updated
, feed
.id, entry
.id)
217 # Mark feed as updated
218 self
.db
.execute("UPDATE blog_feeds SET last_updated_at = CURRENT_TIMESTAMP \
219 WHERE id = %s" % feed
.id)
221 # Refresh the search index
222 with self
.db
.transaction():
226 class Post(misc
.Object
):
227 def init(self
, id, data
=None):
235 return self
.data
.title
239 return self
.data
.slug
243 if self
.data
.author_uid
:
244 return self
.backend
.accounts
.get_by_uid(self
.data
.author_uid
)
246 return self
.data
.author
249 def created_at(self
):
250 return self
.data
.created_at
254 return self
.data
.lang
259 def published_at(self
):
260 return self
.data
.published_at
262 def is_published(self
):
264 Returns True if the post is already published
266 return self
.published_at
and self
.published_at
<= datetime
.datetime
.now()
268 def publish(self
, when
=None):
269 if self
.is_published():
272 self
.db
.execute("UPDATE blog SET published_at = COALESCE(%s, CURRENT_TIMESTAMP) \
273 WHERE id = %s", when
, self
.id)
275 # Update search indices
276 self
.backend
.blog
.refresh()
281 def updated_at(self
):
282 return self
.data
.updated_at
288 return self
.data
.text
295 Returns this post as rendered HTML
297 return self
.data
.html
or self
.backend
.blog
._render
_text
(self
.text
, lang
=self
.lang
)
301 h
= html2text
.HTML2Text()
302 h
.ignore_links
= True
304 return h
.handle(self
.html
)
310 paragraphs
= self
.plaintext
.split("\n\n")
314 for paragraph
in paragraphs
:
315 excerpt
.append(paragraph
)
317 # Add another paragraph if we encountered a headline
318 if paragraph
.startswith("#"):
321 # End if this paragraph was long enough
322 if len(paragraph
) >= 40:
325 return "\n\n".join(excerpt
)
331 return self
.data
.tags
337 return self
.data
.link
341 return self
.backend
.releases
._get
_release
("SELECT * FROM releases \
342 WHERE published IS NOT NULL AND published <= NOW() AND blog_id = %s", self
.id)
344 def is_editable(self
, user
):
345 # Anonymous users cannot do anything
349 # Admins can edit anything
353 # User must have permission for the blog
354 if not user
.is_blog_author():
357 # Authors can edit their own posts
358 return self
.author
== user
360 def update(self
, title
, text
, tags
=[]):
362 Called to update the content of this post
364 # Update slug when post isn't published yet
365 slug
= self
.backend
.blog
._make
_slug
(title
) \
366 if not self
.is_published() and not self
.title
== title
else self
.slug
368 # Render and cache HTML
369 html
= self
.backend
.blog
._render
_text
(text
, lang
=self
.lang
)
371 self
.db
.execute("UPDATE blog SET title = %s, slug = %s, text = %s, html = %s, \
372 tags = %s, updated_at = CURRENT_TIMESTAMP WHERE id = %s",
373 title
, slug
, text
, html
, list(tags
), self
.id)
384 # Update search index if post is published
385 if self
.is_published():
386 self
.backend
.blog
.refresh()
389 self
.db
.execute("DELETE FROM blog WHERE id = %s", self
.id)
391 # Update search indices
392 self
.backend
.blog
.refresh()
394 async def announce(self
):
396 await self
.backend
.campaigns
.send("blog/messages/announcement", promotional
=False, post
=self
)
398 # Mark this post as announced
399 self
.db
.execute("UPDATE blog SET announced_at = CURRENT_TIMESTAMP \
400 WHERE id = %s", self
.id)