fa29edb7ccf731942e6737ed507e91fc5bac2103
[people/shoehn/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         @property
56         def wishlist(self):
57                 return self.handler.wishlist
58
59
60 class 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
74 class FireinfoDeviceTableModule(UIModule):
75         def render(self, devices):
76                 return self.render_string("fireinfo/modules/table-devices.html",
77                                 devices=devices)
78
79
80 class 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
104 class 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
120                         country = backend.database.Row({
121                                 "code"  : code,
122                                 "name"  : name,
123                                 "value" : value,
124                         })
125                         countries.append(country)
126
127                 return self.render_string("fireinfo/modules/table-geo.html",
128                         countries=countries, other_countries=other_countries)
129
130
131 class 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
159 class MapModule(UIModule):
160         def render(self, latitude, longitude):
161                 return self.render_string("modules/map.html", latitude=latitude, longitude=longitude)
162
163
164 class MenuModule(UIModule):
165         def render(self):
166                 return self.render_string("modules/menu.html")
167
168
169 class MirrorItemModule(UIModule):
170         def render(self, item):
171                 return self.render_string("modules/mirror-item.html", item=item)
172
173
174 class 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
180 class NetBootMenuConfigModule(UIModule):
181         def render(self, release):
182                 return self.render_string("netboot/menu-config.cfg", release=release)
183
184
185 class NetBootMenuHeaderModule(UIModule):
186         def render(self, title, releases):
187                 id = unicodedata.normalize("NFKD", unicode(title)).encode("ascii", "ignore")
188                 id = re.sub(r"[^\w]+", " ", id)
189                 id = "-".join(id.lower().strip().split())
190
191                 return self.render_string("netboot/menu-header.cfg", id=id,
192                         title=title, releases=releases)
193
194
195 class NetBootMenuSeparatorModule(UIModule):
196         def render(self):
197                 return self.render_string("netboot/menu-separator.cfg")
198
199
200 class NewsItemModule(UIModule):
201         def get_author(self, author):
202                 # Get name of author
203                 author = self.accounts.find(author)
204                 if author:
205                         return author.name
206                 else:
207                         _ = self.locale.translate
208                         return _("Unknown author")
209
210         def render(self, item, uncut=True, announcement=False, show_heading=True):
211                 # Get author
212                 item.author = self.get_author(item.author_id)
213
214                 if not uncut and len(item.text) >= 400:
215                         item.text = item.text[:400] + "..."
216
217                 # Render text
218                 item.text = textile.textile(item.text.decode("utf8"))
219
220                 # Find a release if one exists
221                 release = self.releases.get_by_news_id(item.uuid)
222
223                 return self.render_string("modules/news-item.html", item=item, release=release,
224                         uncut=uncut, announcement=announcement, show_heading=show_heading)
225
226
227 class NewsLineModule(UIModule):
228         def render(self, item):
229                 return self.render_string("modules/news-line.html", item=item)
230
231
232 class NewsTableModule(UIModule):
233         def render(self, news):
234                 return self.render_string("modules/news-table.html", news=news)
235
236
237 class NewsYearNavigationModule(UIModule):
238         def render(self, active=None):
239                 try:
240                         active = int(active)
241                 except:
242                         active = None
243
244                 return self.render_string("modules/news-year-nav.html",
245                         active=active, years=self.news.years)
246
247
248 class PlanetSearchBoxModule(UIModule):
249         def render(self, query=None):
250                 return self.render_string("modules/planet/search-box.html", query=query)
251
252
253 class SidebarItemModule(UIModule):
254         def render(self):
255                 return self.render_string("modules/sidebar-item.html")
256
257
258 class SidebarReleaseModule(UIModule):
259         def render(self):
260                 return self.render_string("modules/sidebar-release.html",
261                         latest=self.releases.get_latest())
262
263
264 class ReleaseItemModule(UIModule):
265         def render(self, release, latest=False):
266                 arches = ("i586", "arm")
267
268                 downloads = []
269                 for arch in ("i586", "arm"):
270                         files = []
271
272                         for file in release.files:
273                                 if not file.arch == arch:
274                                         continue
275
276                                 files.append(file)
277
278                         if files:
279                                 downloads.append((arch, files))
280
281                 return self.render_string("modules/release-item.html",
282                         release=release, latest=latest, downloads=downloads)
283
284
285 class SidebarBannerModule(UIModule):
286         def render(self, item=None):
287                 if not item:
288                         item = self.banners.get_random()
289
290                 return self.render_string("modules/sidebar-banner.html", item=item)
291
292
293 class DownloadButtonModule(UIModule):
294         def render(self, release, text="Download now!"):
295                 best_image = None
296
297                 for file in release.files:
298                         if file.type == "iso":
299                                 best_image = file
300                                 break
301
302                 # Show nothing when there was no image found.
303                 if not best_image:
304                         return ""
305
306                 return self.render_string("modules/download-button.html",
307                         release=release, image=best_image)
308
309
310 class PlanetAuthorBoxModule(UIModule):
311         def render(self, author):
312                 return self.render_string("planet/modules/author-box.html", author=author)
313
314
315 class PlanetEntryModule(UIModule):
316         def render(self, entry, show_avatar=True):
317                 return self.render_string("modules/planet-entry.html",
318                         entry=entry, show_avatar=show_avatar)
319
320
321 class ProgressBarModule(UIModule):
322         def render(self, value, colour=None):
323                 value *= 100
324
325                 return self.render_string("modules/progress-bar.html",
326                         colour=colour, value=value)
327
328
329 class TalkCallLogModule(UIModule):
330         def render(self, account=None, viewer=None):
331                 if (account is None or not self.current_user == account) \
332                                 and not self.current_user.is_admin():
333                         raise RuntimeException("Insufficient permissions")
334
335                 if viewer is None:
336                         viewer = self.current_user
337
338                 calls = self.talk.get_call_log(account)
339
340                 return self.render_string("talk/modules/call-log.html",
341                         calls=calls, viewer=viewer)
342
343
344 class TalkLinesModule(UIModule):
345         def render(self, account=None, show_account=False):
346                 if (account is None or not self.current_user == account) \
347                                 and not self.current_user.is_admin():
348                         raise RuntimeException("Insufficient permissions")
349
350                 lines = self.talk.get_lines(account)
351
352                 return self.render_string("talk/modules/lines.html",
353                         show_account=show_account, lines=lines)
354
355
356 class TalkOngoingCallsModule(UIModule):
357         def render(self, account=None):
358                 if (account is None or not self.current_user == account) \
359                                 and not self.current_user.is_admin():
360                         raise RuntimeException("Insufficient permissions")
361
362                 calls = self.talk.get_ongoing_calls(account)
363
364                 if calls:
365                         return self.render_string("talk/modules/ongoing-calls.html",
366                                 calls=calls)
367
368                 return ""
369
370
371 class TrackerPeerListModule(UIModule):
372         def render(self, peers):
373                 # Guess country code and hostname of the host
374                 for peer in peers:
375                         country_code = self.geoip.get_country(peer["ip"])
376                         if country_code:
377                                 peer["country_code"] = country_code.lower()
378                         else:
379                                 peer["country_code"] = "unknown"
380
381                         try:
382                                 peer["hostname"] = socket.gethostbyaddr(peer["ip"])[0]
383                         except:
384                                 peer["hostname"] = ""
385
386                 return self.render_string("modules/tracker-peerlist.html",
387                         peers=[backend.database.Row(p) for p in peers])
388
389
390 class WishlistModule(UIModule):
391         def render(self, wishes, short=False):
392                 return self.render_string("wishlist/modules/wishlist.html",
393                         wishes=wishes, short=short)
394
395
396 class WishModule(UIModule):
397         def render(self, wish, short=False):
398                 progress_bar = "progress-bar-warning"
399
400                 if wish.percentage >= 100:
401                         progress_bar = "progress-bar-success"
402
403                 return self.render_string("wishlist/modules/wish.html",
404                         wish=wish, short=short, progress_bar=progress_bar)
405
406
407 class WishlistItemsModule(UIModule):
408         def render(self, wishlist_items):
409                 return self.render_string("modules/wishlist-items.html",
410                         wishlist_items=wishlist_items)
411
412
413 class DonationBoxModule(UIModule):
414         def render(self, reason_for_transfer=None):
415                 if reason_for_transfer:
416                         reason_for_transfer = "IPFire.org - %s" % reason_for_transfer
417
418                 return self.render_string("modules/donation-box.html",
419                         reason_for_transfer=reason_for_transfer)
420
421
422 class DonationButtonModule(UIModule):
423         # https://developer.paypal.com/docs/classic/paypal-payments-standard/integration-guide/Appx_websitestandard_htmlvariables/
424         COUNTRIES = (
425                 "AU",
426                 "AT",
427                 "BE",
428                 "BR",
429                 "CA",
430                 "CH",
431                 "CN",
432                 "DE",
433                 "ES",
434                 "GB",
435                 "FR",
436                 "IT",
437                 "NL",
438                 "PL",
439                 "PT",
440                 "RU",
441                 "US",
442         )
443
444         LOCALES = (
445                 "da_DK",
446                 "he_IL",
447                 "id_ID",
448                 "ja_JP",
449                 "no_NO",
450                 "pt_BR",
451                 "ru_RU",
452                 "sv_SE",
453                 "th_TH",
454                 "zh_CN",
455                 "zh_HK",
456                 "zh_TW",
457         )
458
459         def render(self, reason_for_transfer=None, currency="EUR"):
460                 if not reason_for_transfer:
461                         reason_for_transfer = "IPFire.org"
462
463                 primary = (currency == "EUR")
464
465                 return self.render_string("modules/donation-button.html", primary=primary,
466                         reason_for_transfer=reason_for_transfer, currency=currency, lc=self.lc)
467
468         @property
469         def lc(self):
470                 """
471                         Returns the locale of the user
472                 """
473                 try:
474                         locale, delimiter, encoding = self.locale.code.partition(".")
475
476                         # Break for languages in specific countries
477                         if locale in self.LOCALES:
478                                 return locale
479
480                         lang, delimiter, country_code = locale.partition("_")
481
482                         if country_code and country_code in self.COUNTRIES:
483                                 return country_code
484
485                         lang = lang.upper()
486                         if lang in self.COUNTRIES:
487                                 return lang
488                 except:
489                         pass
490
491                 # If anything goes wrong, fall back to GB
492                 return "GB"
493
494
495 class DonationInputBoxModule(DonationButtonModule):
496         def render(self):
497                 currencies = ("EUR", "USD", "GBP", "CHF", "AUD", "NZD", "CAD")
498
499                 return self.render_string("modules/donation-input-box.html",
500                         currencies=currencies, lc=self.lc)