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
28 from babel
.localedata
import exists
31 from paste
.deploy
.loadwsgi
import NicerConfigParser
32 from webob
import Response
34 from mediagoblin
import globals as mgoblin_globals
35 from mediagoblin
.db
.util
import ObjectId
39 def _activate_testing():
41 Call this to activate testing in util.py
47 def get_jinja_loader(user_template_path
=None):
49 Set up the Jinja template loaders, possibly allowing for user
52 (In the future we may have another system for providing theming;
53 for now this is good enough.)
55 if user_template_path
:
56 return jinja2
.ChoiceLoader(
57 [jinja2
.FileSystemLoader(user_template_path
),
58 jinja2
.PackageLoader('mediagoblin', 'templates')])
60 return jinja2
.PackageLoader('mediagoblin', 'templates')
66 def get_jinja_env(template_loader
, locale
):
68 Set up the Jinja environment,
70 (In the future we may have another system for providing theming;
71 for now this is good enough.)
75 # If we have a jinja environment set up with this locale, just
77 if SETUP_JINJA_ENVS
.has_key(locale
):
78 return SETUP_JINJA_ENVS
[locale
]
80 template_env
= jinja2
.Environment(
81 loader
=template_loader
, autoescape
=True,
82 extensions
=['jinja2.ext.i18n'])
84 template_env
.install_gettext_callables(
85 mgoblin_globals
.translations
.gettext
,
86 mgoblin_globals
.translations
.ngettext
)
89 SETUP_JINJA_ENVS
[locale
] = template_env
94 # We'll store context information here when doing unit tests
95 TEMPLATE_TEST_CONTEXT
= {}
98 def render_template(request
, template
, context
):
100 Render a template with context.
102 Always inserts the request into the context, so you don't have to.
103 Also stores the context if we're doing unit tests. Helpful!
105 template
= request
.template_env
.get_template(
107 context
['request'] = request
108 rendered
= template
.render(context
)
111 TEMPLATE_TEST_CONTEXT
[template
] = context
116 def clear_test_template_context():
117 global TEMPLATE_TEST_CONTEXT
118 TEMPLATE_TEST_CONTEXT
= {}
121 def render_to_response(request
, template
, context
):
122 """Much like Django's shortcut.render()"""
123 return Response(render_template(request
, template
, context
))
126 def setup_user_in_request(request
):
128 Examine a request and tack on a request.user parameter if that's
131 if not request
.session
.has_key('user_id'):
136 user
= request
.app
.db
.User
.one(
137 {'_id': ObjectId(request
.session
['user_id'])})
140 # Something's wrong... this user doesn't exist? Invalidate
142 request
.session
.invalidate()
147 def import_component(import_string
):
149 Import a module component defined by STRING. Probably a method,
150 class, or global variable.
153 - import_string: a string that defines what to import. Written
154 in the format of "module1.module2:component"
156 module_name
, func_name
= import_string
.split(':', 1)
157 __import__(module_name
)
158 module
= sys
.modules
[module_name
]
159 func
= getattr(module
, func_name
)
162 _punct_re
= re
.compile(r
'[\t !"#$%&\'()*\
-/<=>?
@\
[\\\
]^_`
{|
},.]+')
164 def slugify(text, delim=u'-'):
166 Generates an ASCII-only slug. Taken from http://flask.pocoo.org/snippets/5/
169 for word in _punct_re.split(text.lower()):
170 word = word.encode('translit
/long')
173 return unicode(delim.join(result))
175 ### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
176 ### Special email test stuff begins HERE
177 ### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
179 # We have two "test inboxes" here:
183 # If you're writing test views
, you
'll probably want to check this.
184 # It contains a list of MIMEText messages.
186 # EMAIL_TEST_MBOX_INBOX:
187 # ----------------------
188 # This collects the messages from the FakeMhost inbox. It's reslly
189 # just here for testing the send_email method itself.
191 # Anyway this contains:
193 # - to: a list of email recipient addresses
194 # - message: not just the body, but the whole message, including
199 # Before running tests that call functions which send email, you should
200 # always call _clear_test_inboxes() to "wipe" the inboxes clean.
202 EMAIL_TEST_INBOX
= []
203 EMAIL_TEST_MBOX_INBOX
= []
206 class FakeMhost(object):
208 Just a fake mail host so we can capture and test messages
214 def sendmail(self
, from_addr
, to_addrs
, message
):
215 EMAIL_TEST_MBOX_INBOX
.append(
220 def _clear_test_inboxes():
221 global EMAIL_TEST_INBOX
222 global EMAIL_TEST_MBOX_INBOX
223 EMAIL_TEST_INBOX
= []
224 EMAIL_TEST_MBOX_INBOX
= []
226 ### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
227 ### </Special email test stuff>
228 ### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
230 def send_email(from_addr
, to_addrs
, subject
, message_body
):
232 Simple email sending wrapper, use this so we can capture messages
233 for unit testing purposes.
236 - from_addr: address you're sending the email from
237 - to_addrs: list of recipient email addresses
238 - subject: subject of the email
239 - message_body: email body text
241 # TODO: make a mock mhost if testing is enabled
242 if TESTS_ENABLED
or mgoblin_globals
.email_debug_mode
:
244 elif not mgoblin_globals
.email_debug_mode
:
245 mhost
= smtplib
.SMTP()
249 message
= MIMEText(message_body
.encode('utf-8'), 'plain', 'utf-8')
250 message
['Subject'] = subject
251 message
['From'] = from_addr
252 message
['To'] = ', '.join(to_addrs
)
255 EMAIL_TEST_INBOX
.append(message
)
257 if getattr(mgoblin_globals
, 'email_debug_mode', False):
258 print u
"===== Email ====="
259 print u
"From address: %s" % message
['From']
260 print u
"To addresses: %s" % message
['To']
261 print u
"Subject: %s" % message
['Subject']
263 print message
.get_payload(decode
=True)
265 return mhost
.sendmail(from_addr
, to_addrs
, message
.as_string())
273 TRANSLATIONS_PATH
= pkg_resources
.resource_filename(
274 'mediagoblin', 'translations')
277 def locale_to_lower_upper(locale
):
279 Take a locale, regardless of style, and format it like "en-us"
282 lang
, country
= locale
.split('-', 1)
283 return '%s_%s' % (lang
.lower(), country
.upper())
285 lang
, country
= locale
.split('_', 1)
286 return '%s_%s' % (lang
.lower(), country
.upper())
288 return locale
.lower()
291 def locale_to_lower_lower(locale
):
293 Take a locale, regardless of style, and format it like "en_US"
296 lang
, country
= locale
.split('_', 1)
297 return '%s-%s' % (lang
.lower(), country
.lower())
299 return locale
.lower()
302 def get_locale_from_request(request
):
304 Figure out what target language is most appropriate based on the
307 request_form
= request
.GET
or request
.POST
309 if request_form
.has_key('lang'):
310 return locale_to_lower_upper(request_form
['lang'])
312 accept_lang_matches
= request
.accept_language
.best_matches()
314 # Your routing can explicitly specify a target language
315 if request
.matchdict
.has_key('locale'):
316 target_lang
= request
.matchdict
['locale']
317 elif request
.session
.has_key('target_lang'):
318 target_lang
= request
.session
['target_lang']
319 # Pull the first acceptable language
320 elif accept_lang_matches
:
321 target_lang
= accept_lang_matches
[0]
322 # Fall back to English
326 return locale_to_lower_upper(target_lang
)
329 def read_config_file(conf_file
):
331 Read a paste deploy style config file and process it.
333 if not os
.path
.exists(conf_file
):
335 "MEDIAGOBLIN_CONFIG not set or file does not exist")
337 parser
= NicerConfigParser(conf_file
)
338 parser
.read(conf_file
)
339 parser
._defaults
.setdefault(
340 'here', os
.path
.dirname(os
.path
.abspath(conf_file
)))
341 parser
._defaults
.setdefault(
342 '__file__', os
.path
.abspath(conf_file
))
345 [(section_name
, dict(parser
.items(section_name
)))
346 for section_name
in parser
.sections()])
353 def setup_gettext(locale
):
355 Setup the gettext instance based on this locale
357 # Later on when we have plugins we may want to enable the
358 # multi-translations system they have so we can handle plugin
361 # TODO: fallback nicely on translations from pt_PT to pt if not
363 if SETUP_GETTEXTS
.has_key(locale
):
364 this_gettext
= SETUP_GETTEXTS
[locale
]
366 this_gettext
= gettext
.translation(
367 'mediagoblin', TRANSLATIONS_PATH
, [locale
], fallback
=True)
369 SETUP_GETTEXTS
[locale
] = this_gettext
371 mgoblin_globals
.setup_globals(
372 translations
=this_gettext
)
375 PAGINATION_DEFAULT_PER_PAGE
= 30
377 class Pagination(object):
379 Pagination class for mongodb queries.
381 Initialization through __init__(self, cursor, page=1, per_page=2),
382 get actual data slice through __call__().
385 def __init__(self
, page
, cursor
, per_page
=PAGINATION_DEFAULT_PER_PAGE
):
387 Initializes Pagination
390 - page: requested page
391 - per_page: number of objects per page
395 self
.per_page
= per_page
397 self
.total_count
= self
.cursor
.count()
401 Returns slice of objects for the requested page
403 return self
.cursor
.skip(
404 (self
.page
- 1) * self
.per_page
).limit(self
.per_page
)
408 return int(ceil(self
.total_count
/ float(self
.per_page
)))
416 return self
.page
< self
.pages
418 def iter_pages(self
, left_edge
=2, left_current
=2,
419 right_current
=5, right_edge
=2):
421 for num
in xrange(1, self
.pages
+ 1):
422 if num
<= left_edge
or \
423 (num
> self
.page
- left_current
- 1 and \
424 num
< self
.page
+ right_current
) or \
425 num
> self
.pages
- right_edge
:
431 def get_page_url_explicit(self
, base_url
, get_params
, page_no
):
433 Get a page url by adding a page= parameter to the base url
435 new_get_params
= copy
.copy(get_params
or {})
436 new_get_params
['page'] = page_no
438 base_url
, urllib
.urlencode(new_get_params
))
440 def get_page_url(self
, request
, page_no
):
442 Get a new page url based of the request, and the new page number.
444 This is a nice wrapper around get_page_url_explicit()
446 return self
.get_page_url_explicit(
447 request
.path_info
, request
.GET
, page_no
)