]>
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 | |
940227cb | 122 | |
a6dc0bad | 123 | class Planet(Object): |
940227cb | 124 | def get_authors(self): |
9068dba1 MT |
125 | query = self.db.query("SELECT DISTINCT author_id FROM planet WHERE status = %s \ |
126 | AND published IS NOT NULL AND published <= NOW()", "published") | |
127 | ||
940227cb | 128 | authors = [] |
9068dba1 | 129 | for author in query: |
a6dc0bad | 130 | author = self.accounts.search(author.author_id) |
940227cb MT |
131 | if author: |
132 | authors.append(author) | |
133 | ||
27066195 | 134 | return sorted(authors) |
940227cb | 135 | |
cc3b928d | 136 | def get_years(self): |
9068dba1 | 137 | res = self.db.query("SELECT DISTINCT EXTRACT(YEAR FROM published)::integer AS year \ |
a6dc0bad | 138 | FROM planet WHERE status = %s ORDER BY year DESC", "published") |
cc3b928d MT |
139 | |
140 | return [row.year for row in res] | |
141 | ||
940227cb MT |
142 | def get_entry_by_slug(self, slug): |
143 | entry = self.db.get("SELECT * FROM planet WHERE slug = %s", slug) | |
9068dba1 | 144 | |
940227cb | 145 | if entry: |
a6dc0bad | 146 | return PlanetEntry(self.backend, entry) |
940227cb | 147 | |
27066195 MT |
148 | def get_entry_by_id(self, id): |
149 | entry = self.db.get("SELECT * FROM planet WHERE id = %s", id) | |
9068dba1 | 150 | |
27066195 | 151 | if entry: |
a6dc0bad | 152 | return PlanetEntry(self.backend, entry) |
27066195 | 153 | |
a6dc0bad MT |
154 | def get_entries(self, limit=3, offset=None, status="published", author_id=None): |
155 | query = "SELECT * FROM planet" | |
156 | args, clauses = [], [] | |
940227cb | 157 | |
a6dc0bad MT |
158 | if status: |
159 | clauses.append("status = %s") | |
160 | args.append(status) | |
940227cb | 161 | |
8570acc8 MT |
162 | if status == "published": |
163 | clauses.append("published <= NOW()") | |
164 | ||
a6dc0bad MT |
165 | if author_id: |
166 | clauses.append("author_id = %s") | |
167 | args.append(author_id) | |
940227cb | 168 | |
a6dc0bad MT |
169 | if clauses: |
170 | query += " WHERE %s" % " AND ".join(clauses) | |
940227cb | 171 | |
a6dc0bad MT |
172 | query += " ORDER BY published DESC" |
173 | ||
174 | # Respect limit and offset | |
175 | if limit: | |
9068dba1 MT |
176 | query += " LIMIT %s" |
177 | args.append(limit) | |
178 | ||
a6dc0bad | 179 | if offset: |
9068dba1 MT |
180 | query += " OFFSET %s" |
181 | args.append(offset) | |
940227cb | 182 | |
a6dc0bad MT |
183 | entries = [] |
184 | for entry in self.db.query(query, *args): | |
185 | entry = PlanetEntry(self.backend, entry) | |
186 | entries.append(entry) | |
940227cb | 187 | |
a6dc0bad | 188 | return entries |
940227cb | 189 | |
a6dc0bad MT |
190 | def get_entries_by_author(self, author_id, limit=None, offset=None): |
191 | return self.get_entries(limit=limit, offset=offset, author_id=author_id) | |
27066195 | 192 | |
cc3b928d MT |
193 | def get_entries_by_year(self, year): |
194 | entries = self.db.query("SELECT * FROM planet \ | |
9068dba1 MT |
195 | WHERE status = %s AND EXTRACT(YEAR FROM published) = %s \ |
196 | ORDER BY published DESC", "published", year) | |
cc3b928d | 197 | |
a6dc0bad | 198 | return [PlanetEntry(self.backend, e) for e in entries] |
d88b8f41 MT |
199 | |
200 | def get_hot_entries(self, days=30, limit=8): | |
201 | entries = self.db.query("WITH hottest AS (SELECT post_id, COUNT(post_id) AS count \ | |
202 | FROM planet_views WHERE \"when\" >= NOW() - INTERVAL '%s days' \ | |
203 | GROUP BY post_id ORDER BY count DESC) SELECT * FROM planet \ | |
204 | LEFT JOIN hottest ON planet.id = hottest.post_id \ | |
205 | WHERE hottest.count IS NOT NULL \ | |
206 | ORDER BY hottest.count DESC LIMIT %s", | |
207 | days, limit) | |
208 | ||
209 | return [PlanetEntry(self.backend, e) for e in entries] | |
cc3b928d | 210 | |
27066195 MT |
211 | def render(self, text, limit=0): |
212 | if limit and len(text) >= limit: | |
213 | text = text[:limit] + "..." | |
a6dc0bad | 214 | |
27066195 MT |
215 | return textile.textile(text) |
216 | ||
217 | def _generate_slug(self, title): | |
218 | slug = unicodedata.normalize("NFKD", title).encode("ascii", "ignore") | |
219 | slug = re.sub(r"[^\w]+", " ", slug) | |
220 | slug = "-".join(slug.lower().strip().split()) | |
221 | ||
222 | if not slug: | |
223 | slug = "entry" | |
224 | ||
225 | while True: | |
226 | e = self.db.get("SELECT * FROM planet WHERE slug = %s", slug) | |
227 | if not e: | |
228 | break | |
229 | slug += "-" | |
230 | ||
231 | return slug | |
232 | ||
3066ac8d | 233 | def create(self, title, markdown, author, status="published", published=None): |
67ab72b8 MT |
234 | slug = self._generate_slug(title) |
235 | markup = self.render(markdown) | |
236 | ||
237 | if published is None: | |
238 | published = datetime.datetime.utcnow() | |
239 | ||
240 | id = self.db.execute("INSERT INTO planet(author_id, slug, title, status, \ | |
3066ac8d | 241 | markdown, markup, published) VALUES(%s, %s, %s, %s, %s, %s, %s) RETURNING id", |
67ab72b8 MT |
242 | author.uid, slug, title, status, markdown, markup, published) |
243 | ||
3066ac8d MT |
244 | if id: |
245 | return self.get_entry_by_id(id) | |
67ab72b8 | 246 | |
27066195 MT |
247 | def update_entry(self, entry): |
248 | self.db.execute("UPDATE planet SET title = %s, markdown = %s WHERE id = %s", | |
249 | entry.title, entry.markdown, entry.id) | |
250 | ||
251 | def save_entry(self, entry): | |
252 | slug = self._generate_slug(entry.title) | |
253 | ||
9068dba1 MT |
254 | id = self.db.execute("INSERT INTO planet(author_id, title, slug, markdown, published) \ |
255 | VALUES(%s, %s, %s, %s, NOW())", entry.author.uid, entry.title, slug, entry.markdown) | |
27066195 | 256 | |
00af5cf1 MT |
257 | return id |
258 | ||
2bdd073f | 259 | def search(self, what): |
fa7e1a0a MT |
260 | res = self.db.query("WITH \ |
261 | q AS (SELECT plainto_tsquery(%s, %s) AS query), \ | |
262 | ranked AS (SELECT id, query, ts_rank_cd(to_tsvector(%s, markdown), query) AS rank \ | |
263 | FROM planet, q WHERE markdown @@ query ORDER BY rank DESC) \ | |
264 | SELECT *, ts_headline(markup, ranked.query, 'MinWords=100, MaxWords=110') AS markup FROM planet \ | |
265 | JOIN ranked ON planet.id = ranked.id \ | |
266 | WHERE status = %s AND published IS NOT NULL AND published <= NOW() \ | |
267 | ORDER BY ranked DESC LIMIT 10", | |
268 | "english", what, "english", "published") | |
269 | ||
270 | return [PlanetEntry(self.backend, e) for e in res] |