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