]> git.ipfire.org Git - ipfire.org.git/blob - src/backend/wiki.py
wiki: Borrow markdown rendering from blog
[ipfire.org.git] / src / backend / wiki.py
1 #!/usr/bin/python3
2
3 import logging
4 import os.path
5 import re
6
7 from . import misc
8 from .decorators import *
9
10 class Wiki(misc.Object):
11 def _get_pages(self, query, *args):
12 res = self.db.query(query, *args)
13
14 for row in res:
15 yield Page(self.backend, row.id, data=row)
16
17 def _get_page(self, query, *args):
18 res = self.db.get(query, *args)
19
20 if res:
21 return Page(self.backend, res.id, data=res)
22
23 def get_page_title(self, page, default=None):
24 doc = self.get_page(page)
25 if doc:
26 return doc.title
27
28 return default
29
30 def get_page(self, page, revision=None):
31 page = Page.sanitise_page_name(page)
32 assert page
33
34 if revision:
35 return self._get_page("SELECT * FROM wiki WHERE page = %s \
36 AND timestamp = %s", page, revision)
37 else:
38 return self._get_page("SELECT * FROM wiki WHERE page = %s \
39 ORDER BY timestamp DESC LIMIT 1", page)
40
41 def get_recent_changes(self, limit=None):
42 return self._get_pages("SELECT * FROM wiki \
43 WHERE timestamp >= NOW() - INTERVAL '4 weeks' \
44 ORDER BY timestamp DESC LIMIT %s", limit)
45
46 def create_page(self, page, author, content, changes=None, address=None):
47 page = Page.sanitise_page_name(page)
48
49 return self._get_page("INSERT INTO wiki(page, author_uid, markdown, changes, address) \
50 VALUES(%s, %s, %s, %s, %s) RETURNING *", page, author.uid, content or None, changes, address)
51
52 def delete_page(self, page, author, **kwargs):
53 # Do nothing if the page does not exist
54 if not self.get_page(page):
55 return
56
57 # Just creates a blank last version of the page
58 self.create_page(page, author=author, content=None, **kwargs)
59
60 @staticmethod
61 def _split_url(url):
62 parts = list(e for e in url.split("/") if e)
63
64 num_parts = len(parts)
65 for i in range(num_parts):
66 yield "/".join(parts[:i])
67
68 def make_breadcrumbs(self, url):
69 for part in self._split_url(url):
70 title = self.get_page_title(part, os.path.basename(part))
71
72 yield ("/%s" % part, title)
73
74
75 class Page(misc.Object):
76 def init(self, id, data=None):
77 self.id = id
78 self.data = data
79
80 def __lt__(self, other):
81 if isinstance(other, self.__class__):
82 if self.page == other.page:
83 return self.timestamp < other.timestamp
84
85 return self.page < other.page
86
87 @staticmethod
88 def sanitise_page_name(page):
89 if not page:
90 return "/"
91
92 # Make sure that the page name does NOT end with a /
93 if page.endswith("/"):
94 page = page[:-1]
95
96 # Make sure the page name starts with a /
97 if not page.startswith("/"):
98 page = "/%s" % page
99
100 # Remove any double slashes
101 page = page.replace("//", "/")
102
103 return page
104
105 @property
106 def url(self):
107 return self.page
108
109 @property
110 def page(self):
111 return self.data.page
112
113 @property
114 def title(self):
115 return self._title or self.page[1:]
116
117 @property
118 def _title(self):
119 if not self.markdown:
120 return
121
122 # Find first H1 headline in markdown
123 markdown = self.markdown.splitlines()
124
125 m = re.match(r"^# (.*)( #)?$", markdown[0])
126 if m:
127 return m.group(1)
128
129 @lazy_property
130 def author(self):
131 if self.data.author_uid:
132 return self.backend.accounts.get_by_uid(self.data.author_uid)
133
134 def _render(self, text):
135 logging.debug("Rendering %s" % self)
136
137 # Borrow this from the blog
138 return self.backend.blog._render_text(text, lang="markdown")
139
140 @property
141 def markdown(self):
142 return self.data.markdown
143
144 @property
145 def html(self):
146 return self.data.html or self._render(self.markdown)
147
148 @property
149 def timestamp(self):
150 return self.data.timestamp
151
152 def was_deleted(self):
153 return self.markdown is None
154
155 @lazy_property
156 def breadcrumbs(self):
157 return self.backend.wiki.make_breadcrumbs(self.page)
158
159 def get_latest_revision(self):
160 revisions = self.get_revisions()
161
162 # Return first object
163 for rev in revisions:
164 return rev
165
166 def get_revisions(self):
167 return self.backend.wiki._get_pages("SELECT * FROM wiki \
168 WHERE page = %s ORDER BY timestamp DESC", self.page)
169
170 @property
171 def changes(self):
172 return self.data.changes
173
174 # Sidebar
175
176 @lazy_property
177 def sidebar(self):
178 parts = self.page.split("/")
179
180 while parts:
181 sidebar = self.backend.wiki.get_page(os.path.join(*parts, "sidebar"))
182 if sidebar:
183 return sidebar
184
185 parts.pop()