]>
Commit | Line | Data |
---|---|---|
940227cb MT |
1 | #!/usr/bin/python |
2 | ||
67ab72b8 | 3 | import datetime |
27066195 | 4 | import re |
940227cb | 5 | import textile |
27066195 | 6 | import unicodedata |
940227cb | 7 | |
a6dc0bad | 8 | from misc import Object |
940227cb | 9 | |
a6dc0bad MT |
10 | class PlanetEntry(Object): |
11 | def __init__(self, backend, data): | |
12 | Object.__init__(self, backend) | |
8876f3df | 13 | |
a6dc0bad | 14 | self.data = data |
940227cb | 15 | |
60b0917c MT |
16 | def __cmp__(self, other): |
17 | return cmp(self.published, other.published) | |
18 | ||
940227cb MT |
19 | @property |
20 | def id(self): | |
a6dc0bad | 21 | return self.data.id |
940227cb MT |
22 | |
23 | @property | |
24 | def slug(self): | |
a6dc0bad | 25 | return self.data.slug |
940227cb | 26 | |
67ab72b8 MT |
27 | def set_title(self, title): |
28 | if self.title == title: | |
29 | return | |
30 | ||
31 | self.db.execute("UPDATE planet SET title = %s WHERE id = %s", title, self.id) | |
32 | self.data["title"] = title | |
33 | ||
34 | title = property(lambda s: s.data.title, set_title) | |
a6dc0bad MT |
35 | |
36 | @property | |
37 | def url(self): | |
4da2cb99 | 38 | return "https://planet.ipfire.org/post/%s" % self.slug |
940227cb | 39 | |
67ab72b8 MT |
40 | def set_published(self, published): |
41 | if self.published == published: | |
42 | return | |
43 | ||
44 | self.db.execute("UPDATE planet SET published = %s WHERE id = %s", | |
45 | published, self.id) | |
46 | self.data["published"] = published | |
47 | ||
48 | published = property(lambda s: s.data.published, set_published) | |
940227cb | 49 | |
cc3b928d MT |
50 | @property |
51 | def year(self): | |
52 | return self.published.year | |
53 | ||
54 | @property | |
55 | def month(self): | |
56 | return self.published.month | |
57 | ||
940227cb MT |
58 | @property |
59 | def updated(self): | |
a6dc0bad | 60 | return self.data.updated |
940227cb | 61 | |
67ab72b8 | 62 | def get_markdown(self): |
a6dc0bad MT |
63 | return self.data.markdown |
64 | ||
67ab72b8 MT |
65 | def set_markdown(self, markdown): |
66 | if self.markdown == markdown: | |
67 | return | |
68 | ||
69 | markup = self.render(markdown) | |
70 | self.db.execute("UPDATE planet SET markdown = %s, markup = %s WHERE id = %s", | |
71 | markdown, markup, self.id) | |
72 | ||
73 | self.data.update({ | |
74 | "markdown" : markdown, | |
75 | "markup" : markup, | |
76 | }) | |
77 | ||
78 | markdown = property(get_markdown, set_markdown) | |
79 | ||
a6dc0bad MT |
80 | @property |
81 | def markup(self): | |
82 | if self.data.markup: | |
83 | return self.data.markup | |
84 | ||
85 | return self.render(self.markdown) | |
940227cb MT |
86 | |
87 | @property | |
88 | def abstract(self): | |
89 | return self.render(self.markdown, 400) | |
90 | ||
91 | def render(self, text, limit=0): | |
27066195 | 92 | return self.planet.render(text, limit) |
940227cb MT |
93 | |
94 | @property | |
95 | def text(self): | |
a6dc0bad MT |
96 | # Compat for markup |
97 | return self.markup | |
940227cb MT |
98 | |
99 | @property | |
100 | def author(self): | |
66862195 MT |
101 | if not hasattr(self, "_author"): |
102 | self._author = self.accounts.get_by_uid(self.data.author_id) | |
a6dc0bad | 103 | |
66862195 | 104 | return self._author |
940227cb | 105 | |
67ab72b8 MT |
106 | def set_status(self, status): |
107 | if self.status == status: | |
108 | return | |
109 | ||
110 | self.db.execute("UPDATE planet SET status = %s WHERE id = %s", status, self.id) | |
111 | self.data["status"] = status | |
112 | ||
113 | status = property(lambda s: s.data.status, set_status) | |
114 | ||
115 | def is_draft(self): | |
116 | return self.status == "draft" | |
117 | ||
118 | def is_published(self): | |
119 | return self.status == "published" | |
120 | ||
d88b8f41 MT |
121 | def count_view(self, referer=None, location=None): |
122 | self.db.execute("INSERT INTO planet_views(post_id, referer, location) \ | |
123 | VALUES(%s, %s, %s)", self.id, referer, location) | |
c0c55656 | 124 | |
c6ba6949 MT |
125 | if hasattr(self, "_views"): |
126 | self._views += 1 | |
127 | ||
128 | @property | |
129 | def views(self): | |
130 | if not hasattr(self, "_views"): | |
131 | res = self.db.get("SELECT COUNT(*) AS views FROM planet_views \ | |
132 | WHERE post_id = %s", self.id) | |
133 | ||
134 | self._views = res.views | |
135 | ||
136 | return self._views | |
137 | ||
940227cb | 138 | |
a6dc0bad | 139 | class Planet(Object): |
940227cb | 140 | def get_authors(self): |
9068dba1 MT |
141 | query = self.db.query("SELECT DISTINCT author_id FROM planet WHERE status = %s \ |
142 | AND published IS NOT NULL AND published <= NOW()", "published") | |
143 | ||
940227cb | 144 | authors = [] |
9068dba1 | 145 | for author in query: |
a6dc0bad | 146 | author = self.accounts.search(author.author_id) |
940227cb MT |
147 | if author: |
148 | authors.append(author) | |
149 | ||
27066195 | 150 | return sorted(authors) |
940227cb | 151 | |
cc3b928d | 152 | def get_years(self): |
9068dba1 | 153 | res = self.db.query("SELECT DISTINCT EXTRACT(YEAR FROM published)::integer AS year \ |
a6dc0bad | 154 | FROM planet WHERE status = %s ORDER BY year DESC", "published") |
cc3b928d MT |
155 | |
156 | return [row.year for row in res] | |
157 | ||
940227cb MT |
158 | def get_entry_by_slug(self, slug): |
159 | entry = self.db.get("SELECT * FROM planet WHERE slug = %s", slug) | |
9068dba1 | 160 | |
940227cb | 161 | if entry: |
a6dc0bad | 162 | return PlanetEntry(self.backend, entry) |
940227cb | 163 | |
27066195 MT |
164 | def get_entry_by_id(self, id): |
165 | entry = self.db.get("SELECT * FROM planet WHERE id = %s", id) | |
9068dba1 | 166 | |
27066195 | 167 | if entry: |
a6dc0bad | 168 | return PlanetEntry(self.backend, entry) |
27066195 | 169 | |
a6dc0bad MT |
170 | def get_entries(self, limit=3, offset=None, status="published", author_id=None): |
171 | query = "SELECT * FROM planet" | |
172 | args, clauses = [], [] | |
940227cb | 173 | |
a6dc0bad MT |
174 | if status: |
175 | clauses.append("status = %s") | |
176 | args.append(status) | |
940227cb | 177 | |
8570acc8 MT |
178 | if status == "published": |
179 | clauses.append("published <= NOW()") | |
180 | ||
a6dc0bad MT |
181 | if author_id: |
182 | clauses.append("author_id = %s") | |
183 | args.append(author_id) | |
940227cb | 184 | |
a6dc0bad MT |
185 | if clauses: |
186 | query += " WHERE %s" % " AND ".join(clauses) | |
940227cb | 187 | |
a6dc0bad MT |
188 | query += " ORDER BY published DESC" |
189 | ||
190 | # Respect limit and offset | |
191 | if limit: | |
9068dba1 MT |
192 | query += " LIMIT %s" |
193 | args.append(limit) | |
194 | ||
a6dc0bad | 195 | if offset: |
9068dba1 MT |
196 | query += " OFFSET %s" |
197 | args.append(offset) | |
940227cb | 198 | |
a6dc0bad MT |
199 | entries = [] |
200 | for entry in self.db.query(query, *args): | |
201 | entry = PlanetEntry(self.backend, entry) | |
202 | entries.append(entry) | |
940227cb | 203 | |
a6dc0bad | 204 | return entries |
940227cb | 205 | |
a6dc0bad MT |
206 | def get_entries_by_author(self, author_id, limit=None, offset=None): |
207 | return self.get_entries(limit=limit, offset=offset, author_id=author_id) | |
27066195 | 208 | |
cc3b928d MT |
209 | def get_entries_by_year(self, year): |
210 | entries = self.db.query("SELECT * FROM planet \ | |
9068dba1 MT |
211 | WHERE status = %s AND EXTRACT(YEAR FROM published) = %s \ |
212 | ORDER BY published DESC", "published", year) | |
cc3b928d | 213 | |
a6dc0bad | 214 | return [PlanetEntry(self.backend, e) for e in entries] |
d88b8f41 MT |
215 | |
216 | def get_hot_entries(self, days=30, limit=8): | |
217 | entries = self.db.query("WITH hottest AS (SELECT post_id, COUNT(post_id) AS count \ | |
218 | FROM planet_views WHERE \"when\" >= NOW() - INTERVAL '%s days' \ | |
219 | GROUP BY post_id ORDER BY count DESC) SELECT * FROM planet \ | |
220 | LEFT JOIN hottest ON planet.id = hottest.post_id \ | |
221 | WHERE hottest.count IS NOT NULL \ | |
222 | ORDER BY hottest.count DESC LIMIT %s", | |
223 | days, limit) | |
224 | ||
225 | return [PlanetEntry(self.backend, e) for e in entries] | |
cc3b928d | 226 | |
27066195 MT |
227 | def render(self, text, limit=0): |
228 | if limit and len(text) >= limit: | |
229 | text = text[:limit] + "..." | |
a6dc0bad | 230 | |
27066195 MT |
231 | return textile.textile(text) |
232 | ||
233 | def _generate_slug(self, title): | |
234 | slug = unicodedata.normalize("NFKD", title).encode("ascii", "ignore") | |
235 | slug = re.sub(r"[^\w]+", " ", slug) | |
236 | slug = "-".join(slug.lower().strip().split()) | |
237 | ||
238 | if not slug: | |
239 | slug = "entry" | |
240 | ||
241 | while True: | |
242 | e = self.db.get("SELECT * FROM planet WHERE slug = %s", slug) | |
243 | if not e: | |
244 | break | |
245 | slug += "-" | |
246 | ||
247 | return slug | |
248 | ||
3066ac8d | 249 | def create(self, title, markdown, author, status="published", published=None): |
67ab72b8 MT |
250 | slug = self._generate_slug(title) |
251 | markup = self.render(markdown) | |
252 | ||
253 | if published is None: | |
254 | published = datetime.datetime.utcnow() | |
255 | ||
256 | id = self.db.execute("INSERT INTO planet(author_id, slug, title, status, \ | |
3066ac8d | 257 | markdown, markup, published) VALUES(%s, %s, %s, %s, %s, %s, %s) RETURNING id", |
67ab72b8 MT |
258 | author.uid, slug, title, status, markdown, markup, published) |
259 | ||
3066ac8d MT |
260 | if id: |
261 | return self.get_entry_by_id(id) | |
67ab72b8 | 262 | |
27066195 MT |
263 | def update_entry(self, entry): |
264 | self.db.execute("UPDATE planet SET title = %s, markdown = %s WHERE id = %s", | |
265 | entry.title, entry.markdown, entry.id) | |
266 | ||
267 | def save_entry(self, entry): | |
268 | slug = self._generate_slug(entry.title) | |
269 | ||
9068dba1 MT |
270 | id = self.db.execute("INSERT INTO planet(author_id, title, slug, markdown, published) \ |
271 | VALUES(%s, %s, %s, %s, NOW())", entry.author.uid, entry.title, slug, entry.markdown) | |
27066195 | 272 | |
00af5cf1 MT |
273 | return id |
274 | ||
2bdd073f | 275 | def search(self, what): |
fa7e1a0a MT |
276 | res = self.db.query("WITH \ |
277 | q AS (SELECT plainto_tsquery(%s, %s) AS query), \ | |
278 | ranked AS (SELECT id, query, ts_rank_cd(to_tsvector(%s, markdown), query) AS rank \ | |
279 | FROM planet, q WHERE markdown @@ query ORDER BY rank DESC) \ | |
280 | SELECT *, ts_headline(markup, ranked.query, 'MinWords=100, MaxWords=110') AS markup FROM planet \ | |
281 | JOIN ranked ON planet.id = ranked.id \ | |
282 | WHERE status = %s AND published IS NOT NULL AND published <= NOW() \ | |
60b0917c | 283 | ORDER BY ranked DESC LIMIT 25", |
fa7e1a0a MT |
284 | "english", what, "english", "published") |
285 | ||
286 | return [PlanetEntry(self.backend, e) for e in res] |