1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011 Free Software Foundation, Inc
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.
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.
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/>.
17 from email
.MIMEText
import MIMEText
24 from mediagoblin
.db
.util
import ObjectId
27 from mediagoblin
import globals as mgoblin_globals
34 def _activate_testing():
36 Call this to activate testing in util.py
42 def get_jinja_loader(user_template_path
=None):
44 Set up the Jinja template loaders, possibly allowing for user
47 (In the future we may have another system for providing theming;
48 for now this is good enough.)
50 if user_template_path
:
51 return jinja2
.ChoiceLoader(
52 [jinja2
.FileSystemLoader(user_template_path
),
53 jinja2
.PackageLoader('mediagoblin', 'templates')])
55 return jinja2
.PackageLoader('mediagoblin', 'templates')
58 def get_jinja_env(template_loader
, locale
):
60 Set up the Jinja environment,
62 (In the future we may have another system for providing theming;
63 for now this is good enough.)
67 template_env
= jinja2
.Environment(
68 loader
=template_loader
, autoescape
=True,
69 extensions
=['jinja2.ext.i18n'])
71 template_env
.install_gettext_callables(
72 mgoblin_globals
.translations
.gettext
,
73 mgoblin_globals
.translations
.ngettext
)
78 def setup_user_in_request(request
):
80 Examine a request and tack on a request.user parameter if that's
83 if not request
.session
.has_key('user_id'):
88 user
= request
.app
.db
.User
.one(
89 {'_id': ObjectId(request
.session
['user_id'])})
92 # Something's wrong... this user doesn't exist? Invalidate
94 request
.session
.invalidate()
99 def import_component(import_string
):
101 Import a module component defined by STRING. Probably a method,
102 class, or global variable.
105 - import_string: a string that defines what to import. Written
106 in the format of "module1.module2:component"
108 module_name
, func_name
= import_string
.split(':', 1)
109 __import__(module_name
)
110 module
= sys
.modules
[module_name
]
111 func
= getattr(module
, func_name
)
114 _punct_re
= re
.compile(r
'[\t !"#$%&\'()*\
-/<=>?
@\
[\\\
]^_`
{|
},.]+')
116 def slugify(text, delim=u'-'):
118 Generates an ASCII-only slug. Taken from http://flask.pocoo.org/snippets/5/
121 for word in _punct_re.split(text.lower()):
122 word = word.encode('translit
/long')
125 return unicode(delim.join(result))
127 ### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
128 ### Special email test stuff begins HERE
129 ### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
131 # We have two "test inboxes" here:
135 # If you're writing test views
, you
'll probably want to check this.
136 # It contains a list of MIMEText messages.
138 # EMAIL_TEST_MBOX_INBOX:
139 # ----------------------
140 # This collects the messages from the FakeMhost inbox. It's reslly
141 # just here for testing the send_email method itself.
143 # Anyway this contains:
145 # - to: a list of email recipient addresses
146 # - message: not just the body, but the whole message, including
151 # Before running tests that call functions which send email, you should
152 # always call _clear_test_inboxes() to "wipe" the inboxes clean.
154 EMAIL_TEST_INBOX
= []
155 EMAIL_TEST_MBOX_INBOX
= []
158 class FakeMhost(object):
160 Just a fake mail host so we can capture and test messages
166 def sendmail(self
, from_addr
, to_addrs
, message
):
167 EMAIL_TEST_MBOX_INBOX
.append(
172 def _clear_test_inboxes():
173 global EMAIL_TEST_INBOX
174 global EMAIL_TEST_MBOX_INBOX
175 EMAIL_TEST_INBOX
= []
176 EMAIL_TEST_MBOX_INBOX
= []
178 ### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
179 ### </Special email test stuff>
180 ### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
182 def send_email(from_addr
, to_addrs
, subject
, message_body
):
184 Simple email sending wrapper, use this so we can capture messages
185 for unit testing purposes.
188 - from_addr: address you're sending the email from
189 - to_addrs: list of recipient email addresses
190 - subject: subject of the email
191 - message_body: email body text
193 # TODO: make a mock mhost if testing is enabled
194 if TESTS_ENABLED
or mgoblin_globals
.email_debug_mode
:
196 elif not mgoblin_globals
.email_debug_mode
:
197 mhost
= smtplib
.SMTP()
201 message
= MIMEText(message_body
.encode('utf-8'), 'plain', 'utf-8')
202 message
['Subject'] = subject
203 message
['From'] = from_addr
204 message
['To'] = ', '.join(to_addrs
)
207 EMAIL_TEST_INBOX
.append(message
)
209 if getattr(mgoblin_globals
, 'email_debug_mode', False):
210 print u
"===== Email ====="
211 print u
"From address: %s" % message
['From']
212 print u
"To addresses: %s" % message
['To']
213 print u
"Subject: %s" % message
['Subject']
215 print message
.get_payload(decode
=True)
217 return mhost
.sendmail(from_addr
, to_addrs
, message
.as_string())
225 TRANSLATIONS_PATH
= pkg_resources
.resource_filename(
226 'mediagoblin', 'translations')
229 def locale_to_lower_upper(locale
):
231 Take a locale, regardless of style, and format it like "en-us"
234 lang
, country
= locale
.split('-', 1)
235 return '%s_%s' % (lang
.lower(), country
.upper())
237 lang
, country
= locale
.split('_', 1)
238 return '%s_%s' % (lang
.lower(), country
.upper())
240 return locale
.lower()
243 def locale_to_lower_lower(locale
):
245 Take a locale, regardless of style, and format it like "en_US"
248 lang
, country
= locale
.split('_', 1)
249 return '%s-%s' % (lang
.lower(), country
.lower())
251 return locale
.lower()
254 def get_locale_from_request(request
):
256 Figure out what target language is most appropriate based on the
259 request_form
= request
.GET
or request
.POST
261 if request_form
.has_key('lang'):
262 return locale_to_lower_upper(request_form
['lang'])
264 accept_lang_matches
= request
.accept_language
.best_matches()
266 # Your routing can explicitly specify a target language
267 if request
.matchdict
.has_key('locale'):
268 target_lang
= request
.matchdict
['locale']
269 elif request
.session
.has_key('target_lang'):
270 target_lang
= request
.session
['target_lang']
271 # Pull the first acceptable language
272 elif accept_lang_matches
:
273 target_lang
= accept_lang_matches
[0]
274 # Fall back to English
278 return locale_to_lower_upper(target_lang
)
281 def setup_gettext(locale
):
283 Setup the gettext instance based on this locale
285 # Later on when we have plugins we may want to enable the
286 # multi-translations system they have so we can handle plugin
289 # TODO: fallback nicely on translations from pt_PT to pt if not
291 this_gettext
= gettext
.translation(
292 'mediagoblin', TRANSLATIONS_PATH
, [locale
], fallback
=True)
294 mgoblin_globals
.setup_globals(
295 translations
=this_gettext
)
298 PAGINATION_DEFAULT_PER_PAGE
= 30
300 class Pagination(object):
302 Pagination class for mongodb queries.
304 Initialization through __init__(self, cursor, page=1, per_page=2),
305 get actual data slice through __call__().
308 def __init__(self
, page
, cursor
, per_page
=PAGINATION_DEFAULT_PER_PAGE
):
310 Initializes Pagination
313 - page: requested page
314 - per_page: number of objects per page
318 self
.per_page
= per_page
320 self
.total_count
= self
.cursor
.count()
324 Returns slice of objects for the requested page
326 return self
.cursor
.skip(
327 (self
.page
- 1) * self
.per_page
).limit(self
.per_page
)
331 return int(ceil(self
.total_count
/ float(self
.per_page
)))
339 return self
.page
< self
.pages
341 def iter_pages(self
, left_edge
=2, left_current
=2,
342 right_current
=5, right_edge
=2):
344 for num
in xrange(1, self
.pages
+ 1):
345 if num
<= left_edge
or \
346 (num
> self
.page
- left_current
- 1 and \
347 num
< self
.page
+ right_current
) or \
348 num
> self
.pages
- right_edge
:
354 def get_page_url_explicit(self
, base_url
, get_params
, page_no
):
356 Get a page url by adding a page= parameter to the base url
358 new_get_params
= copy
.copy(get_params
or {})
359 new_get_params
['page'] = page_no
361 base_url
, urllib
.urlencode(new_get_params
))
363 def get_page_url(self
, request
, page_no
):
365 Get a new page url based of the request, and the new page number.
367 This is a nice wrapper around get_page_url_explicit()
369 return self
.get_page_url_explicit(
370 request
.path_info
, request
.GET
, page_no
)