]> git.ipfire.org Git - ipfire.org.git/blame - src/web/__init__.py
sponsors: Build controls for the second step
[ipfire.org.git] / src / web / __init__.py
CommitLineData
81675874 1#/usr/bin/python
2
8d45158b 3import babel.numbers
feb02477 4import logging
81675874 5import os.path
c01ad253 6import phonenumbers
e96e445b 7import phonenumbers.geocoder
81675874 8import tornado.locale
a49b5422 9import tornado.options
81675874 10import tornado.web
11
a95c2f97 12import ipfire
574a88c7 13import ipfire.countries
440aba92 14from .. import util
feb02477 15
11347e46 16from .handlers import *
81675874 17
08df6527 18from . import auth
12e5de7e 19from . import blog
f301d952 20from . import boot
1958a22b 21from . import docs
c7bcb9ca 22from . import donate
aec63a26 23from . import downloads
96c9bb79 24from . import fireinfo
699a0911 25from . import iuse
97e15cf6 26from . import lists
f5b01fc2 27from . import location
a41085fb 28from . import nopaste
8d45158b 29from . import sponsors
df6180a5 30from . import ui_modules
b01a1ee3 31from . import users
4235ba55 32from . import voip
12e5de7e 33
81675874 34class Application(tornado.web.Application):
3e7c6ccd
MT
35 def __init__(self, config, **kwargs):
36 # Initialize backend
a95c2f97 37 self.backend = ipfire.Backend(config)
a6dc0bad 38
9ed02e3b
MT
39 settings = {
40 # Do not compress responses
41 "gzip" : False,
42
43 # Enable XSRF cookies
44 "xsrf_cookies" : True,
45
46 # Login
47 "login_url" : "/login",
48
49 # Setup directory structure
50 "static_path" : self.backend.config.get("global", "static_dir"),
51 "template_path" : self.backend.config.get("global", "templates_dir"),
52
eabe6b8d 53 # UI Methods
9ed02e3b 54 "ui_methods" : {
574a88c7 55 "format_country_name" : self.format_country_name,
8d45158b 56 "format_currency" : self.format_currency,
6eddfb50 57 "format_language_name" : self.format_language_name,
8d45158b 58 "format_money" : self.format_money,
e96e445b
MT
59 "format_month_name" : self.format_month_name,
60 "format_phone_number" : self.format_phone_number,
61 "format_phone_number_to_e164" : self.format_phone_number_to_e164,
62 "format_phone_number_location" : self.format_phone_number_location,
cc3b928d 63 },
eabe6b8d
MT
64
65 # UI Modules
9ed02e3b 66 "ui_modules" : {
5806d6fc
MT
67 # Auth
68 "Password" : auth.PasswordModule,
69
f5b01fc2 70 # Blog
7e64f6a3
MT
71 "BlogHistoryNavigation": blog.HistoryNavigationModule,
72 "BlogList" : blog.ListModule,
f91dfcc7
MT
73 "BlogPost" : blog.PostModule,
74
93feb275
MT
75 # Boot
76 "BootMenuConfig" : boot.MenuConfigModule,
77 "BootMenuHeader" : boot.MenuHeaderModule,
78 "BootMenuSeparator" : boot.MenuSeparatorModule,
79
cf59466c 80 # Docs
739fff76 81 "DocsDiff" : docs.DiffModule,
cf59466c 82 "DocsHeader" : docs.HeaderModule,
d25f886f 83 "DocsList" : docs.ListModule,
cf59466c 84
e1814f16
MT
85 # Nopaste
86 "Code" : nopaste.CodeModule,
87
23f0179e 88 # Fireinfo
96c9bb79 89 "FireinfoDeviceTable" : fireinfo.DeviceTableModule,
eabe6b8d
MT
90 "FireinfoDeviceAndGroupsTable"
91 : fireinfo.DeviceAndGroupsTableModule,
92
beb13102
MT
93 # Users
94 "UsersList" : users.ListModule,
95
4235ba55 96 # VoIP
214a68a0 97 "VoIPConferences" : voip.ConferencesModule,
00465786
MT
98 "VoIPOutboundRegistrations" :
99 voip.OutboundRegistrationsModule,
8e93325b 100 "VoIPQueues" : voip.QueuesModule,
4235ba55
MT
101 "VoIPRegistrations" : voip.RegistrationsModule,
102
eabe6b8d 103 # Misc
6c6de80a 104 "IPFireLogo" : ui_modules.IPFireLogoModule,
1c4522dc 105 "Markdown" : ui_modules.MarkdownModule,
eabe6b8d
MT
106 "Map" : ui_modules.MapModule,
107 "ProgressBar" : ui_modules.ProgressBarModule,
81675874 108 },
3403dc5e
MT
109
110 # Call this when a page wasn't found
b22bc8e8 111 "default_handler_class" : base.NotFoundHandler,
9ed02e3b 112 }
9068dba1 113 settings.update(kwargs)
5cf160e0 114
ae0228e1
MT
115 tornado.web.Application.__init__(self, **settings)
116
66862195 117 authentication_handlers = [
08df6527
MT
118 (r"/login", auth.LoginHandler),
119 (r"/logout", auth.LogoutHandler),
66862195
MT
120 ]
121
c85f80e2 122 self.add_handlers(r"(www\.)?([a-z]+\.dev\.)?ipfire\.org", [
940227cb
MT
123 # Entry site that lead the user to index
124 (r"/", IndexHandler),
940227cb 125
8505c8cd
MT
126 # Authentication
127 (r"/login", auth.LoginHandler),
128 (r"/logout", auth.LogoutHandler),
129 (r"/register", auth.RegisterHandler),
42c7cc66 130 (r"/activate/([a-z_][a-z0-9_-]{0,31})/(\w+)", auth.ActivateHandler),
8505c8cd 131
34472923
MT
132 # Blog
133 (r"/blog", blog.IndexHandler),
34472923
MT
134 (r"/blog/drafts", blog.DraftsHandler),
135 (r"/blog/feed.xml", blog.FeedHandler),
34472923 136 (r"/blog/tags/([0-9a-z\-\.]+)", blog.TagHandler),
4d657f4f 137 (r"/blog/write", blog.WriteHandler),
feb245e0 138 (r"/blog/years/([0-9]{4})", blog.YearHandler),
34472923
MT
139 (r"/blog/([0-9a-z\-\._]+)", blog.PostHandler),
140 (r"/blog/([0-9a-z\-\._]+)/delete", blog.DeleteHandler),
141 (r"/blog/([0-9a-z\-\._]+)/edit", blog.EditHandler),
142 (r"/blog/([0-9a-z\-\._]+)/publish", blog.PublishHandler),
56801afb 143 (r"/blog/([0-9a-z\-\._]+)/debug/email", blog.DebugEmailHandler),
34472923 144
1958a22b 145 # Docs
5f0d294e 146 (r"/docs/recent\-changes", docs.RecentChangesHandler),
0ce8cc32 147 (r"/docs/search", docs.SearchHandler),
350f391e 148 (r"/docs/tree", docs.TreeHandler),
16619e57 149 (r"/docs/watchlist", docs.WatchlistHandler),
bb998aca 150 (r"/docs/_restore", docs.RestoreHandler),
26ade731 151 (r"/docs/_upload", docs.UploadHandler),
a7342d24
MT
152 (r"/docs(?:/([A-Za-z0-9\-_\/]+))?/_edit", docs.EditHandler),
153 (r"/docs(?:/([A-Za-z0-9\-_\/]+))?/_render", docs.RenderHandler),
154 (r"/docs(?:/([A-Za-z0-9\-_\/]+))?/_(watch|unwatch)", docs.WatchHandler),
16510618 155 (r"/docs/((?:[A-Za-z0-9\-_\/]+)?(?:.*)\.(?:\w+))/_delete", docs.DeleteFileHandler),
4a1bfdd5 156 (r"/docs((?:[A-Za-z0-9\-_\/]+)?(?:.*)\.(?:\w+))$", docs.FileHandler),
a7342d24
MT
157 (r"/docs(?:/([A-Za-z0-9\-_\/]+))?/_files", docs.FilesHandler),
158 (r"/docs(?:/([A-Za-z0-9\-_\/]+))?", docs.PageHandler),
1958a22b 159
aec63a26
MT
160 # Downloads
161 (r"/downloads", downloads.IndexHandler),
162 (r"/downloads/mirrors", downloads.MirrorsHandler),
163 (r"/downloads/thank-you", downloads.ThankYouHandler),
164 (r"/downloads/([0-9a-z\-\.]+)", downloads.ReleaseHandler),
165
e64ce07e 166 # Donate
c7bcb9ca
MT
167 (r"/donate", donate.DonateHandler),
168 (r"/donate/thank-you", donate.ThankYouHandler),
169 (r"/donate/error", donate.ErrorHandler),
8d48f4ef 170
97e15cf6
MT
171 # Lists
172 (r"/lists", lists.IndexHandler),
173
8066a1e7
MT
174 # Password Reset
175 (r"/password\-reset", auth.PasswordResetInitiationHandler),
176 (r"/password\-reset/([a-z_][a-z0-9_-]{0,31})/(\w+)", auth.PasswordResetHandler),
177
d9f7b6e5
MT
178 # Projects
179 (r"/projects/location", location.IndexHandler),
180 (r"/projects/location/download", StaticHandler, { "template" : "../location/download.html" }),
181 (r"/projects/location/how\-to\-use", StaticHandler, { "template" : "../location/how-to-use.html" }),
d9f7b6e5
MT
182 (r"/projects/location/lookup/(.+)", location.LookupHandler),
183
8d45158b
MT
184 # Sponsors
185 (r"/sponsors", sponsors.IndexHandler),
186 (r"/sponsors/faq", sponsors.FAQHandler),
83fd7727 187 (r"/sponsors/next", sponsors.NextHandler),
8d45158b 188
2fe1d960
MT
189 # Single-Sign-On for Discourse
190 (r"/sso/discourse", auth.SSODiscourse),
191
f2c38da0
MT
192 # User Groups
193 (r"/users/groups", users.GroupIndexHandler),
194 (r"/users/groups/([a-z_][a-z0-9_-]{0,31})", users.GroupShowHandler),
195
b01a1ee3 196 # Users
beb13102 197 (r"/users", users.IndexHandler),
53b2117f
MT
198 (r"/users/([a-z_][a-z0-9_-]{0,31})", users.ShowHandler),
199 (r"/users/([a-z_][a-z0-9_-]{0,31})\.jpg", users.AvatarHandler),
1cd4d7d3 200 (r"/users/([a-z_][a-z0-9_-]{0,31})/delete", users.DeleteHandler),
3c986f14 201 (r"/users/([a-z_][a-z0-9_-]{0,31})/edit", users.EditHandler),
e4d2f51f 202 (r"/users/([a-z_][a-z0-9_-]{0,31})/passwd", users.PasswdHandler),
b01a1ee3 203
bb440bad
MT
204 # Promotional Consent Stuff
205 (r"/subscribe", users.SubscribeHandler),
206 (r"/unsubscribe", users.UnsubscribeHandler),
207
4235ba55
MT
208 # VoIP
209 (r"/voip", voip.IndexHandler),
210
45592df5 211 # Static Pages
8ea3eaa2 212 (r"/about", StaticHandler, { "template" : "about.html" }),
45592df5 213 (r"/legal", StaticHandler, { "template" : "legal.html" }),
d2000918 214 (r"/help", StaticHandler, { "template" : "help.html" }),
d1ec64e1 215 (r"/sitemap", StaticHandler, { "template" : "sitemap.html" }),
45592df5 216
86d86819
MT
217 # API
218 (r"/api/check/email", auth.APICheckEmail),
219 (r"/api/check/uid", auth.APICheckUID),
220
14cd4fa8 221 # Handle old pages that have moved elsewhere
1f153396 222 (r"/blog/authors/(\w+)", tornado.web.RedirectHandler, { "url" : "/users/{0}" }),
7fa7d38d
MT
223 (r"/donation", tornado.web.RedirectHandler, { "url" : "/donate" }),
224 (r"/download", tornado.web.RedirectHandler, { "url" : "/downloads" }),
225 (r"/download/([0-9a-z\-\.]+)", tornado.web.RedirectHandler, { "url" : "/downloads/{0}" }),
85c977e2 226 (r"/features", tornado.web.RedirectHandler, { "url" : "/about" }),
14cd4fa8 227 (r"/imprint", tornado.web.RedirectHandler, { "url" : "/legal" }),
7fa7d38d
MT
228 (r"/news.rss", tornado.web.RedirectHandler, { "url" : "/blog/feed.xml" }),
229 (r"/news/(.*)", tornado.web.RedirectHandler, { "url" : "/blog/{0}" }),
d2000918 230 (r"/support", tornado.web.RedirectHandler, { "url" : "/help"}),
7fa7d38d 231 (r"/(de|en)/(.*)", tornado.web.RedirectHandler, { "url" : "/{0}"}),
37ed7c3c
MT
232
233 # Export arbitrary error pages
b22bc8e8 234 (r"/error/([45][0-9]{2})", base.ErrorHandler),
0ed3ea5c
MT
235
236 # Serve any static files
237 (r"/static/(.*)", tornado.web.StaticFileHandler, { "path" : self.settings.get("static_path") }),
940227cb
MT
238 ])
239
34472923 240 # blog.ipfire.org - LEGACY REDIRECTION
c85f80e2 241 self.add_handlers(r"blog\.([a-z]+\.dev\.)?ipfire\.org", [
34472923
MT
242 (r"/", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/blog" }),
243 (r"/authors/(\w+)", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/blog/authors/{0}" }),
244 (r"/post/([0-9a-z\-\._]+)", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/blog/{0}" }),
245 (r"/tags/([0-9a-z\-\.]+)", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/blog/tags/{0}" }),
f0714277
MT
246
247 # RSS Feed
34472923
MT
248 (r"/feed.xml", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/blog/feed.xml" }),
249 ])
12e5de7e 250
940227cb 251 # downloads.ipfire.org
c85f80e2 252 self.add_handlers(r"downloads\.([a-z]+\.dev\.)?ipfire\.org", [
82d4e789 253 (r"/", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/download" }),
450972a3 254 (r"/release/(.*)", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/download/{0}" }),
aec63a26 255 (r"/(.*)", downloads.FileHandler),
54af860e 256 ])
940227cb
MT
257
258 # mirrors.ipfire.org
c85f80e2 259 self.add_handlers(r"mirrors\.([a-z]+\.dev\.)?ipfire\.org", [
278b3118
MT
260 (r"/", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/download/mirrors" }),
261 (r"/mirrors/(.*)", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/download/mirrors/{0}" }),
3808b871 262 ])
940227cb 263
d0d074e0 264 # planet.ipfire.org
c85f80e2 265 self.add_handlers(r"planet\.([a-z]+\.dev\.)?ipfire\.org", [
3d4ce901 266 (r"/", tornado.web.RedirectHandler, { "url" : "https://blog.ipfire.org/" }),
7fa7d38d
MT
267 (r"/post/([A-Za-z0-9_-]+)", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/blog/{0}" }),
268 (r"/user/([a-z0-9_-]+)", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/blog/authors/{0}" }),
bcc3ed4d
MT
269
270 # RSS
f0714277 271 (r"/rss", tornado.web.RedirectHandler, { "url" : "https://blog.ipfire.org/feed.xml" }),
d76ec66e 272 (r"/user/([a-z0-9_-]+)/rss", tornado.web.RedirectHandler, { "url" : "https://blog.ipfire.org/feed.xml" }),
f0714277 273 (r"/news.rss", tornado.web.RedirectHandler, { "url" : "https://blog.ipfire.org/feed.xml" }),
3808b871 274 ])
d0d074e0 275
66862195 276 # fireinfo.ipfire.org
c85f80e2 277 self.add_handlers(r"fireinfo\.([a-z]+\.dev\.)?ipfire\.org", [
96c9bb79 278 (r"/", fireinfo.IndexHandler),
8ab37e0b 279
dc96f754
MT
280 # Admin
281 (r"/admin", fireinfo.AdminIndexHandler),
282
8ab37e0b
MT
283 # Vendors
284 (r"/vendors", fireinfo.VendorsHandler),
285 (r"/vendors/(pci|usb)/([0-9a-f]{4})", fireinfo.VendorHandler),
66862195 286
1e3b2aad 287 # Driver
0cd21a36 288 (r"/drivers/(.*)", fireinfo.DriverDetail),
1e3b2aad 289
66862195 290 # Show profiles
96c9bb79 291 (r"/profile/random", fireinfo.RandomProfileHandler),
b84b407f 292 (r"/profile/([a-z0-9]{40})", fireinfo.ProfileHandler),
91a446f0 293
ed2e3c1f 294 # Stats
19518d6e 295 (r"/processors", fireinfo.ProcessorsHandler),
ed2e3c1f
MT
296 (r"/releases", fireinfo.ReleasesHandler),
297
19518d6e 298 # Send profiles
96c9bb79 299 (r"/send/([a-z0-9]+)", fireinfo.ProfileSendHandler),
0ed3ea5c
MT
300
301 # Serve any static files
302 (r"/static/(.*)", tornado.web.StaticFileHandler, { "path" : self.settings.get("static_path") }),
dc96f754 303 ] + authentication_handlers)
5cf160e0 304
c37ec602 305 # i-use.ipfire.org
c85f80e2 306 self.add_handlers(r"i-use\.([a-z]+\.dev\.)?ipfire\.org", [
e2f2865d 307 (r"/", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/" }),
395c1ac0 308 (r"/profile/([a-f0-9]{40})/([0-9]+).png", iuse.ImageHandler),
c37ec602
MT
309 ])
310
8e2e1261
MT
311 # boot.ipfire.org
312 BOOT_STATIC_PATH = os.path.join(self.settings["static_path"], "netboot")
c85f80e2 313 self.add_handlers(r"boot\.([a-z]+\.dev\.)?ipfire\.org", [
f301d952 314 (r"/", tornado.web.RedirectHandler, { "url" : "https://wiki.ipfire.org/installation/pxe" }),
8e2e1261
MT
315
316 # Configurations
f301d952
MT
317 (r"/premenu.cfg", boot.PremenuCfgHandler),
318 (r"/menu.gpxe", boot.MenuGPXEHandler),
319 (r"/menu.cfg", boot.MenuCfgHandler),
8e2e1261
MT
320
321 # Static files
37b5c0cf 322 (r"/(boot\.png|pxelinux\.0|menu\.c32|vesamenu\.c32)",
8e2e1261
MT
323 tornado.web.StaticFileHandler, { "path" : BOOT_STATIC_PATH }),
324 ])
325
60024cc8 326 # nopaste.ipfire.org
c85f80e2 327 self.add_handlers(r"nopaste\.([a-z]+\.dev\.)?ipfire\.org", [
a41085fb
MT
328 (r"/", nopaste.CreateHandler),
329 (r"/raw/(.*)", nopaste.RawHandler),
330 (r"/view/(.*)", nopaste.ViewHandler),
0ed3ea5c
MT
331
332 # Serve any static files
333 (r"/static/(.*)", tornado.web.StaticFileHandler, { "path" : self.settings.get("static_path") }),
3808b871 334 ] + authentication_handlers)
60024cc8 335
f5b01fc2 336 # location.ipfire.org
c85f80e2 337 self.add_handlers(r"location\.([a-z]+\.dev\.)?ipfire\.org", [
d9f7b6e5 338 (r"/(.*)", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/projects/location/{0}" }),
f5b01fc2
MT
339 ])
340
9068dba1 341 # geoip.ipfire.org
c85f80e2 342 self.add_handlers(r"geoip\.([a-z]+\.dev\.)?ipfire\.org", [
f5b01fc2 343 (r"/", tornado.web.RedirectHandler, { "url" : "https://location.ipfire.org/" }),
3808b871 344 ])
9068dba1 345
66862195 346 # talk.ipfire.org
c85f80e2 347 self.add_handlers(r"talk\.([a-z]+\.dev\.)?ipfire\.org", [
786e9ca8
MT
348 (r"/", tornado.web.RedirectHandler, { "url" : "https://people.ipfire.org/" }),
349 ])
66862195 350
03706893 351 # people.ipfire.org
c85f80e2 352 self.add_handlers(r"people\.([a-z]+\.dev\.)?ipfire\.org", [
b6026905
MT
353 (r"/", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/users" }),
354 (r"/register", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/register" }),
355 (r"/users", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/users" }),
356 (r"/users/([a-z_][a-z0-9_-]{0,31})", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/users/{0}" }),
357 (r"/users/([a-z_][a-z0-9_-]{0,31})\.jpg", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/users/{0}.jpg" }),
358 ])
2cd9af74 359
181d08f3 360 # wiki.ipfire.org
9741abf5
MT
361 self.add_handlers(r"wiki\.([a-z]+\.dev\.)?ipfire\.org", [
362 (r"/(.*)", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org/docs/{0}" }),
181d08f3
MT
363 ])
364
ae0228e1 365 # ipfire.org
e65b9a6d 366 self.add_handlers(r"(.*)ipfire\.org", [
ba43a892 367 (r".*", tornado.web.RedirectHandler, { "url" : "https://www.ipfire.org" })
5cf160e0 368 ])
3add293a 369
feb02477
MT
370 logging.info("Successfully initialied application")
371
574a88c7 372 def format_country_name(self, handler, country_code):
e929ed92 373 return self.backend.get_country_name(country_code)
574a88c7 374
8d45158b
MT
375 def format_currency(self, handler, currency):
376 return babel.numbers.get_currency_name(currency, locale=handler.locale.code)
377
378 def format_money(self, handler, amount, currency):
379 return babel.numbers.format_currency(amount, currency, locale=handler.locale.code)
380
6eddfb50
MT
381 def format_language_name(self, handler, language):
382 _ = handler.locale.translate
383
384 if language == "de":
385 return _("German")
386 elif language == "en":
387 return _("English")
388 elif language == "es":
389 return _("Spanish")
390 elif language == "fr":
391 return _("French")
392 elif language == "it":
393 return _("Italian")
394 elif language == "nl":
395 return _("Dutch")
396 elif language == "pl":
397 return _("Polish")
398 elif language == "pt":
399 return _("Portuguese")
400 elif language == "ru":
401 return _("Russian")
402 elif language == "tr":
403 return _("Turkish")
404
405 return language
406
cc3b928d
MT
407 def format_month_name(self, handler, month):
408 _ = handler.locale.translate
409
410 if month == 1:
411 return _("January")
412 elif month == 2:
413 return _("February")
414 elif month == 3:
415 return _("March")
416 elif month == 4:
417 return _("April")
418 elif month == 5:
419 return _("May")
420 elif month == 6:
421 return _("June")
422 elif month == 7:
423 return _("July")
424 elif month == 8:
425 return _("August")
426 elif month == 9:
427 return _("September")
428 elif month == 10:
429 return _("October")
430 elif month == 11:
431 return _("November")
432 elif month == 12:
433 return _("December")
434
435 return month
401827c2 436
e96e445b
MT
437 def format_phone_number(self, handler, number):
438 if not isinstance(number, phonenumbers.PhoneNumber):
439 try:
01e73b0e 440 number = phonenumbers.parse(number, None)
e96e445b
MT
441 except phonenumbers.phonenumberutil.NumberParseException:
442 return number
c01ad253
MT
443
444 return phonenumbers.format_number(number, phonenumbers.PhoneNumberFormat.INTERNATIONAL)
445
e96e445b
MT
446 def format_phone_number_to_e164(self, handler, number):
447 return phonenumbers.format_number(number, phonenumbers.PhoneNumberFormat.E164)
448
449 def format_phone_number_location(self, handler, number):
450 s = [
451 phonenumbers.geocoder.description_for_number(number, handler.locale.code),
452 phonenumbers.region_code_for_number(number),
453 ]
454
455 return ", ".join((e for e in s if e))