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