]> git.ipfire.org Git - ipfire.org.git/blob - webapp/ui_modules.py
.gitignore: Add .vscode
[ipfire.org.git] / webapp / ui_modules.py
1 #!/usr/bin/python
2
3 from __future__ import division
4
5 import hashlib
6 import logging
7 import operator
8 import re
9 import socket
10 import textile
11 import tornado.escape
12 import tornado.locale
13 import tornado.web
14 import unicodedata
15
16 import backend
17
18 class UIModule(tornado.web.UIModule):
19 @property
20 def accounts(self):
21 return self.handler.accounts
22
23 @property
24 def advertisements(self):
25 return self.handler.advertisements
26
27 @property
28 def banners(self):
29 return self.handler.banners
30
31 @property
32 def memcached(self):
33 return self.handler.memcached
34
35 @property
36 def releases(self):
37 return self.handler.releases
38
39 @property
40 def geoip(self):
41 return self.handler.geoip
42
43 @property
44 def news(self):
45 return self.handler.news
46
47 @property
48 def planet(self):
49 return self.handler.planet
50
51 @property
52 def talk(self):
53 return self.handler.talk
54
55
56 class AdvertisementModule(UIModule):
57 def render(self, where):
58 assert where in ("download-splash",), where
59
60 ad = self.advertisements.get(where)
61 if not ad:
62 return ""
63
64 # Mark that advert has been shown.
65 ad.update_impressions()
66
67 return self.render_string("modules/ads/%s.html" % where, ad=ad)
68
69
70 class FireinfoDeviceTableModule(UIModule):
71 def render(self, devices):
72 return self.render_string("fireinfo/modules/table-devices.html",
73 devices=devices)
74
75
76 class FireinfoDeviceAndGroupsTableModule(UIModule):
77 def render(self, devices):
78 _ = self.locale.translate
79
80 groups = {}
81
82 for device in devices:
83 if not groups.has_key(device.cls):
84 groups[device.cls] = []
85
86 groups[device.cls].append(device)
87
88 # Sort all devices
89 for key in groups.keys():
90 groups[key].sort()
91
92 # Order the groups by their name
93 groups = groups.items()
94 groups.sort()
95
96 return self.render_string("fireinfo/modules/table-devices-and-groups.html",
97 groups=groups)
98
99
100 class FireinfoGeoTableModule(UIModule):
101 def render(self, items):
102 countries = []
103 other_countries = []
104 for code, value in items:
105 # Skip the satellite providers in this ranking
106 if code in (None, "A1", "A2"):
107 continue
108
109 name = self.geoip.get_country_name(code)
110
111 # Don't add countries with a small share on the list
112 if value < 0.01:
113 other_countries.append(name)
114 continue
115
116 country = backend.database.Row({
117 "code" : code,
118 "name" : name,
119 "value" : value,
120 })
121 countries.append(country)
122
123 return self.render_string("fireinfo/modules/table-geo.html",
124 countries=countries, other_countries=other_countries)
125
126
127 class LanguageNameModule(UIModule):
128 def render(self, language):
129 _ = self.locale.translate
130
131 if language == "de":
132 return _("German")
133 elif language == "en":
134 return _("English")
135 elif language == "es":
136 return _("Spanish")
137 elif language == "fr":
138 return _("French")
139 elif language == "it":
140 return _("Italian")
141 elif language == "nl":
142 return _("Dutch")
143 elif language == "pl":
144 return _("Polish")
145 elif language == "pt":
146 return _("Portuguese")
147 elif language == "ru":
148 return _("Russian")
149 elif language == "tr":
150 return _("Turkish")
151
152 return language
153
154
155 class MapModule(UIModule):
156 def render(self, latitude, longitude):
157 return self.render_string("modules/map.html", latitude=latitude, longitude=longitude)
158
159
160 class MenuModule(UIModule):
161 def render(self):
162 return self.render_string("modules/menu.html")
163
164
165 class MirrorItemModule(UIModule):
166 def render(self, item):
167 return self.render_string("modules/mirror-item.html", item=item)
168
169
170 class MirrorsTableModule(UIModule):
171 def render(self, mirrors, preferred_mirrors=[]):
172 return self.render_string("modules/mirrors-table.html",
173 mirrors=mirrors, preferred_mirrors=preferred_mirrors)
174
175
176 class NetBootMenuConfigModule(UIModule):
177 def render(self, release, arch=None, platform=None):
178 return self.render_string("netboot/menu-config.cfg", release=release,
179 arch=arch, platform=platform)
180
181
182 class NetBootMenuHeaderModule(UIModule):
183 def render(self, title, releases, arch=None, platform=None):
184 id = unicodedata.normalize("NFKD", unicode(title)).encode("ascii", "ignore")
185 id = re.sub(r"[^\w]+", " ", id)
186 id = "-".join(id.lower().strip().split())
187
188 return self.render_string("netboot/menu-header.cfg", id=id,
189 title=title, releases=releases, arch=arch, platform=platform)
190
191
192 class NetBootMenuSeparatorModule(UIModule):
193 def render(self):
194 return self.render_string("netboot/menu-separator.cfg")
195
196
197 class NewsItemModule(UIModule):
198 def get_author(self, author):
199 # Get name of author
200 author = self.accounts.find(author)
201 if author:
202 return author.name
203 else:
204 _ = self.locale.translate
205 return _("Unknown author")
206
207 def render(self, item, uncut=True, announcement=False, show_heading=True):
208 # Get author
209 item.author = self.get_author(item.author_id)
210
211 if not uncut and len(item.text) >= 400:
212 item.text = item.text[:400] + "..."
213
214 # Render text
215 item.text = textile.textile(item.text.decode("utf8"))
216
217 # Find a release if one exists
218 release = self.releases.get_by_news_id(item.uuid)
219
220 return self.render_string("modules/news-item.html", item=item, release=release,
221 uncut=uncut, announcement=announcement, show_heading=show_heading)
222
223
224 class NewsLineModule(UIModule):
225 def render(self, item):
226 return self.render_string("modules/news-line.html", item=item)
227
228
229 class NewsTableModule(UIModule):
230 def render(self, news):
231 return self.render_string("modules/news-table.html", news=news)
232
233
234 class NewsYearNavigationModule(UIModule):
235 def render(self, active=None):
236 try:
237 active = int(active)
238 except:
239 active = None
240
241 return self.render_string("modules/news-year-nav.html",
242 active=active, years=self.news.years)
243
244
245 class PlanetSearchBoxModule(UIModule):
246 def render(self, query=None):
247 return self.render_string("modules/planet/search-box.html", query=query)
248
249
250 class SidebarItemModule(UIModule):
251 def render(self):
252 return self.render_string("modules/sidebar-item.html")
253
254
255 class SidebarReleaseModule(UIModule):
256 def render(self):
257 return self.render_string("modules/sidebar-release.html",
258 latest=self.releases.get_latest())
259
260
261 class ReleaseItemModule(UIModule):
262 def render(self, release, latest=False):
263 arches = ("x86_64", "i586", "arm")
264
265 downloads = []
266 for arch in arches:
267 files = []
268
269 for file in release.files:
270 if not file.arch == arch:
271 continue
272
273 files.append(file)
274
275 if files:
276 downloads.append((arch, files))
277
278 return self.render_string("modules/release-item.html",
279 release=release, latest=latest, downloads=downloads)
280
281
282 class SidebarBannerModule(UIModule):
283 def render(self, item=None):
284 if not item:
285 item = self.banners.get_random()
286
287 return self.render_string("modules/sidebar-banner.html", item=item)
288
289
290 class DownloadButtonModule(UIModule):
291 def render(self, release, text="Download now!"):
292 best_image = None
293
294 for file in release.files:
295 if (release.sname < "ipfire-2.19-core100" or file.arch == "x86_64") \
296 and file.type == "iso":
297 best_image = file
298 break
299
300 # Show nothing when there was no image found.
301 if not best_image:
302 return ""
303
304 return self.render_string("modules/download-button.html",
305 release=release, image=best_image)
306
307
308 class PlanetAuthorBoxModule(UIModule):
309 def render(self, author):
310 return self.render_string("planet/modules/author-box.html", author=author)
311
312
313 class PlanetEntryModule(UIModule):
314 def render(self, entry, show_avatar=True):
315 return self.render_string("modules/planet-entry.html",
316 entry=entry, show_avatar=show_avatar)
317
318
319 class ProgressBarModule(UIModule):
320 def render(self, value, colour=None):
321 value *= 100
322
323 return self.render_string("modules/progress-bar.html",
324 colour=colour, value=value)
325
326
327 class TalkContactModule(UIModule):
328 def render(self, number, name=None, application=None):
329 account = self.accounts.get_by_sip_id(number)
330
331 return self.render_string("talk/modules/contact.html",
332 account=account, number=number, name=name, application=application)
333
334
335 class TalkCallLogModule(UIModule):
336 def render(self, account=None, viewer=None):
337 if (account is None or not self.current_user == account) \
338 and not self.current_user.is_admin():
339 raise RuntimeException("Insufficient permissions")
340
341 if viewer is None:
342 viewer = self.current_user
343
344 calls = self.talk.get_call_log(account)
345
346 return self.render_string("talk/modules/call-log.html",
347 calls=calls, viewer=viewer)
348
349
350 class TalkLinesModule(UIModule):
351 def render(self, account=None, show_account=False):
352 if (account is None or not self.current_user == account) \
353 and not self.current_user.is_admin():
354 raise RuntimeException("Insufficient permissions")
355
356 lines = self.talk.get_lines(account)
357
358 return self.render_string("talk/modules/lines.html",
359 show_account=show_account, lines=lines)
360
361
362 class TalkOngoingCallsModule(UIModule):
363 def render(self, account=None, debug=False):
364 if (account is None or not self.current_user == account) \
365 and not self.current_user.is_admin():
366 raise RuntimeException("Insufficient permissions")
367
368 channels = self.talk.get_channels()
369
370 return self.render_string("talk/modules/ongoing-calls.html",
371 account=account, channels=channels, debug=debug)
372
373
374 class DonationBoxModule(UIModule):
375 def render(self, reason_for_transfer=None):
376 if reason_for_transfer:
377 reason_for_transfer = "IPFire.org - %s" % reason_for_transfer
378
379 return self.render_string("modules/donation-box.html",
380 reason_for_transfer=reason_for_transfer)
381
382
383 class DonationButtonModule(UIModule):
384 # https://developer.paypal.com/docs/classic/paypal-payments-standard/integration-guide/Appx_websitestandard_htmlvariables/
385 COUNTRIES = (
386 "AU",
387 "AT",
388 "BE",
389 "BR",
390 "CA",
391 "CH",
392 "CN",
393 "DE",
394 "ES",
395 "GB",
396 "FR",
397 "IT",
398 "NL",
399 "PL",
400 "PT",
401 "RU",
402 "US",
403 )
404
405 LOCALES = (
406 "da_DK",
407 "he_IL",
408 "id_ID",
409 "ja_JP",
410 "no_NO",
411 "pt_BR",
412 "ru_RU",
413 "sv_SE",
414 "th_TH",
415 "zh_CN",
416 "zh_HK",
417 "zh_TW",
418 )
419
420 def render(self, reason_for_transfer=None, currency="EUR"):
421 if not reason_for_transfer:
422 reason_for_transfer = "IPFire.org"
423
424 primary = (currency == "EUR")
425
426 return self.render_string("modules/donation-button.html", primary=primary,
427 reason_for_transfer=reason_for_transfer, currency=currency, lc=self.lc)
428
429 @property
430 def lc(self):
431 """
432 Returns the locale of the user
433 """
434 try:
435 locale, delimiter, encoding = self.locale.code.partition(".")
436
437 # Break for languages in specific countries
438 if locale in self.LOCALES:
439 return locale
440
441 lang, delimiter, country_code = locale.partition("_")
442
443 if country_code and country_code in self.COUNTRIES:
444 return country_code
445
446 lang = lang.upper()
447 if lang in self.COUNTRIES:
448 return lang
449 except:
450 pass
451
452 # If anything goes wrong, fall back to GB
453 return "GB"
454
455
456 class DonationInputBoxModule(DonationButtonModule):
457 def render(self):
458 currencies = ("EUR", "USD", "GBP", "CHF", "AUD", "NZD", "CAD")
459
460 return self.render_string("modules/donation-input-box.html",
461 currencies=currencies, lc=self.lc)