]> git.ipfire.org Git - ipfire.org.git/blame - src/backend/blog.py
Fix whitespace error
[ipfire.org.git] / src / backend / blog.py
CommitLineData
0a6875dc
MT
1#!/usr/bin/python
2
541c952b 3import datetime
c70a7c29
MT
4import feedparser
5import re
7e64f6a3 6import textile
c70a7c29 7import unicodedata
7e64f6a3 8
0a6875dc
MT
9from . import misc
10
11class Blog(misc.Object):
12 def _get_post(self, query, *args):
13 res = self.db.get(query, *args)
14
15 if res:
16 return Post(self.backend, res.id, data=res)
17
18 def _get_posts(self, query, *args):
19 res = self.db.query(query, *args)
20
21 for row in res:
22 yield Post(self.backend, row.id, data=row)
23
487417ad
MT
24 def get_by_id(self, id):
25 return self._get_post("SELECT * FROM blog \
26 WHERE id = %s", id)
27
df157ede
MT
28 def get_by_slug(self, slug, published=True):
29 if published:
30 return self._get_post("SELECT * FROM blog \
31 WHERE slug = %s AND published_at <= NOW()", slug)
32
0a6875dc 33 return self._get_post("SELECT * FROM blog \
df157ede
MT
34 WHERE slug = %s", slug)
35
0a6875dc
MT
36 def get_newest(self, limit=None):
37 return self._get_posts("SELECT * FROM blog \
38 WHERE published_at IS NOT NULL \
39 AND published_at <= NOW() \
40 ORDER BY published_at DESC LIMIT %s", limit)
41
42 def get_by_tag(self, tag, limit=None):
43 return self._get_posts("SELECT * FROM blog \
44 WHERE published_at IS NOT NULL \
45 AND published_at <= NOW() \
46 AND %s = ANY(tags) \
4bde7f18 47 ORDER BY published_at DESC LIMIT %s", tag, limit)
0a6875dc 48
cdf85ee7 49 def get_by_author(self, author, limit=None):
0a6875dc 50 return self._get_posts("SELECT * FROM blog \
cdf85ee7 51 WHERE (author = %s OR author_uid = %s) \
0a6875dc
MT
52 AND published_at IS NOT NULL \
53 AND published_at <= NOW() \
cdf85ee7
MT
54 ORDER BY published_at DESC LIMIT %s",
55 author.name, author.uid, limit)
0a6875dc 56
7e64f6a3
MT
57 def get_by_year(self, year):
58 return self._get_posts("SELECT * FROM blog \
59 WHERE EXTRACT(year FROM published_at) = %s \
60 AND published_at IS NOT NULL \
61 AND published_at <= NOW() \
62 ORDER BY published_at DESC", year)
63
0b342a05
MT
64 def get_drafts(self, author=None, limit=None):
65 if author:
66 return self._get_posts("SELECT * FROM blog \
67 WHERE author_uid = %s \
68 AND (published_at IS NULL OR published_at > NOW()) \
69 ORDER BY COALESCE(updated_at, created_at) DESC LIMIT %s",
70 author.uid, limit)
71
72 return self._get_posts("SELECT * FROM blog \
73 WHERE (published_at IS NULL OR published_at > NOW()) \
74 ORDER BY COALESCE(updated_at, created_at) DESC LIMIT %s", limit)
75
0a6875dc
MT
76 def search(self, query, limit=None):
77 return self._get_posts("SELECT blog.* FROM blog \
78 LEFT JOIN blog_search_index search_index ON blog.id = search_index.post_id \
79 WHERE search_index.document @@ to_tsquery('english', %s) \
80 ORDER BY ts_rank(search_index.document, to_tsquery('english', %s)) DESC \
81 LIMIT %s", query, query, limit)
82
541c952b
MT
83 def create_post(self, title, text, author, tags=[]):
84 """
85 Creates a new post and returns the resulting Post object
86 """
87 return self._get_post("INSERT INTO blog(title, slug, text, author_uid, tags) \
88 VALUES(%s, %s, %s, %s, %s) RETURNING *", title, self._make_slug(title),
89 text, author.uid, list(tags))
90
c70a7c29
MT
91 def _make_slug(self, s):
92 # Remove any non-ASCII characters
93 try:
94 s = unicodedata.normalize("NFKD", s)
95 except TypeError:
96 pass
97
98 # Remove excessive whitespace
99 s = re.sub(r"[^\w]+", " ", s)
100
101 slug = "-".join(s.split()).lower()
102
103 while True:
104 e = self.db.get("SELECT 1 FROM blog WHERE slug = %s", slug)
105 if not e:
106 break
107
108 slug += "-"
109
110 return slug
111
0a6875dc
MT
112 def refresh(self):
113 """
114 Needs to be called after a post has been changed
115 and updates the search index.
116 """
117 self.db.execute("REFRESH MATERIALIZED VIEW blog_search_index")
118
7e64f6a3
MT
119 @property
120 def years(self):
121 res = self.db.query("SELECT DISTINCT EXTRACT(year FROM published_at)::integer AS year \
122 FROM blog WHERE published_at IS NOT NULL AND published_at <= NOW() \
123 ORDER BY year DESC")
124
125 for row in res:
126 yield row.year
127
c70a7c29
MT
128 def update_feeds(self):
129 """
130 Updates all enabled feeds
131 """
132 for feed in self.db.query("SELECT * FROM blog_feeds WHERE enabled IS TRUE"):
133 try:
134 f = feedparser.parse(feed.url)
135 except Exception as e:
136 raise e
137
138 with self.db.transaction():
139 # Update name
140 self.db.execute("UPDATE blog_feeds SET name = %s \
141 WHERE id = %s", f.feed.title, feed.id)
142
143 # Walk through all entries
144 for entry in f.entries:
145 # Skip everything without the "blog.ipfire.org" tag
146 try:
147 tags = list((t.term for t in entry.tags))
148
149 if not "blog.ipfire.org" in tags:
150 continue
151 except AttributeError:
152 continue
153
154 # Get link to the posting site
155 link = entry.links[0].href
156
157 # Check if the entry has already been imported
158 res = self.db.get("SELECT id, (updated_at < %s) AS needs_update \
159 FROM blog WHERE feed_id = %s AND foreign_id = %s",
160 entry.updated, feed.id, entry.id)
161 if res:
162 # If the post needs to be updated, we do so
163 if res.needs_update:
164 self.db.execute("UPDATE blog SET title = %s, author = %s, \
165 published_at = %s, updated_at = %s, html = %s, link = %s, \
166 tags = %s WHERE id = %s", entry.title, entry.author,
167 entry.published, entry.updated, entry.summary, link,
168 feed.tags + tags, res.id)
169
170 # Done here
171 continue
172
173 # Insert the new post
174 self.db.execute("INSERT INTO blog(title, slug, author, \
175 published_at, html, link, tags, updated_at, feed_id, foreign_id) \
176 VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
177 entry.title, self._make_slug(entry.title), entry.author,
178 entry.published, entry.summary, link, feed.tags + tags,
179 entry.updated, feed.id, entry.id)
180
20277bf5
MT
181 # Mark feed as updated
182 self.db.execute("UPDATE blog_feeds SET last_updated_at = CURRENT_TIMESTAMP \
183 WHERE id = %s" % feed.id)
184
c70a7c29
MT
185 # Refresh the search index
186 with self.db.transaction():
187 self.refresh()
188
0a6875dc
MT
189
190class Post(misc.Object):
191 def init(self, id, data=None):
192 self.id = id
193 self.data = data
194
541c952b
MT
195 # Title
196
197 def get_title(self):
0a6875dc
MT
198 return self.data.title
199
541c952b
MT
200 def set_title(self, title):
201 self.db.execute("UPDATE blog SET title = %s \
202 WHERE id = %s", title, self.id)
203
204 # Update slug if post is not published, yet
205 if not self.is_published():
206 self.db.execute("UPDATE blog SET slug = %s \
207 WHERE id = %s", self.backend.blog._make_slug(title), self.id)
208
209 title = property(get_title, set_title)
210
0a6875dc
MT
211 @property
212 def slug(self):
213 return self.data.slug
214
215 # XXX needs caching
216 @property
217 def author(self):
218 if self.data.author_uid:
219 return self.backend.accounts.get_by_uid(self.data.author_uid)
220
cdf85ee7
MT
221 return self.data.author
222
0a6875dc
MT
223 @property
224 def created_at(self):
225 return self.data.created_at
226
541c952b
MT
227 # Published?
228
0a6875dc
MT
229 @property
230 def published_at(self):
231 return self.data.published_at
232
541c952b
MT
233 def is_published(self):
234 """
235 Returns True if the post is already published
236 """
237 return self.published_at and self.published_at <= datetime.datetime.now()
238
239 def publish(self):
240 if self.is_published():
241 return
242
243 self.db.execute("UPDATE blog SET published_at = NOW() \
244 WHERE id = %s", self.id)
245
246 # Update search indices
247 self.backend.blog.refresh()
248
249 # Updated?
250
7e64f6a3 251 @property
541c952b
MT
252 def updated_at(self):
253 return self.data.updated_at
254
255 def updated(self):
256 self.db.execute("UPDATE blog SET updated_at = NOW() \
257 WHERE id = %s", self.id)
258
259 # Update search indices
260 self.backend.blog.refresh()
261
262 # Text
263
264 def get_text(self):
265 return self.data.text
266
267 def set_text(self, text):
268 self.db.execute("UPDATE blog SET text = %s \
269 WHERE id = %s", text, self.id)
270
271 text = property(get_text, set_text)
272
273 # HTML
7e64f6a3 274
0a6875dc
MT
275 @property
276 def html(self):
277 """
278 Returns this post as rendered HTML
279 """
541c952b 280 return self.data.html or textile.textile(self.text.decode("utf-8"))
8ebc98d4 281
541c952b
MT
282 # Tags
283
284 def get_tags(self):
8ebc98d4 285 return self.data.tags
1e76fec4 286
541c952b
MT
287 def set_tags(self, tags):
288 self.db.execute("UPDATE blog SET tags = %s \
289 WHERE id = %s", list(tags), self.id)
290
291 tags = property(get_tags, set_tags)
292
1e76fec4
MT
293 @property
294 def link(self):
295 return self.data.link
984e4e7b
MT
296
297 # XXX needs caching
298 @property
299 def release(self):
300 return self.backend.releases._get_release("SELECT * FROM releases \
301 WHERE published IS NOT NULL AND published <= NOW() AND blog_id = %s", self.id)