]> git.ipfire.org Git - people/shoehn/ipfire.org.git/blame - webapp/ui_modules.py
Revert "geoip: Tolerate lots of spaces in database fields"
[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):
181 def render(self, release):
182 return self.render_string("netboot/menu-config.cfg", release=release)
183
184
185class 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
195class NetBootMenuSeparatorModule(UIModule):
196 def render(self):
197 return self.render_string("netboot/menu-separator.cfg")
198
199
81675874 200class NewsItemModule(UIModule):
940227cb
MT
201 def get_author(self, author):
202 # Get name of author
203 author = self.accounts.find(author)
204 if author:
2fed2438 205 return author.name
940227cb
MT
206 else:
207 _ = self.locale.translate
208 return _("Unknown author")
209
60024cc8 210 def render(self, item, uncut=True, announcement=False, show_heading=True):
940227cb
MT
211 # Get author
212 item.author = self.get_author(item.author_id)
81675874 213
940227cb
MT
214 if not uncut and len(item.text) >= 400:
215 item.text = item.text[:400] + "..."
81675874 216
940227cb 217 # Render text
e46e8df8 218 item.text = textile.textile(item.text.decode("utf8"))
81675874 219
9de13943
MT
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,
60024cc8 224 uncut=uncut, announcement=announcement, show_heading=show_heading)
940227cb
MT
225
226
7771acea 227class NewsLineModule(UIModule):
940227cb
MT
228 def render(self, item):
229 return self.render_string("modules/news-line.html", item=item)
230
231
7771acea
MT
232class NewsTableModule(UIModule):
233 def render(self, news):
234 return self.render_string("modules/news-table.html", news=news)
235
236
237class 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
fa7e1a0a
MT
248class PlanetSearchBoxModule(UIModule):
249 def render(self, query=None):
60b0917c 250 return self.render_string("modules/planet/search-box.html", query=query)
fa7e1a0a
MT
251
252
81675874 253class SidebarItemModule(UIModule):
254 def render(self):
255 return self.render_string("modules/sidebar-item.html")
256
257
258class SidebarReleaseModule(UIModule):
81675874 259 def render(self):
260 return self.render_string("modules/sidebar-release.html",
940227cb 261 latest=self.releases.get_latest())
81675874 262
263
264class ReleaseItemModule(UIModule):
60024cc8 265 def render(self, release, latest=False):
110e8687 266 arches = ("i586", "arm")
60024cc8 267
110e8687
MT
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))
60024cc8
MT
280
281 return self.render_string("modules/release-item.html",
110e8687 282 release=release, latest=latest, downloads=downloads)
81675874 283
284
285class SidebarBannerModule(UIModule):
940227cb
MT
286 def render(self, item=None):
287 if not item:
288 item = self.banners.get_random()
289
81675874 290 return self.render_string("modules/sidebar-banner.html", item=item)
291
292
60024cc8
MT
293class 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
d88b8f41
MT
310class PlanetAuthorBoxModule(UIModule):
311 def render(self, author):
312 return self.render_string("planet/modules/author-box.html", author=author)
313
314
940227cb 315class PlanetEntryModule(UIModule):
60024cc8 316 def render(self, entry, show_avatar=True):
940227cb 317 return self.render_string("modules/planet-entry.html",
60024cc8 318 entry=entry, show_avatar=show_avatar)
d0d074e0
MT
319
320
66862195
MT
321class 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
329class TalkCallLogModule(UIModule):
77431b9c
MT
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
344class 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)
66862195
MT
354
355
356class TalkOngoingCallsModule(UIModule):
77431b9c
MT
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 ""
66862195
MT
369
370
940227cb 371class TrackerPeerListModule(UIModule):
6d524cba 372 def render(self, peers):
940227cb
MT
373 # Guess country code and hostname of the host
374 for peer in peers:
6d524cba
MT
375 country_code = self.geoip.get_country(peer["ip"])
376 if country_code:
66862195
MT
377 peer["country_code"] = country_code.lower()
378 else:
379 peer["country_code"] = "unknown"
d0d074e0 380
940227cb
MT
381 try:
382 peer["hostname"] = socket.gethostbyaddr(peer["ip"])[0]
383 except:
384 peer["hostname"] = ""
d0d074e0 385
940227cb 386 return self.render_string("modules/tracker-peerlist.html",
e46e8df8 387 peers=[backend.database.Row(p) for p in peers])
372efc19
MT
388
389
7771acea
MT
390class 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
396class WishModule(UIModule):
397 def render(self, wish, short=False):
3907b320 398 progress_bar = "progress-bar-warning"
7771acea 399
f60b79ce 400 if wish.percentage >= 100:
3907b320 401 progress_bar = "progress-bar-success"
7771acea
MT
402
403 return self.render_string("wishlist/modules/wish.html",
404 wish=wish, short=short, progress_bar=progress_bar)
405
406
e64ce07e
MT
407class WishlistItemsModule(UIModule):
408 def render(self, wishlist_items):
409 return self.render_string("modules/wishlist-items.html",
410 wishlist_items=wishlist_items)
411
412
7771acea 413class DonationBoxModule(UIModule):
e00c06b9 414 def render(self, reason_for_transfer=None):
353880e5
MT
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",
e00c06b9
MT
419 reason_for_transfer=reason_for_transfer)
420
421
422class DonationButtonModule(UIModule):
069e18f5
MT
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
e00c06b9
MT
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,
069e18f5
MT
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("_")
069e18f5
MT
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"
60b0917c
MT
493
494
495class 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)