Fixing changes
[mediagoblin.git] / mediagoblin / util.py
CommitLineData
8e1e744d 1# GNU MediaGoblin -- federated, autonomous media hosting
e5572c60
ML
2# Copyright (C) 2011 Free Software Foundation, Inc
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Affero General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
4d4f6050 17from email.MIMEText import MIMEText
b77eec65
CAW
18import gettext
19import pkg_resources
4d4f6050 20import smtplib
cb8ea0fe 21import sys
0546833c 22import re
31a8ff42 23import jinja2
58dec5ef 24import mongokit
0546833c 25import translitcodec
31a8ff42 26
29f3fb70
CAW
27from mediagoblin import globals as mgoblin_globals
28
4d4f6050
CAW
29
30TESTS_ENABLED = False
31def _activate_testing():
32 """
33 Call this to activate testing in util.py
34 """
35 global TESTS_ENABLED
36 TESTS_ENABLED = True
37
38
0e0e3d9a 39def get_jinja_loader(user_template_path=None):
904f61c2 40 """
0e0e3d9a 41 Set up the Jinja template loaders, possibly allowing for user
904f61c2
CAW
42 overridden templates.
43
44 (In the future we may have another system for providing theming;
45 for now this is good enough.)
46 """
31a8ff42 47 if user_template_path:
0e0e3d9a 48 return jinja2.ChoiceLoader(
31a8ff42
CAW
49 [jinja2.FileSystemLoader(user_template_path),
50 jinja2.PackageLoader('mediagoblin', 'templates')])
51 else:
0e0e3d9a 52 return jinja2.PackageLoader('mediagoblin', 'templates')
31a8ff42 53
0e0e3d9a
CAW
54
55def get_jinja_env(template_loader, locale):
56 """
57 Set up the Jinja environment,
58
59 (In the future we may have another system for providing theming;
60 for now this is good enough.)
61 """
b77eec65
CAW
62 setup_gettext(locale)
63
64 template_env = jinja2.Environment(
0e0e3d9a 65 loader=template_loader, autoescape=True,
20c834ff 66 extensions=['jinja2.ext.i18n'])
58dec5ef 67
b77eec65
CAW
68 template_env.install_gettext_callables(
69 mgoblin_globals.translations.gettext,
70 mgoblin_globals.translations.ngettext)
71
72 return template_env
73
58dec5ef
CAW
74
75def setup_user_in_request(request):
76 """
77 Examine a request and tack on a request.user parameter if that's
78 appropriate.
79 """
80 if not request.session.has_key('user_id'):
59dd5c7e 81 request.user = None
58dec5ef
CAW
82 return
83
5d6840a0 84 user = None
6648c52b 85 user = request.app.db.User.one(
c74e1462 86 {'_id': mongokit.ObjectId(request.session['user_id'])})
5d6840a0 87
c74e1462
CAW
88 if not user:
89 # Something's wrong... this user doesn't exist? Invalidate
90 # this session.
58dec5ef 91 request.session.invalidate()
5d6840a0
CAW
92
93 request.user = user
cb8ea0fe
CAW
94
95
96def import_component(import_string):
97 """
98 Import a module component defined by STRING. Probably a method,
99 class, or global variable.
100
101 Args:
102 - import_string: a string that defines what to import. Written
103 in the format of "module1.module2:component"
104 """
105 module_name, func_name = import_string.split(':', 1)
106 __import__(module_name)
107 module = sys.modules[module_name]
108 func = getattr(module, func_name)
109 return func
4d4f6050 110
0546833c
AW
111_punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')
112
113def slugify(text, delim=u'-'):
114 """
115 Generates an ASCII-only slug. Taken from http://flask.pocoo.org/snippets/5/
116 """
117 result = []
118 for word in _punct_re.split(text.lower()):
119 word = word.encode('translit/long')
120 if word:
121 result.append(word)
122 return unicode(delim.join(result))
4d4f6050
CAW
123
124### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
125### Special email test stuff begins HERE
126### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
127
128# We have two "test inboxes" here:
129#
130# EMAIL_TEST_INBOX:
131# ----------------
132# If you're writing test views, you'll probably want to check this.
133# It contains a list of MIMEText messages.
134#
135# EMAIL_TEST_MBOX_INBOX:
136# ----------------------
137# This collects the messages from the FakeMhost inbox. It's reslly
138# just here for testing the send_email method itself.
139#
140# Anyway this contains:
141# - from
142# - to: a list of email recipient addresses
143# - message: not just the body, but the whole message, including
144# headers, etc.
145#
146# ***IMPORTANT!***
147# ----------------
148# Before running tests that call functions which send email, you should
149# always call _clear_test_inboxes() to "wipe" the inboxes clean.
150
151EMAIL_TEST_INBOX = []
152EMAIL_TEST_MBOX_INBOX = []
153
154
155class FakeMhost(object):
156 """
157 Just a fake mail host so we can capture and test messages
158 from send_email
159 """
160 def connect(self):
161 pass
162
163 def sendmail(self, from_addr, to_addrs, message):
164 EMAIL_TEST_MBOX_INBOX.append(
165 {'from': from_addr,
166 'to': to_addrs,
167 'message': message})
168
169def _clear_test_inboxes():
170 global EMAIL_TEST_INBOX
171 global EMAIL_TEST_MBOX_INBOX
172 EMAIL_TEST_INBOX = []
173 EMAIL_TEST_MBOX_INBOX = []
174
175### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
176### </Special email test stuff>
177### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
178
179def send_email(from_addr, to_addrs, subject, message_body):
61ec968b
CAW
180 """
181 Simple email sending wrapper, use this so we can capture messages
182 for unit testing purposes.
183
184 Args:
185 - from_addr: address you're sending the email from
186 - to_addrs: list of recipient email addresses
187 - subject: subject of the email
188 - message_body: email body text
189 """
4d4f6050 190 # TODO: make a mock mhost if testing is enabled
29f3fb70 191 if TESTS_ENABLED or mgoblin_globals.email_debug_mode:
4d4f6050 192 mhost = FakeMhost()
29f3fb70 193 elif not mgoblin_globals.email_debug_mode:
4d4f6050
CAW
194 mhost = smtplib.SMTP()
195
196 mhost.connect()
197
198 message = MIMEText(message_body.encode('utf-8'), 'plain', 'utf-8')
199 message['Subject'] = subject
200 message['From'] = from_addr
201 message['To'] = ', '.join(to_addrs)
202
203 if TESTS_ENABLED:
204 EMAIL_TEST_INBOX.append(message)
205
21919313 206 if getattr(mgoblin_globals, 'email_debug_mode', False):
29f3fb70
CAW
207 print u"===== Email ====="
208 print u"From address: %s" % message['From']
209 print u"To addresses: %s" % message['To']
210 print u"Subject: %s" % message['Subject']
211 print u"-- Body: --"
212 print message.get_payload(decode=True)
213
21919313 214 return mhost.sendmail(from_addr, to_addrs, message.as_string())
20c834ff 215
8b28bee4
CAW
216
217###################
218# Translation tools
219###################
220
221
b77eec65
CAW
222TRANSLATIONS_PATH = pkg_resources.resource_filename(
223 'mediagoblin', 'translations')
224
225
8b28bee4
CAW
226def locale_to_lower_upper(locale):
227 """
228 Take a locale, regardless of style, and format it like "en-us"
229 """
230 if '-' in locale:
231 lang, country = locale.split('-', 1)
232 return '%s_%s' % (lang.lower(), country.upper())
233 elif '_' in locale:
234 lang, country = locale.split('_', 1)
235 return '%s_%s' % (lang.lower(), country.upper())
236 else:
237 return locale.lower()
238
239
240def locale_to_lower_lower(locale):
241 """
242 Take a locale, regardless of style, and format it like "en_US"
243 """
244 if '_' in locale:
245 lang, country = locale.split('_', 1)
246 return '%s-%s' % (lang.lower(), country.lower())
247 else:
248 return locale.lower()
249
250
251def get_locale_from_request(request):
252 """
253 Figure out what target language is most appropriate based on the
254 request
255 """
256 request_form = request.GET or request.POST
257
258 if request_form.has_key('lang'):
259 return locale_to_lower_upper(request_form['lang'])
260
261 accept_lang_matches = request.accept_language.best_matches()
262
263 # Your routing can explicitly specify a target language
376e6ef2
CAW
264 if request.matchdict.has_key('locale'):
265 target_lang = request.matchdict['locale']
8b28bee4
CAW
266 elif request.session.has_key('target_lang'):
267 target_lang = request.session['target_lang']
268 # Pull the first acceptable language
269 elif accept_lang_matches:
270 target_lang = accept_lang_matches[0]
271 # Fall back to English
272 else:
273 target_lang = 'en'
274
0e0e3d9a 275 return locale_to_lower_upper(target_lang)
b77eec65
CAW
276
277
278def setup_gettext(locale):
279 """
280 Setup the gettext instance based on this locale
281 """
282 # Later on when we have plugins we may want to enable the
283 # multi-translations system they have so we can handle plugin
284 # translations too
285
286 # TODO: fallback nicely on translations from pt_PT to pt if not
287 # available, etc.
288 this_gettext = gettext.translation(
289 'mediagoblin', TRANSLATIONS_PATH, [locale], fallback=True)
290
291 mgoblin_globals.setup_globals(
292 translations=this_gettext)