Nathan Van Gheem avatar Nathan Van Gheem committed 40c8b8d Merge

merged with zine-main

Comments (0)

Files changed (30)

external-plugins/dark_vessel_colorscheme/metadata.txt

 Author: Armin Ronacher <armin.ronacher@active-4.com>
 Author URL: http://lucumr.pocoo.org/
 License: BSD
-Version: %ZINE_VERSION%
+Version: 0.1
 Description: A dark colorscheme for the vessel theme
 Description[de]: Ein dunkles Farbschema für das Vessel-Design
 Depends: vessel_theme

external-plugins/markdown_parser/metadata.txt

 Author: Armin Ronacher <armin.ronacher@active-4.com>
 Author URL: http://lucumr.pocoo.org/
 License: BSD
-Version: 0.2
+Version: 0.1
 Contributors: Kiran Jonnalagadda <jace@pobox.com>
 Description: This plugin allows you to use Markdown for your posts.
 Description[de]: Dieses Plugin erlaubt dir Markdown für die Post-\
Add a comment to this file

scripts/_install-posix.py

File contents unchanged.

scripts/bundle-plugin

     if not options.dont_compile:
         from subprocess import call
         compile_script = join(dirname(__file__), 'compile-translations')
-        call([compile_script, plugin.path, '-s'])
+        call([sys.executable, compile_script, plugin.path, '-s'])
 
     plugin.dump(output_filename)
     print 'Plugin written to %s' % output_filename

zine/application.py

 from urlparse import urlparse, urljoin
 from collections import deque
 from inspect import getdoc
+from traceback import format_exception
+from pprint import pprint
+from StringIO import StringIO
 
 from babel import Locale
 
 from jinja2 import Environment, BaseLoader, TemplateNotFound
 
+from sqlalchemy.exceptions import SQLAlchemyError
+
 from werkzeug import Request as RequestBase, Response as ResponseBase, \
      SharedDataMiddleware, url_quote, routing, redirect as _redirect, \
      escape, cached_property, url_encode
         # the notification manager
         from zine.notifications import NotificationManager, \
              DEFAULT_NOTIFICATION_SYSTEMS, DEFAULT_NOTIFICATION_TYPES
+
         self.notification_manager = NotificationManager()
         for system in DEFAULT_NOTIFICATION_SYSTEMS:
             self.add_notification_system(system)
         response.status_code = 404
         return response
 
+    def send_error_notification(self, request, error):
+        from zine.notifications import send_notification_template, ZINE_ERROR
+        request_buffer = StringIO()
+        pprint(request.__dict__, request_buffer)
+        request_buffer.seek(0)
+        send_notification_template(
+            ZINE_ERROR, 'notifications/on_server_error.zeml',
+            user=request.user, summary=error.message,
+            request_details=request_buffer.read(),
+            longtext=''.join(format_exception(*sys.exc_info()))
+        )
+
     def handle_server_error(self, request, exc_info=None, suppress_log=False):
         """Called if a server error happens.  Logs the error and returns a
         response with an error message.
         response.status_code = 500
         return response
 
-    def handle_internal_error(self, request, error):
+    def handle_internal_error(self, request, error, suppress_log=True):
         """Called if internal errors are caught."""
         if request.user.is_admin:
             response = render_response('internal_error.html', error=error)
             response.status_code = 500
             return response
-        return self.handle_server_error(request, suppress_log=True)
+        # We got here, meaning no admin has seen this error yet. Notify Them!
+        self.send_error_notification(request, error)
+        return self.handle_server_error(request, suppress_log=suppress_log)
 
     def dispatch_request(self, request):
         #! the after-request-setup event can return a response
                                                  next=request.path))
         except HTTPException, e:
             response = e.get_response(request)
+        except SQLAlchemyError, e:
+            # Some database screwup?! Don't let Zine stay dispatching 500's
+            db.session.rollback()
+            response = self.handle_internal_error(request, e,
+                                                  suppress_log=False)
 
         # in debug mode on HTML responses we inject the collected queries.
         if self.cfg['database_debug'] and \
     'blog_email':               TextField(default=u'', help_text=lazy_gettext(
         u'The email address given here is used by the notification system to send '
         u'mails from.  Also plugins that send mails will use this address as '
-        u'sender address.'), validators=[is_valid_email]),
+        u'sender address.'), validators=[is_valid_email()]),
     'timezone':                 ChoiceField(choices=sorted(list_timezones()),
         default=u'UTC', help_text=lazy_gettext(
         u'The timezone of the blog.  All times and dates in the user interface '
         (2, lazy_gettext(u'Automatically approve comments by known comment authors'))
                                             ], default=1),
     'pings_enabled':            BooleanField(default=True),
+    'plaintext_parser_nolinks': BooleanField(default=False, help_text=lazy_gettext(
+        u'If set to true, the plaintext parser will not create links automatically.')),
 
     # post view
     'posts_per_page':           IntegerField(default=10, help_text=lazy_gettext(
             else:
                 notification_type = NEW_COMMENT
             send_notification_template(notification_type,
-                'notifications/on_new_comment.zeml', comment=comment)
+                'notifications/on_new_comment.zeml',
+                user=req.user, comment=comment)
 
         # Still allow the user to see his comment if it's blocked
         if comment.blocked:
              (p.status == STATUS_PUBLISHED) &
              (p.pub_date <= datetime.utcnow()))
 
-        s = db.select([t.tag_id, t.slug, t.name,
-                       db.func.count(p.post_id).label('s_count')],
-                      q, group_by=[t.slug, t.name]).alias('post_count_query').c
+        s = db.select(
+            [t.tag_id, t.slug, t.name,
+             db.func.count(p.post_id).label('s_count')],
+            q, group_by=[t.slug, t.name, t.tag_id]).alias('post_count_query').c
 
         options = {'order_by': [db.asc(s.s_count)]}
         if max is not None:

zine/notifications.py

 DEFAULT_NOTIFICATION_TYPES = {}
 
 
-def send_notification(type, message):
+def send_notification(type, message, user=Ellipsis):
     """Convenience function.  Get the application object and deliver the
     notification to it's NotificationManager.
 
         New comment on "Foo bar baz."  Mr. Miracle wrote anew comment:
         "This is awesome".  http://.../link
     """
-    get_application().notification_manager.send(Notification(type, message))
+    get_application().notification_manager.send(
+        Notification(type, message, user)
+    )
 
 
-def send_notification_template(type, template_name, **context):
+def send_notification_template(type, template_name, user=Ellipsis, **context):
     """Like `send_notification` but renders a template instead."""
     notification = render_template(template_name, **context)
-    send_notification(type, notification)
+    send_notification(type, notification, user)
 
 
 class NotificationType(object):
     The message is a zeml construct.
     """
 
-    def __init__(self, id, message):
+    def __init__(self, id, message, user=Ellipsis):
         self.message = parse_zeml(message, 'system')
         self.id = id
         self.sent_date = datetime.utcnow()
+        if user is Ellipsis:
+            self.user = get_request().user
+        else:
+            self.user = user
 
     @property
     def self_link(self):
         # given the type of the notification, check what users want that
         # notification; via what system and call the according
         # notification system in order to finally deliver the message
-        subscriptions = NotificationSubscription.query \
-            .filter_by(notification_id=notification.id.name).all()
+        subscriptions = NotificationSubscription.query.filter_by(
+            notification_id=notification.id.name
+        )
+        if notification.user:
+            subscriptions = subscriptions.filter(
+                NotificationSubscription.user!=notification.user
+            )
 
-        for subscription in subscriptions:
+        for subscription in subscriptions.all():
             system = self.systems.get(subscription.notification_system)
             if system is not None:
                 system.send(subscription.user, notification)
 _register('SECURITY_ALERT',
           lazy_gettext(u'When Zine found an urgent security alarm.'),
           BLOG_ADMIN)
+_register('ZINE_ERROR', lazy_gettext(u'When Zine throws errors.'), BLOG_ADMIN)
 
 
 DEFAULT_NOTIFICATION_SYSTEMS = [EMailNotificationSystem]
         """Convert a token to normal text."""
         return replace_entities(unicode(token))
 
-    def _to_zeml(self, node):
+    def _to_zeml(self, node, untrusted=False):
         """Convert a potty-mouth node into a ZEML tree."""
         from zine._ext.pottymouth import Token
         def add_text(node, text):
                result.children[0].name == 'blockquote':
                 result = result.children[0]
 
+            # untrusted posts get nofollow on links
+            if untrusted and result.name == 'a':
+                result.attributes['rel'] = 'nofollow'
+
             return result
         return convert(node, True)
 
     def parse(self, input_data, reason):
         from zine._ext.pottymouth import PottyMouth
         parser = PottyMouth(emdash=False, ellipsis=False, smart_quotes=False,
-                            youtube=False, image=False, italic=False)
+                            youtube=False, image=False, italic=False,
+                            all_links=not self.app.cfg['plaintext_parser_nolinks'])
         node = parser.parse(input_data)
-        return self._to_zeml(node)
+        return self._to_zeml(node, reason == 'comment')
 
 
 all_parsers = {

zine/plugins/akismet_spam_filter/metadata.txt

 Author: Armin Ronacher <armin.ronacher@active-4.com>
 Author URL: http://lucumr.pocoo.org/
 License: BSD
-Version: %ZINE_VERSION%
+Version: 0.2
 Contributors: Pedro Algarvio <ufs@ufsoft.org>
 Description: <a href="http://akismet.com/">Akismet</a> checks your comments\
  against the Akismet web service to see if they look like spam or not. It\

zine/plugins/blogger_feedimport/metadata.txt

 Author: Georg Brandl <georg@python.org>
 Author URL: http://dev.pocoo.org/
 License: BSD
-Version: %ZINE_VERSION%
+Version: 0.1
 Description: This plugin allows you to import the Atom feed export format from\
  blogger.com.
 Description[de]: Dieses Plugin dient dem Import von Atom-Feeds, die von\

zine/plugins/creole_parser/metadata.txt

 Author: Armin Ronacher <armin.ronacher@active-4.com>
 Author URL: http://lucumr.pocoo.org/
 License: BSD
-Version: %ZINE_VERSION%
+Version: 0.2
 Description: This plugin allows you to use the Creole Parser for your posts.
 Description[de]: Dieses Plugin erlaubt es dir deine Posts mit Creole Wikisyntax\
  zu formattieren.

zine/plugins/legacy_apis/metadata.txt

 Plugin URL: http://zine.pocoo.org/plugins/legacy_apis
 Author: Armin Ronacher <armin.ronacher@active-4.com>
 License: BSD
-Version: %ZINE_VERSION%
+Version: 0.2
 Description: Adds support for various legacy weblog APIs such as\
  MetaWeblog, WordPress or MovableType.
 Description[de]: Fügt Unterstützung für existierende Weblog APIs\

zine/plugins/miniblog_theme/metadata.txt

 Author: Armin Ronacher <armin.ronacher@active-4.com>
 Author URL: http://lucumr.pocoo.org/
 License: BSD
-Version: %ZINE_VERSION%
+Version: 0.1
 Description: A very simple theme for tumblelogs or whatever inspired by\
  the flickr design.
 Description[de]: Ein einfaches Design für thublelogs und ähnliches,\

zine/plugins/myrtle_theme/metadata.txt

 Author: Armin Ronacher <armin.ronacher@active-4.com>
 Author URL: http://lucumr.pocoo.org/
 License: BSD
-Version: %ZINE_VERSION%
+Version: 0.1
 Description: A brownish theme called "myrtle".
 Description[de]: Ein braunes Design namens "myrtle".
 Preview: myrtle_theme::preview.png

zine/plugins/pygments_support/metadata.txt

 Author: Armin Ronacher <armin.ronacher@active-4.com>
 Author URL: http://lucumr.pocoo.org/
 License: BSD
-Version: %ZINE_VERSION%
+Version: 0.2
 Description: Adds support for <a href="http://pygments.org/">pygments</a>\
  highlighted code blocks. Blocks marked with <code>&lt;sourcecode\
  syntax="language"&gt;</code> are highlighted using the Pygments\

zine/plugins/rst_parser/metadata.txt

 Plugin URL: http://zine.pocoo.org/plugins/rst_parser
 Author: Georg Brandl <georg@python.org>
 License: BSD
-Version: %ZINE_VERSION%
+Version: 0.2
 Description: Adds support for writing posts in \
  <a href="http://docutils.sf.net/rst.html">reStructuredText</a> markup.
 Description[de]: Fügt Unterstützung für das Schreiben von Blogposts in \

zine/plugins/typography/metadata.txt

 Author: Armin Ronacher <armin.ronacher@active-4.com>
 Author URL: http://lucumr.pocoo.org/
 License: BSD
-Version: %ZINE_VERSION%
+Version: 0.1
 Description: This plugin replaces some characters with their typographic\
  better equivalents (quotes etc.)
 Description[de]: Dieses Plugin erlaubt es dir, einige Zeichen automatisch\

zine/plugins/vessel_theme/metadata.txt

 Author: Armin Ronacher <armin.ronacher@active-4.com>
 Author URL: http://lucumr.pocoo.org/
 License: BSD
-Version: %ZINE_VERSION%
+Version: 0.1
 Description: A simple default theme
 Description[de]: Ein einfaches Standarddesign
 Preview: vessel_theme::preview.png

zine/pluginsystem.py

 from zine.utils.exceptions import UserException, summarize_exception
 from zine.i18n import ZineTranslations as Translations, lazy_gettext, _
 from zine.environment import BUILTIN_PLUGIN_FOLDER
-from zine import __version__ as zine_version
 
 
 _py_import = __builtin__.__import__
     @property
     def version(self):
         """The version of the plugin."""
-        rv = self.metadata.get('version')
-        if rv == '%ZINE_VERSION%':
-            return zine_version
-        return rv
+        return self.metadata.get('version')
 
     @property
     def depends(self):

zine/privileges.py

         container.remove(current_map[name])
         # remove any privilege dependencies that are not attached to other
         # privileges
-        for privilege in current_map[name].dependencies.iter_privileges():
-            container.remove(privilege)
+        if current_map[name].dependencies:
+            for privilege in current_map[name].dependencies.iter_privileges():
+                try:
+                    container.remove(privilege)
+                except KeyError:
+                    # privilege probably already removed
+                    pass
 
         # remove notification subscriptions that required the privilege
         # being deleted.
         privilege = app.privileges[name]
         container.add(privilege)
         # add dependable privileges
-        for privilege in privilege.dependencies.iter_privileges():
-            container.add(privilege)
+        if privilege.dependencies:
+            for privilege in privilege.dependencies.iter_privileges():
+                container.add(privilege)
 
 
 def require_privilege(expr):
                                 creator=creator_func)
 
 
-def _register(name, description, privilege_dependencies=()):
+def _register(name, description, privilege_dependencies=None):
     """Register a new builtin privilege."""
     priv = Privilege(name, description, privilege_dependencies)
     DEFAULT_PRIVILEGES[name] = priv

zine/shared/admin/preview.css

+/* :::: GENERIC RESET :::: */
+div.preview div.text {
+    margin: 0;
+    padding: 0;
+    border: 0;
+    outline: 0;
+    background: transparent;
+    color: black;
+    font-family: 'Trebuchet MS', sans-serif;
+    font-style: normal;
+    font-size: 14px;
+    vertical-align: baseline;
+    text-decoration: none;
+    line-height: normal;
+    max-width: 700px;
+}
+
+/* :::: BLOCK ELEMENTS :::: */
+div.preview div.text map, div.preview div.text dt, div.preview div.text form {
+    display: block;
+}
+
+div.preview div.text {
+    margin: 8px;
+    display: block;
+}
+
+div.preview div.text p, div.preview div.text dl, div.preview div.text multicol {
+    display: block;
+    margin: 1em 0;
+}
+
+div.preview div.text dd {
+    display: block;
+    margin-left: 40px;
+}
+
+div.preview div.text blockquote {
+    display: block;
+    margin: 1em 40px;
+}
+
+div.preview div.text address {
+    display: block;
+    font-style: italic;
+}
+
+div.preview div.text center {
+    display: block;
+    text-align: center;
+}
+
+div.preview div.text h1, div.preview div.text h2, div.preview div.text h3,
+div.preview div.text h4, div.preview div.text h5, div.preview div.text h6 {
+    font-family: 'Trebuchet MS', sans-serif;
+}
+
+div.preview div.text h1 {
+    display: block;
+    font-size: 2em;
+    font-weight: bold;
+    margin: .67em 0;
+    color: red;
+    border-bottom: none; /* thou shalt not use h1 headers */
+}
+
+div.preview div.text h2 {
+    display: block;
+    font-size: 1.5em;
+    font-weight: bold;
+    margin: .83em 0;
+}
+
+div.preview div.text h3 {
+    display: block;
+    font-size: 1.17em;
+    font-weight: bold;
+    margin: 1em 0;
+}
+
+div.preview div.text h4 {
+    display: block;
+    font-weight: bold;
+    margin: 1.33em 0;
+}
+
+div.preview div.text h5 {
+    display: block;
+    font-size: 0.83em;
+    font-weight: bold;
+    margin: 1.67em 0;
+}
+
+div.preview div.text h6 {
+    display: block;
+    font-size: 0.67em;
+    font-weight: bold;
+    margin: 2.33em 0;
+}
+
+div.preview div.text pre {
+    display: block;
+    font-family: 'Consolas', 'Bitstream Vera Sans Mono', 'Monaco', monospace;
+    border: 1px solid #adb6bc;
+    white-space: pre;
+    margin: 1em 0;
+    padding: 5px;
+    background: #cdd6db;
+}
+
+div.preview div.text tbody, div.preview div.text thead, div.preview div.text tfoot {
+  vertical-align: middle;
+}
+
+div.preview div.text table {
+    display: table;
+    border: 2px solid #86939a;
+    border-spacing: 0;
+    border-collapse: collapsed;
+}
+
+div.preview div.text td, div.preview div.text th {
+    background: transparent;
+    vertical-align: inherit;
+    border: 1px solid #adb6bc;
+    text-align: inherit; 
+    padding: 3px 5px;
+}
+
+div.preview div.text th {
+    font-weight: bold;
+}
+
+div.preview div.text thead th {
+    background: #cdd6db;
+}
+
+div.preview div.text q:before {
+    content: open-quote;
+}
+
+div.preview div.text q:after {
+    content: close-quote;
+}
+
+/* :::: SPECIAL STUFF :::: */
+div.preview div.text hr {
+    display: block;
+    height: 2px;
+    border: 1px inset;
+    margin: 0.5em auto 0.5em auto;
+    color: gray;
+    border-style: outset;
+}
+
+div.preview div.text a {
+    color: blue;
+    text-decoration: underline;
+}
+
+div.preview div.text a:visited {
+    color: purple;
+}
+
+/* :::: INLINE TEXT STYLES :::: */
+div.preview div.text b, div.preview div.text strong {
+    font-weight: bolder;
+}
+
+div.preview div.text i, div.preview div.text cite, div.preview div.text em,
+div.preview div.text var, div.preview div.text dfn {
+    font-style: italic;
+}
+
+div.preview div.text tt, div.preview div.text code,
+div.preview div.text kbd, div.preview div.text samp {
+    font-family: monospace;
+}
+
+div.preview div.text u, div.preview div.text ins {
+    text-decoration: underline;
+}
+
+div.preview div.text s, div.preview div.text strike, div.preview div.text del {
+    text-decoration: line-through;
+}
+
+div.preview div.text blink {
+    text-decoration: blink;
+}
+
+div.preview div.text big {
+    font-size: larger;
+}
+
+div.preview div.text small {
+    font-size: smaller;
+}
+
+div.preview div.text sub {
+    vertical-align: sub;
+    font-size: smaller;
+    line-height: normal;
+}
+
+div.preview div.text sup {
+    vertical-align: super;
+    font-size: smaller;
+    line-height: normal;
+}
+
+div.preview div.text nobr {
+    white-space: nowrap;
+}
+
+
+/* :::: LIST RENDERING :::: */
+div.preview div.text ul, div.preview div.text menu, div.preview div.text dir {
+    display: block;
+    list-style-type: disc;
+    margin: 1em 0;
+    padding-left: 40px;
+}
+
+div.preview div.text ol {
+    display: block;
+    list-style-type: decimal;
+    margin: 1em 0;
+    padding-left: 40px;
+}
+
+div.preview div.text li {
+    display: list-item;
+}
+
+/* nested lists have no top/bottom margins */
+div.preview div.text ul ul,   div.preview div.text ul ol,   div.preview div.text ul dir,
+div.preview div.text ul menu, div.preview div.text ul dl,   div.preview div.text ol ul,
+div.preview div.text ol ol,   div.preview div.text ol dir,  div.preview div.text ol menu,
+div.preview div.text ol dl,   div.preview div.text dir ul,  div.preview div.text dir ol,
+div.preview div.text dir dir, div.preview div.text dir menu,div.preview div.text dir dl,
+div.preview div.text menu ul, div.preview div.text menu ol, div.preview div.text menu dir,
+div.preview div.text menu menu,div.preview div.text menu dl,div.preview div.text dl ul,
+div.preview div.text dl ol,   div.preview div.text dl dir,  div.preview div.text dl menu,
+div.preview div.text dl dl {
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+/* 2 deep unordered lists use a circle */
+div.preview div.text ol ul,   div.preview div.text ul ul,   div.preview div.text menu ul,   div.preview div.text dir ul,
+div.preview div.text ol menu, div.preview div.text ul menu, div.preview div.text menu menu, div.preview div.text dir menu,
+div.preview div.text ol dir,  div.preview div.text ul dir,  div.preview div.text menu dir,  div.preview div.text dir dir {
+    list-style-type: circle;
+}
+
+/* 3 deep (or more) unordered lists use a square */
+div.preview div.text ol ol ul,     div.preview div.text ol ul ul,     div.preview div.text ol menu ul,     div.preview div.text ol dir ul,
+div.preview div.text ol ol menu,   div.preview div.text ol ul menu,   div.preview div.text ol menu menu,   div.preview div.text ol dir menu,
+div.preview div.text ol ol dir,    div.preview div.text ol ul dir,    div.preview div.text ol menu dir,    div.preview div.text ol dir dir,
+div.preview div.text ul ol ul,     div.preview div.text ul ul ul,     div.preview div.text ul menu ul,     div.preview div.text ul dir ul,
+div.preview div.text ul ol menu,   div.preview div.text ul ul menu,   div.preview div.text ul menu menu,   div.preview div.text ul dir menu,
+div.preview div.text ul ol dir,    div.preview div.text ul ul dir,    div.preview div.text ul menu dir,    div.preview div.text ul dir dir,
+div.preview div.text menu ol ul,   div.preview div.text menu ul ul,   div.preview div.text menu menu ul,   div.preview div.text menu dir ul,
+div.preview div.text menu ol menu, div.preview div.text menu ul menu, div.preview div.text menu menu menu, div.preview div.text menu dir menu,
+div.preview div.text menu ol dir,  div.preview div.text menu ul dir,  div.preview div.text menu menu dir,  div.preview div.text menu dir dir,
+div.preview div.text dir ol ul,    div.preview div.text dir ul ul,    div.preview div.text dir menu ul,    div.preview div.text dir dir ul,
+div.preview div.text dir ol menu,  div.preview div.text dir ul menu,  div.preview div.text dir menu menu,  div.preview div.text dir dir menu,
+div.preview div.text dir ol dir,   div.preview div.text dir ul dir,   div.preview div.text dir menu dir,   div.preview div.text dir dir dir {
+    list-style-type: square;
+}

zine/shared/admin/style.css

 
 table.plugins td.plugin {
     font-weight: bold;
+    min-width: 200px;
 }
 
 table.plugins td.version {

zine/shared/js/Admin.js

     if (input_box.length == 0)
       return;
 
+    // windows browser put the invisible space directly into the
+    // window manager title which causes a question mark to show
+    // up.  Because of that we fire the change() event right away
+    // to force an updated title without the invisble space.
     var title = document.title.split(/\u200B/, 2)[1];
     input_box.bind('change', function() {
       var arg = input_box.val();
       document.title = (arg ? arg + ' — ' : '') + title;
-    });
+    }).change();
   })();
 
   // Make some textareas resizable

zine/templates/admin/layout.html

 <head>
   {%- block html_head %}
   <title>{% block title %}{% endblock %} &mdash; {{ cfg.blog_title|e }} {{ _("Administration") }}</title>
+  <link rel="stylesheet" type="text/css" href="{{ url_for('core/shared', filename='admin/preview.css') }}">
   <link rel="stylesheet" type="text/css" href="{{ url_for('core/shared', filename='admin/style.css') }}">
   {{ get_page_metadata() }}
   <script type="text/javascript" src="{{ url_for('core/shared', filename='js/jquery.autocomplete.js') }}"></script>

zine/templates/notifications/on_server_error.zeml

+<title>{% trans %}Zine Server Error{% endtrans %}</title>
+<summary>{% trans summary=summary|e %}Zine has suffered an internal error: “{{ summary }}”{% endtrans %}</summary>
+<longtext>
+ {% trans %}Request Details:{% endtrans %}
+--------------------------------------------------------------------------------
+ <pre>{{ request_details|e }}</pre>
+--------------------------------------------------------------------------------
+
+ {% trans %}Python Traceback:{% endtrans %}
+--------------------------------------------------------------------------------
+ <pre>{{ longtext|e }}</pre>
+--------------------------------------------------------------------------------
+</longtext>

zine/utils/mail.py

             #smtp.set_debuglevel(1)
             smtp.ehlo()
             if not smtp.esmtp_features.has_key('starttls'):
-                # XXX: untranlated because python exceptions do not support
+                # XXX: untranslated because python exceptions do not support
                 # unicode messages.
                 raise RuntimeError('TLS enabled but server does not '
                                    'support TLS')
         msgtext = self.format()
         try:
             try:
-                return smtp.sendmail(from_addr, to_addrs, msgtext)
+                return smtp.sendmail(self.from_addr, self.to_addrs, msgtext)
             except SMTPException, e:
                 raise RuntimeError(str(e))
         finally:

zine/utils/zeml.py

     breaking_rules = [
         (['p'], set(['#block'])),
         (['li'], set(['li'])),
-        (['td', 'tr'], set(['td', 'th', 'tr', 'tbody', 'thead', 'tfoot'])),
+        (['td', 'th'], set(['td', 'th', 'tr', 'tbody', 'thead', 'tfoot'])),
         (['tr'], set(['tr', 'tbody', 'thead', 'tfoot'])),
-        (['dd', 'dt'], set(['dl', 'dt', 'dd']))
+        (['thead', 'tbody', 'tfoot'], set(['thead', 'tbody', 'tfoot'])),
+        (['dd', 'dt'], set(['dl', 'dt', 'dd'])),
+        (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], set(['#block']))
     ]
 
     def __init__(self, string, parsing_reason, extensions=None):
         """
         # if the tag is not nestable and we are directly inside a tag with
         # the same name we pop.
-        if self.is_breaking(tag, self.current):
+        while self.is_breaking(tag, self.current):
             self.leave(None)
         element = Element(tag)
         self.current.children.append(element)
 
     def multiline(self, element):
         self.textify(element)
+        self.flush_par()
         self.write_links()
         return self.result.getvalue()
 

zine/views/admin.py

         raise Forbidden(_(u'You\'re not allowed to manage this comment'))
     form = EditCommentForm(comment)
 
-    if request.method == 'POST' and form.validate(request.form):
+    if request.method == 'POST':
         if request.form.get('cancel'):
             return form.redirect('admin/manage_comments')
         elif request.form.get('delete'):
             return redirect_to('admin/delete_comment', comment_id=comment_id)
-        form.save_changes()
-        db.commit()
-        flash(_(u'Comment by %s moderated successfully.') %
-              escape(comment.author))
-        return form.redirect('admin/manage_comments')
+        elif form.validate(request.form):
+            form.save_changes()
+            db.commit()
+            flash(_(u'Comment by %s moderated successfully.') %
+                  escape(comment.author))
+            return form.redirect('admin/manage_comments')
 
     return render_admin_response('admin/edit_comment.html',
                                  'comments.overview', form=form.as_widget())
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.