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