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