# GNU MediaGoblin -- federated, autonomous media hosting
-# Copyright (C) 2011 Free Software Foundation, Inc
+# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
from __future__ import division
from email.MIMEText import MIMEText
-import gettext
-import pkg_resources
+#import gettext
+#import pkg_resources
import smtplib
import sys
-import re
+#import re
+#import translitcodec
import urllib
from math import ceil, floor
import copy
+import wtforms
-from babel.localedata import exists
-import jinja2
-import translitcodec
+#from babel.localedata import exists
+#from babel.support import LazyProxy
+#import jinja2
from webob import Response, exc
from lxml.html.clean import Cleaner
import markdown
+from wtforms.form import Form
from mediagoblin import mg_globals
-from mediagoblin import messages
+#from mediagoblin import messages
from mediagoblin.db.util import ObjectId
+from mediagoblin.tools import url
+from mediagoblin.tools import common
+from mediagoblin.tools.template import TEMPLATE_TEST_CONTEXT, render_template
from itertools import izip, count
-TESTS_ENABLED = False
+DISPLAY_IMAGE_FETCHING_ORDER = [u'medium', u'original', u'thumb']
+
def _activate_testing():
"""
Call this to activate testing in util.py
"""
- global TESTS_ENABLED
- TESTS_ENABLED = True
+ common.TESTS_ENABLED = True
def clear_test_buckets():
"""
clear_test_template_context()
-SETUP_JINJA_ENVS = {}
+# SETUP_JINJA_ENVS = {}
-def get_jinja_env(template_loader, locale):
- """
- Set up the Jinja environment,
+# def get_jinja_env(template_loader, locale):
+# """
+# Set up the Jinja environment,
- (In the future we may have another system for providing theming;
- for now this is good enough.)
- """
- setup_gettext(locale)
+# (In the future we may have another system for providing theming;
+# for now this is good enough.)
+# """
+# setup_gettext(locale)
- # If we have a jinja environment set up with this locale, just
- # return that one.
- if SETUP_JINJA_ENVS.has_key(locale):
- return SETUP_JINJA_ENVS[locale]
+# # If we have a jinja environment set up with this locale, just
+# # return that one.
+# if SETUP_JINJA_ENVS.has_key(locale):
+# return SETUP_JINJA_ENVS[locale]
- template_env = jinja2.Environment(
- loader=template_loader, autoescape=True,
- extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'])
+# template_env = jinja2.Environment(
+# loader=template_loader, autoescape=True,
+# extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'])
- template_env.install_gettext_callables(
- mg_globals.translations.gettext,
- mg_globals.translations.ngettext)
+# template_env.install_gettext_callables(
+# mg_globals.translations.ugettext,
+# mg_globals.translations.ungettext)
- # All templates will know how to ...
- # ... fetch all waiting messages and remove them from the queue
- template_env.globals['fetch_messages'] = messages.fetch_messages
+# # All templates will know how to ...
+# # ... fetch all waiting messages and remove them from the queue
+# # ... construct a grid of thumbnails or other media
+# template_env.globals['fetch_messages'] = messages.fetch_messages
+# template_env.globals['gridify_list'] = gridify_list
+# template_env.globals['gridify_cursor'] = gridify_cursor
- if exists(locale):
- SETUP_JINJA_ENVS[locale] = template_env
+# if exists(locale):
+# SETUP_JINJA_ENVS[locale] = template_env
- return template_env
+# return template_env
-# We'll store context information here when doing unit tests
-TEMPLATE_TEST_CONTEXT = {}
+# # We'll store context information here when doing unit tests
+# TEMPLATE_TEST_CONTEXT = {}
-def render_template(request, template_path, context):
- """
- Render a template with context.
+# def render_template(request, template_path, context):
+# """
+# Render a template with context.
- Always inserts the request into the context, so you don't have to.
- Also stores the context if we're doing unit tests. Helpful!
- """
- template = request.template_env.get_template(
- template_path)
- context['request'] = request
- rendered = template.render(context)
+# Always inserts the request into the context, so you don't have to.
+# Also stores the context if we're doing unit tests. Helpful!
+# """
+# template = request.template_env.get_template(
+# template_path)
+# context['request'] = request
+# rendered = template.render(context)
- if TESTS_ENABLED:
- TEMPLATE_TEST_CONTEXT[template_path] = context
+# if TESTS_ENABLED:
+# TEMPLATE_TEST_CONTEXT[template_path] = context
- return rendered
+# return rendered
def clear_test_template_context():
TEMPLATE_TEST_CONTEXT = {}
-def render_to_response(request, template, context):
+def render_to_response(request, template, context, status=200):
"""Much like Django's shortcut.render()"""
- return Response(render_template(request, template, context))
+ return Response(
+ render_template(request, template, context),
+ status=status)
def redirect(request, *args, **kwargs):
func = getattr(module, func_name)
return func
-_punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')
+# _punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')
-def slugify(text, delim=u'-'):
- """
- Generates an ASCII-only slug. Taken from http://flask.pocoo.org/snippets/5/
- """
- result = []
- for word in _punct_re.split(text.lower()):
- word = word.encode('translit/long')
- if word:
- result.append(word)
- return unicode(delim.join(result))
+# def slugify(text, delim=u'-'):
+# """
+# Generates an ASCII-only slug. Taken from http://flask.pocoo.org/snippets/5/
+# """
+# result = []
+# for word in _punct_re.split(text.lower()):
+# word = word.encode('translit/long')
+# if word:
+# result.append(word)
+# return unicode(delim.join(result))
### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### Special email test stuff begins HERE
Just a fake mail host so we can capture and test messages
from send_email
"""
- def connect(self):
+ def login(self, *args, **kwargs):
pass
def sendmail(self, from_addr, to_addrs, message):
- subject: subject of the email
- message_body: email body text
"""
- # TODO: make a mock mhost if testing is enabled
- if TESTS_ENABLED or mg_globals.email_debug_mode:
+ if common.TESTS_ENABLED or mg_globals.app_config['email_debug_mode']:
mhost = FakeMhost()
- elif not mg_globals.email_debug_mode:
- mhost = smtplib.SMTP()
+ elif not mg_globals.app_config['email_debug_mode']:
+ mhost = smtplib.SMTP(
+ mg_globals.app_config['email_smtp_host'],
+ mg_globals.app_config['email_smtp_port'])
- mhost.connect()
+ # SMTP.__init__ Issues SMTP.connect implicitly if host
+ if not mg_globals.app_config['email_smtp_host']: # e.g. host = ''
+ mhost.connect() # We SMTP.connect explicitly
+
+ if mg_globals.app_config['email_smtp_user'] \
+ or mg_globals.app_config['email_smtp_pass']:
+ mhost.login(
+ mg_globals.app_config['email_smtp_user'],
+ mg_globals.app_config['email_smtp_pass'])
message = MIMEText(message_body.encode('utf-8'), 'plain', 'utf-8')
message['Subject'] = subject
message['From'] = from_addr
message['To'] = ', '.join(to_addrs)
- if TESTS_ENABLED:
+ if common.TESTS_ENABLED:
EMAIL_TEST_INBOX.append(message)
- if getattr(mg_globals, 'email_debug_mode', False):
+ if mg_globals.app_config['email_debug_mode']:
print u"===== Email ====="
print u"From address: %s" % message['From']
print u"To addresses: %s" % message['To']
return mhost.sendmail(from_addr, to_addrs, message.as_string())
-###################
-# Translation tools
-###################
+# ###################
+# # Translation tools
+# ###################
-TRANSLATIONS_PATH = pkg_resources.resource_filename(
- 'mediagoblin', 'translations')
+# TRANSLATIONS_PATH = pkg_resources.resource_filename(
+# 'mediagoblin', 'i18n')
-def locale_to_lower_upper(locale):
- """
- Take a locale, regardless of style, and format it like "en-us"
- """
- if '-' in locale:
- lang, country = locale.split('-', 1)
- return '%s_%s' % (lang.lower(), country.upper())
- elif '_' in locale:
- lang, country = locale.split('_', 1)
- return '%s_%s' % (lang.lower(), country.upper())
- else:
- return locale.lower()
+# def locale_to_lower_upper(locale):
+# """
+# Take a locale, regardless of style, and format it like "en-us"
+# """
+# if '-' in locale:
+# lang, country = locale.split('-', 1)
+# return '%s_%s' % (lang.lower(), country.upper())
+# elif '_' in locale:
+# lang, country = locale.split('_', 1)
+# return '%s_%s' % (lang.lower(), country.upper())
+# else:
+# return locale.lower()
-def locale_to_lower_lower(locale):
- """
- Take a locale, regardless of style, and format it like "en_US"
- """
- if '_' in locale:
- lang, country = locale.split('_', 1)
- return '%s-%s' % (lang.lower(), country.lower())
- else:
- return locale.lower()
+# def locale_to_lower_lower(locale):
+# """
+# Take a locale, regardless of style, and format it like "en_US"
+# """
+# if '_' in locale:
+# lang, country = locale.split('_', 1)
+# return '%s-%s' % (lang.lower(), country.lower())
+# else:
+# return locale.lower()
-def get_locale_from_request(request):
- """
- Figure out what target language is most appropriate based on the
- request
- """
- request_form = request.GET or request.POST
+# def get_locale_from_request(request):
+# """
+# Figure out what target language is most appropriate based on the
+# request
+# """
+# request_form = request.GET or request.POST
- if request_form.has_key('lang'):
- return locale_to_lower_upper(request_form['lang'])
+# if request_form.has_key('lang'):
+# return locale_to_lower_upper(request_form['lang'])
- accept_lang_matches = request.accept_language.best_matches()
+# accept_lang_matches = request.accept_language.best_matches()
- # Your routing can explicitly specify a target language
- if request.matchdict.has_key('locale'):
- target_lang = request.matchdict['locale']
- elif request.session.has_key('target_lang'):
- target_lang = request.session['target_lang']
- # Pull the first acceptable language
- elif accept_lang_matches:
- target_lang = accept_lang_matches[0]
- # Fall back to English
- else:
- target_lang = 'en'
+# # Your routing can explicitly specify a target language
+# matchdict = request.matchdict or {}
- return locale_to_lower_upper(target_lang)
+# if matchdict.has_key('locale'):
+# target_lang = matchdict['locale']
+# elif request.session.has_key('target_lang'):
+# target_lang = request.session['target_lang']
+# # Pull the first acceptable language
+# elif accept_lang_matches:
+# target_lang = accept_lang_matches[0]
+# # Fall back to English
+# else:
+# target_lang = 'en'
+
+# return locale_to_lower_upper(target_lang)
# A super strict version of the lxml.html cleaner class
return HTML_CLEANER.clean_html(html)
-MARKDOWN_INSTANCE = markdown.Markdown(safe_mode='escape')
+def convert_to_tag_list_of_dicts(tag_string):
+ """
+ Filter input from incoming string containing user tags,
+
+ Strips trailing, leading, and internal whitespace, and also converts
+ the "tags" text into an array of tags
+ """
+ taglist = []
+ if tag_string:
+
+ # Strip out internal, trailing, and leading whitespace
+ stripped_tag_string = u' '.join(tag_string.strip().split())
+
+ # Split the tag string into a list of tags
+ for tag in stripped_tag_string.split(
+ mg_globals.app_config['tags_delimiter']):
+
+ # Ignore empty or duplicate tags
+ if tag.strip() and tag.strip() not in [t['name'] for t in taglist]:
+
+ taglist.append({'name': tag.strip(),
+ 'slug': url.slugify(tag.strip())})
+ return taglist
+
+
+def media_tags_as_string(media_entry_tags):
+ """
+ Generate a string from a media item's tags, stored as a list of dicts
+
+ This is the opposite of convert_to_tag_list_of_dicts
+ """
+ media_tag_string = ''
+ if media_entry_tags:
+ media_tag_string = mg_globals.app_config['tags_delimiter'].join(
+ [tag['name'] for tag in media_entry_tags])
+ return media_tag_string
+
+TOO_LONG_TAG_WARNING = \
+ u'Tags must be shorter than %s characters. Tags that are too long: %s'
+
+def tag_length_validator(form, field):
+ """
+ Make sure tags do not exceed the maximum tag length.
+ """
+ tags = convert_to_tag_list_of_dicts(field.data)
+ too_long_tags = [
+ tag['name'] for tag in tags
+ if len(tag['name']) > mg_globals.app_config['tags_max_length']]
+ if too_long_tags:
+ raise wtforms.ValidationError(
+ TOO_LONG_TAG_WARNING % (mg_globals.app_config['tags_max_length'], \
+ ', '.join(too_long_tags)))
+
+
+MARKDOWN_INSTANCE = markdown.Markdown(safe_mode='escape')
def cleaned_markdown_conversion(text):
"""
return clean_html(MARKDOWN_INSTANCE.convert(text))
-SETUP_GETTEXTS = {}
+# SETUP_GETTEXTS = {}
+
+# def setup_gettext(locale):
+# """
+# Setup the gettext instance based on this locale
+# """
+# # Later on when we have plugins we may want to enable the
+# # multi-translations system they have so we can handle plugin
+# # translations too
+
+# # TODO: fallback nicely on translations from pt_PT to pt if not
+# # available, etc.
+# if SETUP_GETTEXTS.has_key(locale):
+# this_gettext = SETUP_GETTEXTS[locale]
+# else:
+# this_gettext = gettext.translation(
+# 'mediagoblin', TRANSLATIONS_PATH, [locale], fallback=True)
+# if exists(locale):
+# SETUP_GETTEXTS[locale] = this_gettext
+
+# mg_globals.setup_globals(
+# translations=this_gettext)
+
+
+# # Force en to be setup before anything else so that
+# # mg_globals.translations is never None
+# setup_gettext('en')
+
+
+# def pass_to_ugettext(*args, **kwargs):
+# """
+# Pass a translation on to the appropriate ugettext method.
+
+# The reason we can't have a global ugettext method is because
+# mg_globals gets swapped out by the application per-request.
+# """
+# return mg_globals.translations.ugettext(
+# *args, **kwargs)
+
+
+# def lazy_pass_to_ugettext(*args, **kwargs):
+# """
+# Lazily pass to ugettext.
+
+# This is useful if you have to define a translation on a module
+# level but you need it to not translate until the time that it's
+# used as a string.
+# """
+# return LazyProxy(pass_to_ugettext, *args, **kwargs)
+
+
+# def pass_to_ngettext(*args, **kwargs):
+# """
+# Pass a translation on to the appropriate ngettext method.
+
+# The reason we can't have a global ngettext method is because
+# mg_globals gets swapped out by the application per-request.
+# """
+# return mg_globals.translations.ngettext(
+# *args, **kwargs)
+
+
+# def lazy_pass_to_ngettext(*args, **kwargs):
+# """
+# Lazily pass to ngettext.
+
+# This is useful if you have to define a translation on a module
+# level but you need it to not translate until the time that it's
+# used as a string.
+# """
+# return LazyProxy(pass_to_ngettext, *args, **kwargs)
-def setup_gettext(locale):
- """
- Setup the gettext instance based on this locale
- """
- # Later on when we have plugins we may want to enable the
- # multi-translations system they have so we can handle plugin
- # translations too
- # TODO: fallback nicely on translations from pt_PT to pt if not
- # available, etc.
- if SETUP_GETTEXTS.has_key(locale):
- this_gettext = SETUP_GETTEXTS[locale]
- else:
- this_gettext = gettext.translation(
- 'mediagoblin', TRANSLATIONS_PATH, [locale], fallback=True)
- if exists(locale):
- SETUP_GETTEXTS[locale] = this_gettext
+# def fake_ugettext_passthrough(string):
+# """
+# Fake a ugettext call for extraction's sake ;)
- mg_globals.setup_globals(
- translations=this_gettext)
+# In wtforms there's a separate way to define a method to translate
+# things... so we just need to mark up the text so that it can be
+# extracted, not so that it's actually run through gettext.
+# """
+# return string
PAGINATION_DEFAULT_PER_PAGE = 30
"""
return self.get_page_url_explicit(
request.path_info, request.GET, page_no)
+
+
+# def gridify_list(this_list, num_cols=5):
+# """
+# Generates a list of lists where each sub-list's length depends on
+# the number of columns in the list
+# """
+# grid = []
+
+# # Figure out how many rows we should have
+# num_rows = int(ceil(float(len(this_list)) / num_cols))
+
+# for row_num in range(num_rows):
+# slice_min = row_num * num_cols
+# slice_max = (row_num + 1) * num_cols
+
+# row = this_list[slice_min:slice_max]
+
+# grid.append(row)
+
+# return grid
+
+
+# def gridify_cursor(this_cursor, num_cols=5):
+# """
+# Generates a list of lists where each sub-list's length depends on
+# the number of columns in the list
+# """
+# return gridify_list(list(this_cursor), num_cols)
+
+
+def render_404(request):
+ """
+ Render a 404.
+ """
+ return render_to_response(
+ request, 'mediagoblin/404.html', {}, status=400)
+
+def delete_media_files(media):
+ """
+ Delete all files associated with a MediaEntry
+
+ Arguments:
+ - media: A MediaEntry document
+ """
+ for listpath in media['media_files'].itervalues():
+ mg_globals.public_store.delete_file(
+ listpath)
+
+ for attachment in media['attachment_files']:
+ mg_globals.public_store.delete_file(
+ attachment['filepath'])