Merge remote-tracking branch 'remotes/nyergler/pep8-ification'
authorChristopher Allan Webber <cwebber@dustycloud.org>
Mon, 14 Nov 2011 02:23:26 +0000 (20:23 -0600)
committerChristopher Allan Webber <cwebber@dustycloud.org>
Mon, 14 Nov 2011 02:23:26 +0000 (20:23 -0600)
Conflicts:
mediagoblin/db/migrations.py
mediagoblin/db/models.py
mediagoblin/user_pages/views.py
mediagoblin/util.py

25 files changed:
1  2 
mediagoblin/app.py
mediagoblin/auth/forms.py
mediagoblin/auth/lib.py
mediagoblin/auth/views.py
mediagoblin/db/models.py
mediagoblin/decorators.py
mediagoblin/edit/views.py
mediagoblin/gmg_commands/__init__.py
mediagoblin/gmg_commands/import_export.py
mediagoblin/gmg_commands/migrate.py
mediagoblin/gmg_commands/users.py
mediagoblin/listings/views.py
mediagoblin/process_media/__init__.py
mediagoblin/process_media/errors.py
mediagoblin/submit/views.py
mediagoblin/tools/common.py
mediagoblin/tools/mail.py
mediagoblin/tools/pagination.py
mediagoblin/tools/response.py
mediagoblin/tools/template.py
mediagoblin/tools/text.py
mediagoblin/tools/url.py
mediagoblin/user_pages/views.py
mediagoblin/views.py
setup.py

index d39469c3981df4fd0d41b13fe0e7bf2e443cf24d,9bbccf24f846f88ceeecd902888625dbfad1fa41..ce4b0becfd8baa826dddc594289016ffcf97a5bc
@@@ -101,10 -98,9 +101,9 @@@ class MediaGoblinApp(object)
          setup_workbench()
  
          # instantiate application middleware
 -        self.middleware = [util.import_component(m)(self)
 +        self.middleware = [common.import_component(m)(self)
                             for m in middleware.ENABLED_MIDDLEWARE]
  
      def __call__(self, environ, start_response):
          request = Request(environ)
  
Simple merge
Simple merge
Simple merge
index 0f5174cc71a3632c493c546af76fb84daa7a715c,42db3f838d48f6a4b2b622c785094f1428f1ca59..c010cb8960d5000755fbcf1b9e8c8998f5615e5f
@@@ -218,7 -221,8 +219,8 @@@ class MediaEntry(Document)
          return self.db.MediaComment.find({
                  'media_entry': self['_id']}).sort('created', DESCENDING)
  
-     def get_display_media(self, media_map, fetch_order=common.DISPLAY_IMAGE_FETCHING_ORDER):
+     def get_display_media(self, media_map,
 -                          fetch_order=DISPLAY_IMAGE_FETCHING_ORDER):
++                          fetch_order=common.DISPLAY_IMAGE_FETCHING_ORDER):
          """
          Find the best media for display.
  
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 8003ffafa34fd613a3eb4dea126a242b37a9bf35,cb236154ab0c1ab5a562c5e75a2644e6eab8da92..4224a3e181ed570d80fa72c993bdc80509ed5684
@@@ -14,8 -14,9 +14,9 @@@
  # You should have received a copy of the GNU Affero General Public License
  # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  
 -from mediagoblin.util import lazy_pass_to_ugettext as _
 +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
  
  class BaseProcessingFail(Exception):
      """
      Base exception that all other processing failure messages should
Simple merge
index ea4541a8e18ec4d9be35829ea2bdb848eff3c350,0000000000000000000000000000000000000000..12d8309e9fe0d1e32b27f0accbbe36359200c68f
mode 100644,000000..100644
--- /dev/null
@@@ -1,37 -1,0 +1,38 @@@
 +# GNU MediaGoblin -- federated, autonomous media hosting
 +# Copyright (C) 2011 Free Software Foundation, Inc
 +#
 +# 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
 +# the Free Software Foundation, either version 3 of the License, or
 +# (at your option) any later version.
 +#
 +# This program is distributed in the hope that it will be useful,
 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +# GNU Affero General Public License for more details.
 +#
 +# You should have received a copy of the GNU Affero General Public License
 +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 +
 +import sys
 +
 +DISPLAY_IMAGE_FETCHING_ORDER = [u'medium', u'original', u'thumb']
 +
 +global TESTS_ENABLED
 +TESTS_ENABLED = False
 +
++
 +def import_component(import_string):
 +    """
 +    Import a module component defined by STRING.  Probably a method,
 +    class, or global variable.
 +
 +    Args:
 +     - import_string: a string that defines what to import.  Written
 +       in the format of "module1.module2:component"
 +    """
 +    module_name, func_name = import_string.split(':', 1)
 +    __import__(module_name)
 +    module = sys.modules[module_name]
 +    func = getattr(module, func_name)
 +    return func
index 826acdbff780fdd3c9d2e80d9d3762e0a3d2818f,0000000000000000000000000000000000000000..9e00be7ded73d647ae8015bcbc49a1f9726478d6
mode 100644,000000..100644
--- /dev/null
@@@ -1,120 -1,0 +1,123 @@@
- # 
 +# GNU MediaGoblin -- federated, autonomous media hosting
 +# 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
 +# the Free Software Foundation, either version 3 of the License, or
 +# (at your option) any later version.
 +#
 +# This program is distributed in the hope that it will be useful,
 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +# GNU Affero General Public License for more details.
 +#
 +# You should have received a copy of the GNU Affero General Public License
 +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 +
 +import smtplib
 +from email.MIMEText import MIMEText
 +from mediagoblin import mg_globals
 +from mediagoblin.tools import common
 +
 +### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 +### Special email test stuff begins HERE
 +### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 +
 +# We have two "test inboxes" here:
- # always call _clear_test_inboxes() to "wipe" the inboxes clean. 
++#
 +# EMAIL_TEST_INBOX:
 +# ----------------
 +#   If you're writing test views, you'll probably want to check this.
 +#   It contains a list of MIMEText messages.
 +#
 +# EMAIL_TEST_MBOX_INBOX:
 +# ----------------------
 +#   This collects the messages from the FakeMhost inbox.  It's reslly
 +#   just here for testing the send_email method itself.
 +#
 +#   Anyway this contains:
 +#    - from
 +#    - to: a list of email recipient addresses
 +#    - message: not just the body, but the whole message, including
 +#      headers, etc.
 +#
 +# ***IMPORTANT!***
 +# ----------------
 +# Before running tests that call functions which send email, you should
++# always call _clear_test_inboxes() to "wipe" the inboxes clean.
 +
 +EMAIL_TEST_INBOX = []
 +EMAIL_TEST_MBOX_INBOX = []
 +
++
 +class FakeMhost(object):
 +    """
 +    Just a fake mail host so we can capture and test messages
 +    from send_email
 +    """
 +    def login(self, *args, **kwargs):
 +        pass
 +
 +    def sendmail(self, from_addr, to_addrs, message):
 +        EMAIL_TEST_MBOX_INBOX.append(
 +            {'from': from_addr,
 +             'to': to_addrs,
 +             'message': message})
 +
++
 +def _clear_test_inboxes():
 +    global EMAIL_TEST_INBOX
 +    global EMAIL_TEST_MBOX_INBOX
 +    EMAIL_TEST_INBOX = []
 +    EMAIL_TEST_MBOX_INBOX = []
 +
++
 +### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 +### </Special email test stuff>
 +### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 +
 +def send_email(from_addr, to_addrs, subject, message_body):
 +    """
 +    Simple email sending wrapper, use this so we can capture messages
 +    for unit testing purposes.
 +
 +    Args:
 +     - from_addr: address you're sending the email from
 +     - to_addrs: list of recipient email addresses
 +     - subject: subject of the email
 +     - message_body: email body text
 +    """
 +    if common.TESTS_ENABLED or mg_globals.app_config['email_debug_mode']:
 +        mhost = FakeMhost()
 +    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'])
 +
 +        # 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 common.TESTS_ENABLED:
 +        EMAIL_TEST_INBOX.append(message)
 +
 +    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']
 +        print u"Subject: %s" % message['Subject']
 +        print u"-- Body: --"
 +        print message.get_payload(decode=True)
 +
 +    return mhost.sendmail(from_addr, to_addrs, message.as_string())
index 3ea96e6d5a05c5da1dd318be812a0401750b73f8,0000000000000000000000000000000000000000..bc20ec909fd1df4ffca195fb4ce4d6acf316ae54
mode 100644,000000..100644
--- /dev/null
@@@ -1,109 -1,0 +1,111 @@@
-          - cursor: db cursor 
-          - jump_to_id: ObjectId, sets the page to the page containing the object
-            with _id == jump_to_id.
 +# GNU MediaGoblin -- federated, autonomous media hosting
 +# 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
 +# the Free Software Foundation, either version 3 of the License, or
 +# (at your option) any later version.
 +#
 +# This program is distributed in the hope that it will be useful,
 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +# GNU Affero General Public License for more details.
 +#
 +# You should have received a copy of the GNU Affero General Public License
 +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 +
 +import urllib
 +import copy
 +from math import ceil, floor
 +from itertools import izip, count
 +
++
 +PAGINATION_DEFAULT_PER_PAGE = 30
 +
++
 +class Pagination(object):
 +    """
 +    Pagination class for mongodb queries.
 +
 +    Initialization through __init__(self, cursor, page=1, per_page=2),
 +    get actual data slice through __call__().
 +    """
 +
 +    def __init__(self, page, cursor, per_page=PAGINATION_DEFAULT_PER_PAGE,
 +                 jump_to_id=False):
 +        """
 +        Initializes Pagination
 +
 +        Args:
 +         - page: requested page
 +         - per_page: number of objects per page
-         """ 
++         - cursor: db cursor
++         - jump_to_id: ObjectId, sets the page to the page containing the
++           object with _id == jump_to_id.
 +        """
 +        self.page = page
 +        self.per_page = per_page
 +        self.cursor = cursor
 +        self.total_count = self.cursor.count()
 +        self.active_id = None
 +
 +        if jump_to_id:
 +            cursor = copy.copy(self.cursor)
 +
 +            for (doc, increment) in izip(cursor, count(0)):
 +                if doc['_id'] == jump_to_id:
 +                    self.page = 1 + int(floor(increment / self.per_page))
 +
 +                    self.active_id = jump_to_id
 +                    break
 +
 +
 +    def __call__(self):
 +        """
 +        Returns slice of objects for the requested page
 +        """
 +        return self.cursor.skip(
 +            (self.page - 1) * self.per_page).limit(self.per_page)
 +
 +    @property
 +    def pages(self):
 +        return int(ceil(self.total_count / float(self.per_page)))
 +
 +    @property
 +    def has_prev(self):
 +        return self.page > 1
 +
 +    @property
 +    def has_next(self):
 +        return self.page < self.pages
 +
 +    def iter_pages(self, left_edge=2, left_current=2,
 +                   right_current=5, right_edge=2):
 +        last = 0
 +        for num in xrange(1, self.pages + 1):
 +            if num <= left_edge or \
 +               (num > self.page - left_current - 1 and \
 +                num < self.page + right_current) or \
 +               num > self.pages - right_edge:
 +                if last + 1 != num:
 +                    yield None
 +                yield num
 +                last = num
 +
 +    def get_page_url_explicit(self, base_url, get_params, page_no):
-         """ 
++        """
 +        Get a page url by adding a page= parameter to the base url
-         """ 
++        """
 +        new_get_params = copy.copy(get_params or {})
 +        new_get_params['page'] = page_no
 +        return "%s?%s" % (
 +            base_url, urllib.urlencode(new_get_params))
 +
 +    def get_page_url(self, request, page_no):
-         """ 
++        """
 +        Get a new page url based of the request, and the new page number.
 +
 +        This is a nice wrapper around get_page_url_explicit()
++        """
 +        return self.get_page_url_explicit(
 +            request.full_path, request.GET, page_no)
index 1477b9bc0a8c6011d8c6630f9cdf206e6313e455,0000000000000000000000000000000000000000..b01d31a202588338a3d84c05f6d64bb455a9af5a
mode 100644,000000..100644
--- /dev/null
@@@ -1,44 -1,0 +1,47 @@@
-     
 +# GNU MediaGoblin -- federated, autonomous media hosting
 +# 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
 +# the Free Software Foundation, either version 3 of the License, or
 +# (at your option) any later version.
 +#
 +# This program is distributed in the hope that it will be useful,
 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +# GNU Affero General Public License for more details.
 +#
 +# You should have received a copy of the GNU Affero General Public License
 +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 +
 +from webob import Response, exc
 +from mediagoblin.tools.template import render_template
 +
++
 +def render_to_response(request, template, context, status=200):
 +    """Much like Django's shortcut.render()"""
 +    return Response(
 +        render_template(request, template, context),
 +        status=status)
 +
++
 +def render_404(request):
 +    """
 +    Render a 404.
 +    """
 +    return render_to_response(
 +        request, 'mediagoblin/404.html', {}, status=400)
 +
++
 +def redirect(request, *args, **kwargs):
 +    """Returns a HTTPFound(), takes a request and then urlgen params"""
++
 +    querystring = None
 +    if kwargs.get('querystring'):
 +        querystring = kwargs.get('querystring')
 +        del kwargs['querystring']
 +
 +    return exc.HTTPFound(
 +        location=''.join([
 +                request.urlgen(*args, **kwargs),
 +                querystring if querystring else '']))
index a773ca99f24932a8af082e474a3026139729858e,0000000000000000000000000000000000000000..905a36df8fb920048f239fd0c299cca98805fde2
mode 100644,000000..100644
--- /dev/null
@@@ -1,116 -1,0 +1,119 @@@
- from babel.support import LazyProxy
 +# GNU MediaGoblin -- federated, autonomous media hosting
 +# 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
 +# the Free Software Foundation, either version 3 of the License, or
 +# (at your option) any later version.
 +#
 +# This program is distributed in the hope that it will be useful,
 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +# GNU Affero General Public License for more details.
 +#
 +# You should have received a copy of the GNU Affero General Public License
 +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 +
 +from math import ceil
 +import jinja2
 +from babel.localedata import exists
-     Set up the Jinja environment, 
 +from mediagoblin import mg_globals
 +from mediagoblin import messages
 +from mediagoblin.tools import common
 +from mediagoblin.tools.translate import setup_gettext
 +from mediagoblin.middleware.csrf import render_csrf_form_token
 +
++
 +SETUP_JINJA_ENVS = {}
 +
++
 +def get_jinja_env(template_loader, locale):
 +    """
-     
-     if common.TESTS_ENABLED:        
++    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)
 +
 +    # 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.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
 +    # ... 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
 +
 +    return template_env
 +
++
 +# 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.
 +
 +    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
 +    context['csrf_token'] = render_csrf_form_token(request)
 +    rendered = template.render(context)
++
++    if common.TESTS_ENABLED:
 +        TEMPLATE_TEST_CONTEXT[template_path] = context
 +
 +    return rendered
 +
 +
 +def clear_test_template_context():
 +    global TEMPLATE_TEST_CONTEXT
 +    TEMPLATE_TEST_CONTEXT = {}
 +
++
 +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)
index de4bb28190487e95c302732fa80c50f9cfa2555a,0000000000000000000000000000000000000000..be1adb00e728af16a9b6c07c134e414c89fd2d85
mode 100644,000000..100644
--- /dev/null
@@@ -1,117 -1,0 +1,124 @@@
 +# GNU MediaGoblin -- federated, autonomous media hosting
 +# 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
 +# the Free Software Foundation, either version 3 of the License, or
 +# (at your option) any later version.
 +#
 +# This program is distributed in the hope that it will be useful,
 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +# GNU Affero General Public License for more details.
 +#
 +# You should have received a copy of the GNU Affero General Public License
 +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 +
 +import wtforms
 +import markdown
 +from lxml.html.clean import Cleaner
 +
 +from mediagoblin import mg_globals
 +from mediagoblin.tools import url
 +
++
 +# A super strict version of the lxml.html cleaner class
 +HTML_CLEANER = Cleaner(
 +    scripts=True,
 +    javascript=True,
 +    comments=True,
 +    style=True,
 +    links=True,
 +    page_structure=True,
 +    processing_instructions=True,
 +    embedded=True,
 +    frames=True,
 +    forms=True,
 +    annoying_tags=True,
 +    allow_tags=[
 +        'div', 'b', 'i', 'em', 'strong', 'p', 'ul', 'ol', 'li', 'a', 'br'],
 +    remove_unknown_tags=False, # can't be used with allow_tags
 +    safe_attrs_only=True,
 +    add_nofollow=True, # for now
 +    host_whitelist=(),
 +    whitelist_tags=set([]))
 +
++
 +def clean_html(html):
 +    # clean_html barfs on an empty string
 +    if not html:
 +        return u''
 +
 +    return HTML_CLEANER.clean_html(html)
 +
++
 +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):
 +    """
 +    Take a block of text, run it through MarkDown, and clean its HTML.
 +    """
 +    # Markdown will do nothing with and clean_html can do nothing with
 +    # an empty string :)
 +    if not text:
 +        return u''
 +
 +    return clean_html(MARKDOWN_INSTANCE.convert(text))
index 458ef2c86f6aa9c138a3ada10928b05efa6e7e2a,0000000000000000000000000000000000000000..78b5dd63b2262da4ef8f04a55aa4f1d88fd1b756
mode 100644,000000..100644
--- /dev/null
@@@ -1,31 -1,0 +1,33 @@@
 +# GNU MediaGoblin -- federated, autonomous media hosting
 +# Copyright (C) 2011 Free Software Foundation, Inc
 +#
 +# 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
 +# the Free Software Foundation, either version 3 of the License, or
 +# (at your option) any later version.
 +#
 +# This program is distributed in the hope that it will be useful,
 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +# GNU Affero General Public License for more details.
 +#
 +# You should have received a copy of the GNU Affero General Public License
 +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 +
 +import re
 +import translitcodec
 +
++
 +_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))
Simple merge
Simple merge
diff --cc setup.py
Simple merge