1. Thomas Waldmann
  2. moin-2.0

Commits

Roger Haase  committed 374eb59 Merge

merge

  • Participants
  • Parent commits 5a3705a, 21fb938
  • Branches default

Comments (0)

Files changed (44)

File MoinMoin/app.py

View file
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
+from MoinMoin.constants.misc import ANON
 from MoinMoin.i18n import i18n_init
 from MoinMoin.i18n import _, L_, N_
 
 
     # if we still have no user obj, create a dummy:
     if not userobj:
-        userobj = user.User(name=u'anonymous', auth_method='invalid')
+        userobj = user.User(name=ANON, auth_method='invalid')
     # if we have a valid user we store it in the session
     if userobj.valid:
         session['user.itemid'] = userobj.itemid

File MoinMoin/apps/frontend/views.py

View file
 from MoinMoin.themes import render_template, contenttype_to_class
 from MoinMoin.apps.frontend import frontend
 from MoinMoin.forms import (OptionalText, RequiredText, URL, YourOpenID, YourEmail, RequiredPassword, Checkbox,
-                            InlineCheckbox, Select, Names, Tags, Natural, Submit, Hidden, MultiSelect)
+                            InlineCheckbox, Select, Names, Tags, Natural, Hidden, MultiSelect)
 from MoinMoin.items import BaseChangeForm, Item, NonExistent
 from MoinMoin.items.content import content_registry
 from MoinMoin import user, util
 from MoinMoin.constants.keys import *
+from MoinMoin.constants.itemtypes import ITEMTYPE_DEFAULT
 from MoinMoin.constants.chartypes import CHARS_UPPER, CHARS_LOWER
 from MoinMoin.util import crypto
 from MoinMoin.util.interwiki import url_for_item
     refs = OptionalText.using(label='refs')
     tags = Tags.using(optional=True).using(label='tags')
     history = InlineCheckbox.using(label=L_('search also in non-current revisions'))
-    submit = Submit.using(default=L_('Lookup'))
+    submit_label = L_('Lookup')
 
 
 @frontend.route('/+lookup', methods=['GET', 'POST'])
     # TAGS might be there multiple times, thus we need multi:
     lookup_form = LookupForm.from_flat(request.values.items(multi=True))
     valid = lookup_form.validate()
-    lookup_form['submit'].set_default()  # XXX from_flat() kills all values
     if valid:
         history = bool(request.values.get('history'))
         idx_name = ALL_REVS if history else LATEST_REVS
     with flaskg.storage.indexer.ix[LATEST_REVS].searcher() as searcher:
         # The search process should be as fast as possible so use
         # the indexer low-level documents instead of high-level Revisions.
-        doc = searcher.document(name_exact=item_name)
+        doc = searcher.document(**{NAME_EXACT: item_name})
         if not doc:
             return set()
         transcluded_names = set(doc[ITEMTRANSCLUSIONS])
 def search(item_name):
     search_form = SearchForm.from_flat(request.values)
     valid = search_form.validate()
-    search_form['submit'].set_default()  # XXX from_flat() kills all values
     query = search_form['q'].value
     if valid:
         history = bool(request.values.get('history'))
             flaskg.clock.start('search')
             results = searcher.search(q, filter=_filter, limit=100)
             flaskg.clock.stop('search')
-            # XXX if found that calling key_terms like you see below is 1000..10000x
-            # slower than the search itself, so we better don't do that right now.
-            key_terms_is_fast = False
-            if key_terms_is_fast:
-                flaskg.clock.start('search suggestions')
-                name_suggestions = u', '.join([word
-                                               for word, score in results.key_terms(NAME, docs=20, numterms=10)])
-                content_suggestions = u', '.join([word
-                                                  for word, score in results.key_terms(CONTENT, docs=20, numterms=10)])
-                flaskg.clock.stop('search suggestions')
-            else:
-                name_suggestions = u''
-                content_suggestions = u''
+            flaskg.clock.start('search suggestions')
+            name_suggestions = [word for word, score in results.key_terms(NAME, docs=20, numterms=10)]
+            content_suggestions = [word for word, score in results.key_terms(CONTENT, docs=20, numterms=10)]
+            flaskg.clock.stop('search suggestions')
             flaskg.clock.start('search render')
             html = render_template('search.html',
                                    results=results,
-                                   name_suggestions=name_suggestions,
-                                   content_suggestions=content_suggestions,
+                                   name_suggestions=u', '.join(name_suggestions),
+                                   content_suggestions=u', '.join(content_suggestions),
                                    query=query,
                                    medium_search_form=search_form,
                                    item_name=item_name,
     item_name_converted = item_name + 'converted'
     try:
         # TODO implement Content.create and use it here
-        converted_item = Item.create(item_name_converted, itemtype=u'default', contenttype=contenttype)
+        converted_item = Item.create(item_name_converted, itemtype=ITEMTYPE_DEFAULT, contenttype=contenttype)
     except AccessDenied:
         abort(403)
     return converted_item.content._convert(item.content.internal_representation())
     After successful POST, redirects to the page.
     """
     # XXX drawing applets don't send itemtype
-    itemtype = request.values.get('itemtype', u'default')
+    itemtype = request.values.get('itemtype', ITEMTYPE_DEFAULT)
     contenttype = request.values.get('contenttype')
     try:
         item = Item.create(item_name, itemtype=itemtype, contenttype=contenttype)
 
 class IndexForm(Form):
     contenttype = ContenttypeGroup
-    submit = Submit.using(default=L_('Filter'))
+    submit_label = L_('Filter')
 
 
 @frontend.route('/+index/', defaults=dict(item_name=''), methods=['GET', 'POST'])
     # values, eg. calling items with multi=True. See Werkzeug documentation for
     # more.
     form = IndexForm.from_flat(request.args.items(multi=True))
-    form['submit'].set_default()  # XXX from_flat() kills all values
     if not form['contenttype']:
         form['contenttype'].set(contenttype_groups)
 
     password2 = RequiredPassword.with_properties(placeholder=L_("Repeat the same password"))
     email = YourEmail
     openid = YourOpenID.using(optional=True)
-    submit = Submit.using(default=L_('Register'))
+    submit_label = L_('Register')
 
     validators = [ValidRegistration()]
 
 
     username = OptionalText.using(label=L_('Name')).with_properties(placeholder=L_("Your login name"))
     email = YourEmail.using(optional=True)
-    submit = Submit.using(default=L_('Recover password'))
+    submit_label = L_('Recover password')
 
     validators = [ValidLostPassword()]
 
         placeholder=L_("The login password you want to use"))
     password2 = RequiredPassword.using(label=L_('New password (repeat)')).with_properties(
         placeholder=L_("Repeat the same password"))
-    submit = Submit.using(default=L_('Change password'))
+    submit_label = L_('Change password')
 
     validators = [ValidPasswordRecovery()]
 
     username = RequiredText.using(label=L_('Name'), optional=False).with_properties(autofocus=True)
     password = RequiredPassword
     openid = YourOpenID.using(optional=True)
-    submit = Submit.using(default=L_('Log in'))
+    # This field results in a login_submit field in the POST form, which is in
+    # turn looked for by setup_user() in app.py as marker for login requests.
+    submit = Hidden.using(default='1')
+    submit_label = L_('Log in')
 
     validators = [ValidLogin()]
 
         placeholder=L_("The login password you want to use"))
     password2 = RequiredPassword.using(label=L_('New password (repeat)')).with_properties(
         placeholder=L_("Repeat the same password"))
-    submit = Submit.using(default=L_('Change password'))
+    submit_label = L_('Change password')
 
 
 class UserSettingsNotificationForm(Form):
     name = 'usersettings_notification'
     email = YourEmail
-    submit = Submit.using(default=L_('Save'))
+    submit_label = L_('Save')
 
 
 class UserSettingsNavigationForm(Form):
     name = 'usersettings_navigation'
+    # XXX Flatland insists a form having at least one element
+    dummy = Hidden
     # TODO: find a good way to handle quicklinks here
-    submit = Submit.using(default=L_('Save'))
+    submit_label = L_('Save')
 
 
 class UserSettingsOptionsForm(Form):
     scroll_page_after_edit = Checkbox.using(label=L_('Scroll page after edit'))
     show_comments = Checkbox.using(label=L_('Show comment sections'))
     disabled = Checkbox.using(label=L_('Disable this account forever'))
-    submit = Submit.using(default=L_('Save'))
+    submit_label = L_('Save')
 
 
 @frontend.route('/+usersettings', methods=['GET', 'POST'])
                                    key=lambda x: x[1])
         locales_keys = [l[0] for l in locales_available]
         locale = Select.using(label=L_('Locale')).with_properties(labels=dict(locales_available)).valued(*locales_keys)
-        submit = Submit.using(default=L_('Save'))
+        submit_label = L_('Save')
 
     class UserSettingsUIForm(Form):
         name = 'usersettings_ui'
             placeholder=L_("Editor textarea height (0=auto)"))
         results_per_page = Natural.using(label=L_('History results per page')).with_properties(
             placeholder=L_("Number of results per page (0=no paging)"))
-        submit = Submit.using(default=L_('Save'))
+        submit_label = L_('Save')
 
     form_classes = dict(
         personal=UserSettingsPersonalForm,
                         if set(form['name'].value) != set(flaskg.user.name):
                             new_names = set(form['name'].value) - set(flaskg.user.name)
                             for name in new_names:
-                                if user.search_users(name_exact=name):
+                                if user.search_users(**{NAME_EXACT: name}):
                                     # duplicate name
                                     response['flash'].append((_("The username %(name)r is already in use.", name=name),
                                                               "error"))
                                     success = False
                     if part == 'notification':
                         if (form['email'].value != flaskg.user.email and
-                            user.search_users(email=form['email'].value) and app.cfg.user_email_unique):
+                            user.search_users(**{EMAIL: form['email'].value}) and app.cfg.user_email_unique):
                             # duplicate email
                             response['flash'].append((_('This email is already in use'), 'error'))
                             success = False
                     if success:
                         user_old_email = flaskg.user.email
                         d = dict(form.value)
-                        d.pop('submit')
                         for k, v in d.items():
                             flaskg.user.profile[k] = v
                         if (part == 'notification' and app.cfg.user_email_verification and

File MoinMoin/auth/_tests/test_auth.py

View file
 from flask import g as flaskg
 
 from MoinMoin._tests import wikiconfig
+from MoinMoin.constants.misc import ANON
 from MoinMoin.auth import GivenAuth, handle_login, get_multistage_continuation_url
 from MoinMoin.user import create_user
 
     test_user1 = handle_login(flaskg.user, login_username='test_user', login_password='test_password', stage='moin')
     test_login_message = [u'Invalid username or password.']
     assert flaskg._login_messages == test_login_message
-    assert test_user1.name0 == u'anonymous'
+    assert test_user1.name0 == ANON
     assert not test_user1.valid
     # pop the message
     flaskg._login_messages.pop()

File MoinMoin/auth/_tests/test_http.py

View file
 
 from MoinMoin.user import create_user
 from MoinMoin.auth.http import HTTPAuthMoin
+from MoinMoin.constants.misc import ANON
 
 
 class TestHTTPAuthMoin(object):
         flaskg.user.auth_method = 'invalid'
         test_user, bool_val = httpauthmoin_obj.request(flaskg.user)
         assert not test_user.valid
-        assert test_user.name0 == u'anonymous'
+        assert test_user.name0 == ANON

File MoinMoin/auth/_tests/test_log.py

View file
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
+from flask import g as flaskg
+
 from MoinMoin.auth.log import AuthLog
-from flask import g as flaskg
+from MoinMoin.constants.misc import ANON
 
 
 class TestAuthLog(object):
         result = authlog_obj.login(flaskg.user)
         assert result.continue_flag
         test_user_obj = result.user_obj
-        assert test_user_obj.name0 == u'anonymous'
+        assert test_user_obj.name0 == ANON
 
     def test_request(self):
         authlog_obj = AuthLog()
         result = authlog_obj.request(flaskg.user)
         test_user, bool_value = result
-        assert test_user.name0 == u'anonymous'
+        assert test_user.name0 == ANON
         assert not test_user.valid
         assert bool_value
 
         authlog_obj = AuthLog()
         result = authlog_obj.logout(flaskg.user)
         test_user, bool_value = result
-        assert test_user.name0 == u'anonymous'
+        assert test_user.name0 == ANON
         assert not test_user.valid
         assert bool_value

File MoinMoin/config/default.py

View file
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin import error
 from MoinMoin.constants.rights import ACL_RIGHTS_CONTENTS, ACL_RIGHTS_FUNCTIONS
+from MoinMoin.constants.keys import *
 from MoinMoin import datastruct
 from MoinMoin.auth import MoinAuth
 from MoinMoin.util import plugins
 
         plugins._loadPluginModule(self)
 
-        if self.user_defaults['timezone'] is None:
-            self.user_defaults['timezone'] = self.timezone_default
-        if self.user_defaults['theme_name'] is None:
-            self.user_defaults['theme_name'] = self.theme_default
+        if self.user_defaults[TIMEZONE] is None:
+            self.user_defaults[TIMEZONE] = self.timezone_default
+        if self.user_defaults[THEME_NAME] is None:
+            self.user_defaults[THEME_NAME] = self.theme_default
         # Note: do not assign user_defaults['locale'] = locale_default
         # to give browser language detection a chance.
         try:
   # ==========================================================================
   'user': ('User Preferences', None, (
     ('user_defaults',
-      dict(
-        name=[],
-        display_name=None,
-        email=None,
-        openid=None,
-        css_url=None,
-        mailto_author=False,
-        edit_on_doubleclick=True,
-        scroll_page_after_edit=True,
-        show_comments=False,
-        want_trivial=False,
-        enc_password=u'',  # empty value == invalid hash
-        disabled=False,
-        bookmarks={},
-        quicklinks=[],
-        subscribed_items=[],
-        email_subscribed_events=[
+     {
+        NAME: [],
+        DISPLAY_NAME: None,
+        EMAIL: None,
+        OPENID: None,
+        CSS_URL: None,
+        MAILTO_AUTHOR: False,
+        EDIT_ON_DOUBLECLICK: True,
+        SCROLL_PAGE_AFTER_EDIT: True,
+        SHOW_COMMENTS: False,
+        WANT_TRIVIAL: False,
+        ENC_PASSWORD: u'',  # empty value == invalid hash
+        DISABLED: False,
+        BOOKMARKS: {},
+        QUICKLINKS: [],
+        SUBSCRIBED_ITEMS: [],
+        EMAIL_SUBSCRIBED_EVENTS: [
             # XXX PageChangedEvent.__name__
             # XXX PageRenamedEvent.__name__
             # XXX PageDeletedEvent.__name__
             # XXX PageCopiedEvent.__name__
             # XXX PageRevertedEvent.__name__
         ],
-        theme_name=None,  # None -> use cfg.theme_default
-        edit_rows=0,
-        results_per_page=0,
-        locale=None,  # None -> do browser language detection, otherwise just use this locale
-        timezone=None,  # None -> use cfg.timezone_default
-      ),
+        THEME_NAME: None,  # None -> use cfg.theme_default
+        EDIT_ROWS: 0,
+        RESULTS_PER_PAGE: 0,
+        LOCALE: None,  # None -> do browser language detection, otherwise just use this locale
+        TIMEZONE: None,  # None -> use cfg.timezone_default
+     },
      'Default attributes of the user object'),
   )),
   # ==========================================================================

File MoinMoin/constants/forms.py

View file
 WIDGET_HIDDEN = u'hidden'
 
 WIDGET_SELECT = u'select'
+WIDGET_SELECT_SUBMIT = u'select_submit'
 WIDGET_MULTI_SELECT = u'multi_select'
 
 WIDGET_READONLY_STRING_LIST = u'readonly_string_list'

File MoinMoin/constants/keys.py

View file
 CSS_URL = "css_url"
 EDIT_ROWS = "edit_rows"
 RESULTS_PER_PAGE = "results_per_page"
+WANT_TRIVIAL = "want_trivial"
+EMAIL_SUBSCRIBED_EVENTS = "email_subscribed_events"
 DISABLED = "disabled"
 
 # in which backend is some revision stored?

File MoinMoin/constants/misc.py

View file
 
 import re
 
+ANON = u'anonymous'
+
 # Invalid characters - invisible characters that should not be in page
 # names. Prevent user confusion and wiki abuse, e.g u'\u202aFrontPage'.
 ITEM_INVALID_CHARS_REGEX = re.compile(

File MoinMoin/forms.py

View file
 
 Select = Enum.with_properties(widget=WIDGET_SELECT)
 
+# SelectSubmit is like Select in that it is rendered as a group of controls
+# with different (predefined) `value`s for the same `name`. But the controls are
+# submit buttons instead of radio buttons.
+#
+# This is used to present the user several "OK" buttons with slightly different
+# semantics, like "Update" and "Update and Close" on a ticket page, or
+# "Save as Draft" and "Publish" when editing a blog entry.
+SelectSubmit = Enum.with_properties(widget=WIDGET_SELECT_SUBMIT)
+
 
 # Need a better name to capture the behavior
 class MyJoinedString(JoinedString):
 
 File = FileStorage.with_properties(widget=WIDGET_FILE)
 
-Submit = String.using(default=L_('OK'), optional=True).with_properties(widget=WIDGET_SUBMIT, class_=CLASS_BUTTON)
-
 Hidden = String.using(optional=True).with_properties(widget=WIDGET_HIDDEN)
 
 # optional=True is needed to get rid of the "required field" indicator on the UI (usually an asterisk)

File MoinMoin/items/__init__.py

View file
 from MoinMoin.util.interwiki import url_for_item
 from MoinMoin.util.registry import RegistryBase
 from MoinMoin.util.clock import timed
-from MoinMoin.forms import RequiredText, OptionalText, JSON, Tags, Submit
+from MoinMoin.forms import RequiredText, OptionalText, JSON, Tags
 from MoinMoin.constants.keys import (
     NAME, NAME_OLD, NAME_EXACT, WIKINAME, MTIME, SYSITEM_VERSION, ITEMTYPE,
     CONTENTTYPE, SIZE, ACTION, ADDRESS, HOSTNAME, USERID, COMMENT,
 
 class BaseChangeForm(TextChaizedForm):
     comment = OptionalText.using(label=L_('Comment')).with_properties(placeholder=L_("Comment about your change"))
-    submit = Submit
+    submit_label = L_('OK')
 
 
 class BaseMetaForm(Form):
         # content_registry.get yet, have to patch it later.
         content = Content.create(contenttype)
 
-        itemtype = rev.meta.get(ITEMTYPE) or itemtype or u'default'
+        itemtype = rev.meta.get(ITEMTYPE) or itemtype or ITEMTYPE_DEFAULT
         logging.debug("Item {0!r}, got itemtype {1!r} from revision meta".format(name, itemtype))
 
         item = item_registry.get(itemtype, name, rev=rev, content=content)

File MoinMoin/items/_tests/test_Blog.py

View file
 from MoinMoin._tests import become_trusted, update_item
 from MoinMoin.items import Item
 from MoinMoin.constants.keys import CONTENTTYPE, ITEMTYPE, PTIME, ACL, TAGS
+from MoinMoin.constants.misc import ANON
 from MoinMoin.items.blog import ITEMTYPE_BLOG, ITEMTYPE_BLOG_ENTRY
 from MoinMoin.items.blog import Blog, BlogEntry
 
             item._save(self.entry_meta, entry['data'], comment=self.comment)
         # publish the first three entries with specific ACLs
         # we are an "anonymous" user
-        self._publish_entry(self.entries[0], ptime=1000, acl=u"anonymous:read")
-        self._publish_entry(self.entries[1], ptime=3000, acl=u"anonymous:read")
+        self._publish_entry(self.entries[0], ptime=1000, acl=u"%s:read" % ANON)
+        self._publish_entry(self.entries[1], ptime=3000, acl=u"%s:read" % ANON)
         # specify no rights on the 3rd entry
-        self._publish_entry(self.entries[2], ptime=2000, acl=u"anonymous:")
+        self._publish_entry(self.entries[2], ptime=2000, acl=u"%s:" % ANON)
         # the blog is not empty and the 3rd entry is not displayed
         exclude_data_tokens = [self.NO_ENTRIES_MSG, self.entries[2]['data'], ]
         ordered_data = [self.data,

File MoinMoin/items/_tests/test_Content.py

View file
 from MoinMoin._tests import become_trusted, update_item
 from MoinMoin.items import Item
 from MoinMoin.items.content import Content, ApplicationXTar, Binary, Text, Image, TransformableBitmapImage, MarkupItem
-from MoinMoin.constants.keys import CONTENTTYPE
+from MoinMoin.constants.keys import CONTENTTYPE, TAGS
+from MoinMoin.constants.itemtypes import ITEMTYPE_DEFAULT
 
 
 class TestContent(object):
         item_name1 = u'Template_Item1'
         item1 = Item.create(item_name1)
         contenttype1 = u'text/plain'
-        meta = {CONTENTTYPE: contenttype1, 'tags': ['template']}
+        meta = {CONTENTTYPE: contenttype1, TAGS: ['template']}
         item1._save(meta)
         item1 = Item.create(item_name1)
 
         item_name2 = u'Template_Item2'
         item2 = Item.create(item_name2)
         contenttype1 = u'text/plain'
-        meta = {CONTENTTYPE: contenttype1, 'tags': ['template']}
+        meta = {CONTENTTYPE: contenttype1, TAGS: ['template']}
         item2._save(meta)
         item2 = Item.create(item_name2)
 
         item_name3 = u'Template_Item3'
         item3 = Item.create(item_name3)
         contenttype2 = u'image/png'
-        meta = {CONTENTTYPE: contenttype2, 'tags': ['template']}
+        meta = {CONTENTTYPE: contenttype2, TAGS: ['template']}
         item3._save(meta)
         item3 = Item.create(item_name3)
         # two items of same content type
         creates a container and tests the content saved to the container
         """
         item_name = u'ContainerItem1'
-        item = Item.create(item_name, itemtype=u'default', contenttype=u'application/x-tar')
+        item = Item.create(item_name, itemtype=ITEMTYPE_DEFAULT, contenttype=u'application/x-tar')
         filecontent = 'abcdefghij'
         content_length = len(filecontent)
         members = set(['example1.txt', 'example2.txt'])
         item.content.put_member('example1.txt', filecontent, content_length, expected_members=members)
         item.content.put_member('example2.txt', filecontent, content_length, expected_members=members)
 
-        item = Item.create(item_name, itemtype=u'default', contenttype=u'application/x-tar')
+        item = Item.create(item_name, itemtype=ITEMTYPE_DEFAULT, contenttype=u'application/x-tar')
         tf_names = set(item.content.list_members())
         assert tf_names == members
         assert item.content.get_member('example1.txt').read() == filecontent
         creates two revisions of a container item
         """
         item_name = u'ContainerItem2'
-        item = Item.create(item_name, itemtype=u'default', contenttype=u'application/x-tar')
+        item = Item.create(item_name, itemtype=ITEMTYPE_DEFAULT, contenttype=u'application/x-tar')
         filecontent = 'abcdefghij'
         content_length = len(filecontent)
         members = set(['example1.txt'])
 
     def test_put_member(self):
         item_name = u'Zip_file'
-        item = Item.create(item_name, itemtype=u'default', contenttype='application/zip')
+        item = Item.create(item_name, itemtype=ITEMTYPE_DEFAULT, contenttype='application/zip')
         filecontent = 'test_contents'
         content_length = len(filecontent)
         members = set(['example1.txt', 'example2.txt'])
 
     def test_data_conversion(self):
         item_name = u'Text_Item'
-        item = Item.create(item_name, u'default', u'text/plain')
+        item = Item.create(item_name, ITEMTYPE_DEFAULT, u'text/plain')
         test_text = u'This \n is \n a \n Test'
         # test for data_internal_to_form
         result = Text.data_internal_to_form(item.content, test_text)

File MoinMoin/items/_tests/test_Item.py

View file
 
 from MoinMoin._tests import become_trusted, update_item
 from MoinMoin.items import Item, NonExistent, IndexEntry, MixedIndexEntry
-from MoinMoin.constants.keys import ITEMTYPE, CONTENTTYPE, NAME, COMMENT
+from MoinMoin.constants.keys import ITEMTYPE, CONTENTTYPE, NAME, NAME_OLD, COMMENT, ACTION, ADDRESS
+from MoinMoin.constants.contenttypes import CONTENTTYPE_NONEXISTENT
+from MoinMoin.constants.itemtypes import ITEMTYPE_NONEXISTENT
 
 
 def build_index(basename, relnames):
         assert isinstance(item, NonExistent)
         meta, data = item.meta, item.content.data
         assert meta == {
-                ITEMTYPE: u'nonexistent',
-                CONTENTTYPE: u'application/x-nonexistent',
+                ITEMTYPE: ITEMTYPE_NONEXISTENT,
+                CONTENTTYPE: CONTENTTYPE_NONEXISTENT,
                 NAME: u'DoesNotExist',
                 }
         assert data == ''
     def test_meta_filter(self):
         name = u'Test_item'
         contenttype = u'text/plain;charset=utf-8'
-        meta = {'test_key': 'test_val', CONTENTTYPE: contenttype, NAME: [u'test_name'], 'address': u'1.2.3.4'}
+        meta = {'test_key': 'test_val', CONTENTTYPE: contenttype, NAME: [u'test_name'], ADDRESS: u'1.2.3.4'}
         item = Item.create(name)
         result = Item.meta_filter(item, meta)
         # keys like NAME, ITEMID, REVID, DATAID are filtered
         # item and its contents before renaming
         item = Item.create(name)
         assert item.name == u'Test_Item'
-        assert item.meta['comment'] == u'saved it'
+        assert item.meta[COMMENT] == u'saved it'
         new_name = u'Test_new_Item'
         item.rename(new_name, comment=u'renamed')
         # item at original name and its contents after renaming
         item = Item.create(name)
         assert item.name == u'Test_Item'
         # this should be a fresh, new item, NOT the stuff we renamed:
-        assert item.meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert item.meta[CONTENTTYPE] == CONTENTTYPE_NONEXISTENT
         # item at new name and its contents after renaming
         item = Item.create(new_name)
         assert item.name == u'Test_new_Item'
-        assert item.meta['name_old'] == [u'Test_Item']
-        assert item.meta['comment'] == u'renamed'
+        assert item.meta[NAME_OLD] == [u'Test_Item']
+        assert item.meta[COMMENT] == u'renamed'
         assert item.content.data == u'test_data'
 
     def test_rename_acts_only_in_active_name_in_case_there_are_several_names(self):
         assert item1.rev.revid == item2.rev.revid == item3.rev.revid
 
         item4 = Item.create(u'Second')
-        assert item4.meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert item4.meta[CONTENTTYPE] == CONTENTTYPE_NONEXISTENT
 
     def test_rename_recursion(self):
         update_item(u'Page', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Page 1')
         # items at original name and its contents after renaming
         item = Item.create(u'Page')
         assert item.name == u'Page'
-        assert item.meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert item.meta[CONTENTTYPE] == CONTENTTYPE_NONEXISTENT
         item = Item.create(u'Page/Child')
         assert item.name == u'Page/Child'
-        assert item.meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert item.meta[CONTENTTYPE] == CONTENTTYPE_NONEXISTENT
         item = Item.create(u'Page/Child/Another')
         assert item.name == u'Page/Child/Another'
-        assert item.meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert item.meta[CONTENTTYPE] == CONTENTTYPE_NONEXISTENT
 
         # item at new name and its contents after renaming
         item = Item.create(u'Renamed_Page')
         assert item.name == u'Renamed_Page'
-        assert item.meta['name_old'] == [u'Page']
-        assert item.meta['comment'] == u'renamed'
+        assert item.meta[NAME_OLD] == [u'Page']
+        assert item.meta[COMMENT] == u'renamed'
         assert item.content.data == u'Page 1'
 
         item = Item.create(u'Renamed_Page/Child')
         assert item.name == u'Renamed_Page/Child'
-        assert item.meta['name_old'] == [u'Page/Child']
-        assert item.meta['comment'] == u'renamed'
+        assert item.meta[NAME_OLD] == [u'Page/Child']
+        assert item.meta[COMMENT] == u'renamed'
         assert item.content.data == u'this is child'
 
         item = Item.create(u'Renamed_Page/Child/Another')
         assert item.name == u'Renamed_Page/Child/Another'
-        assert item.meta['name_old'] == [u'Page/Child/Another']
-        assert item.meta['comment'] == u'renamed'
+        assert item.meta[NAME_OLD] == [u'Page/Child/Another']
+        assert item.meta[COMMENT] == u'renamed'
         assert item.content.data == u'another child'
 
     def test_rename_recursion_with_multiple_names_and_children(self):
 
         item.rename(u'Renamed', comment=u'renamed')
 
-        assert Item.create(u'Page/Child').meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert Item.create(u'Page/Child').meta[CONTENTTYPE] == CONTENTTYPE_NONEXISTENT
         assert Item.create(u'Renamed/Child').content.data == u'Child of Page'
-        assert Item.create(u'Page/Second').meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert Item.create(u'Page/Second').meta[CONTENTTYPE] == CONTENTTYPE_NONEXISTENT
         assert Item.create(u'Renamed/Second').content.data == u'Both'
         assert Item.create(u'Another').content.data == u'Both'
-        assert Item.create(u'Page/Second/Child').meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert Item.create(u'Page/Second/Child').meta[CONTENTTYPE] == CONTENTTYPE_NONEXISTENT
         assert Item.create(u'Renamed/Second/Child').content.data == u'Child of Second'
         assert Item.create(u'Other/Child2').content.data == u'Child of Other'
         assert Item.create(u'Another/Child').content.data == u'Child of Another'
         # item and its contents before deleting
         item = Item.create(name)
         assert item.name == u'Test_Item2'
-        assert item.meta['comment'] == u'saved it'
+        assert item.meta[COMMENT] == u'saved it'
         item.delete(u'deleted')
         # item and its contents after deletion
         item = Item.create(name)
         assert item.name == u'Test_Item2'
         # this should be a fresh, new item, NOT the stuff we deleted:
-        assert item.meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert item.meta[CONTENTTYPE] == CONTENTTYPE_NONEXISTENT
 
     def test_revert(self):
         name = u'Test_Item'
         item = Item.create(name)
         item.revert(u'revert')
         item = Item.create(name)
-        assert item.meta['action'] == u'REVERT'
+        assert item.meta[ACTION] == u'REVERT'
 
     def test_modify(self):
         name = u'Test_Item'

File MoinMoin/items/ticket.py

View file
 
 from MoinMoin.i18n import L_
 from MoinMoin.themes import render_template
-from MoinMoin.forms import (Form, OptionalText, OptionalMultilineText, Submit, SmallNatural, Tags,
+from MoinMoin.forms import (Form, OptionalText, OptionalMultilineText, SmallNatural, Tags,
                             Reference, BackReference)
 from MoinMoin.storage.middleware.protecting import AccessDenied
 from MoinMoin.constants.keys import ITEMTYPE, CONTENTTYPE, ITEMID, CURRENT
     meta = TicketMetaForm
     backrefs = TicketBackRefForm
     message = OptionalMultilineText.using(label=L_("Message")).with_properties(rows=8, cols=80)
-    submit = Submit.using(default=L_("Update ticket"))
+    submit_label = L_("Update ticket")
 
     def _load(self, item):
         meta = item.prepare_meta_for_modify(item.meta)
             return self.do_modify()
 
     def do_modify(self):
+        is_new = isinstance(self.content, NonExistentContent)
+
         if request.method in ['GET', 'HEAD']:
             form = TicketForm.from_item(self)
         elif request.method == 'POST':
                     CONTENTTYPE: 'text/x.moin.wiki;charset=utf-8',
                 })
 
-                if isinstance(self.content, NonExistentContent):
-                    data = u''
-                else:
-                    data = self.content.data_storage_to_internal(self.content.data)
+                data = u'' if is_new else self.content.data_storage_to_internal(self.content.data)
                 message = form['message'].value
                 if message:
                     data += message_markup(message)
                     abort(403)
                 else:
                     return redirect(url_for('.show_item', item_name=self.name))
-        if isinstance(self.content, NonExistentContent):
-            is_new = True
+        if is_new:
             # XXX suppress the "foo doesn't exist. Create it?" dummy content
             data_rendered = None
-            form['submit'] = L_('Submit ticket')
+            form.submit_label = L_('Submit ticket')
         else:
-            is_new = False
             data_rendered = Markup(self.content._render_data())
 
         return render_template(self.modify_template,

File MoinMoin/script/migration/moin19/import19.py

View file
 from ._utils19 import quoteWikinameFS, unquoteWikiname, split_body
 from ._logfile19 import LogFile
 
-from MoinMoin.constants.keys import (ACL, CONTENTTYPE, NAME, NAME_OLD, REVERTED_TO, ACTION, ADDRESS, HOSTNAME,
-                                     USERID, MTIME, EXTRA, COMMENT, TAGS, SIZE, HASH_ALGORITHM, ITEMID, REVID)
+from MoinMoin.constants.keys import *
 from MoinMoin.constants.contenttypes import CONTENTTYPE_USER
 
 UID_OLD = 'old_user_id'  # dynamic field *_id, so we don't have to change schema
     def _process_usermeta(self, metadata):
         # stuff we want to have stored as boolean:
         bool_defaults = [  # taken from cfg.checkbox_defaults
-            ('show_comments', 'False'),
-            ('edit_on_doubleclick', 'True'),
-            ('scroll_page_after_edit', 'True'),
-            ('want_trivial', 'False'),
-            ('mailto_author', 'False'),
-            ('disabled', 'False'),
+            (SHOW_COMMENTS, 'False'),
+            (EDIT_ON_DOUBLECLICK, 'True'),
+            (SCROLL_PAGE_AFTER_EDIT, 'True'),
+            (WANT_TRIVIAL, 'False'),
+            (MAILTO_AUTHOR, 'False'),
+            (DISABLED, 'False'),
         ]
         for key, default in bool_defaults:
             metadata[key] = metadata.get(key, default) in ['True', 'true', '1']
 
         # stuff we want to have stored as integer:
         int_defaults = [
-            ('edit_rows', '0'),
+            (EDIT_ROWS, '0'),
         ]
         for key, default in int_defaults:
             metadata[key] = int(metadata.get(key, default))
         metadata[MTIME] = int(float(metadata.get('last_saved', '0')))
 
         # rename aliasname to display_name:
-        metadata['display_name'] = metadata.get('aliasname')
+        metadata[DISPLAY_NAME] = metadata.get('aliasname')
 
         # rename subscribed_pages to subscribed_items
-        metadata['subscribed_items'] = metadata.get('subscribed_pages', [])
+        metadata[SUBSCRIBED_ITEMS] = metadata.get('subscribed_pages', [])
 
         # convert bookmarks from usecs (and str) to secs (int)
-        metadata['bookmarks'] = [(interwiki, int(long(bookmark) / 1000000))
-                                 for interwiki, bookmark in metadata.get('bookmarks', {}).items()]
+        metadata[BOOKMARKS] = [(interwiki, int(long(bookmark) / 1000000))
+                               for interwiki, bookmark in metadata.get('bookmarks', {}).items()]
 
         # stuff we want to get rid of:
         kill = ['aliasname',  # renamed to display_name
 
         # finally, remove some empty values (that have empty defaults anyway or
         # make no sense when empty):
-        empty_kill = ['aliasname', 'display_name', 'bookmarks', 'enc_password',
-                      'language', 'css_url', 'email', ]  # XXX check subscribed_items, quicklinks
+        empty_kill = ['aliasname', DISPLAY_NAME, BOOKMARKS, ENC_PASSWORD,
+                      'language', CSS_URL, EMAIL, ]  # XXX check subscribed_items, quicklinks
         for key in empty_kill:
             if key in metadata and metadata[key] in [u'', tuple(), {}, [], ]:
                 del metadata[key]
 
         # moin2 only supports passlib generated hashes, drop everything else
         # (users need to do pw recovery in case they are affected)
-        pw = metadata.get('enc_password')
+        pw = metadata.get(ENC_PASSWORD)
         if pw is not None:
             if pw.startswith('{PASSLIB}'):
                 # take it, but strip the prefix as moin2 does not use that any more
-                metadata['enc_password'] = pw[len('{PASSLIB}'):]
+                metadata[ENC_PASSWORD] = pw[len('{PASSLIB}'):]
             else:
                 # drop old, unsupported (and also more or less unsafe) hashing scheme
-                del metadata['enc_password']
+                del metadata[ENC_PASSWORD]
 
         # TODO quicklinks and subscribed_items - check for non-interwiki elements and convert them to interwiki
 

File MoinMoin/search/__init__.py

View file
 """
 
 from MoinMoin.i18n import L_
-from MoinMoin.forms import Search, InlineCheckbox, Submit
+from MoinMoin.forms import Search, InlineCheckbox
 
 from flatland import Form, String, Boolean
 from flatland.validation import Validator
 class SearchForm(Form):
     q = Search
     history = InlineCheckbox.using(label=L_('search all revisions'))
-    submit = Submit.using(default=L_('Search'))
+    submit_label = L_('Search')
 
     validators = [ValidSearch()]

File MoinMoin/security/__init__.py

View file
 from flask import abort
 
 from MoinMoin.constants import rights
+from MoinMoin.constants.keys import NAME_EXACT
 from MoinMoin import user
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.util.pysupport import AutoNe
             that means that there is a valid user account present.
             works for subscription emails.
         """
-        if user.search_users(name_exact=name):  # is a user with this name known?
+        if user.search_users(**{NAME_EXACT: name}):  # is a user with this name known?
             return rightsdict.get(dowhat)
         return None
 

File MoinMoin/security/_tests/test_security.py

View file
 
     TO DO: test unknown user?
     """
+    def testhasACL(self):
+        acl = AccessControlList(valid=app.cfg.acl_rights_contents)
+        assert not acl.has_acl()
+        acl = AccessControlList(["All:read", ], valid=app.cfg.acl_rights_contents)
+        assert acl.has_acl()
+
     def testApplyACLByUser(self):
         """ security: applying acl by user name"""
         # This acl string...
     from MoinMoin._tests import wikiconfig
     class Config(wikiconfig.Config):
         content_acl = dict(hierarchic=False, before=u"WikiAdmin:admin,read,write,create,destroy", default=u"All:read,write", after=u"All:read")
+        acl_functions = u"SuperUser:superuser NoTextchaUser:notextcha"
 
     def setup_method(self, method):
         become_trusted(username=u'WikiAdmin')
             for right in mayNot:
                 yield _not_have_right, u, right, itemname
 
+        # check function rights
+        u = User(auth_username='SuperUser')
+        assert u.may.superuser()
+        u = User(auth_username='NoTextchaUser')
+        assert u.may.notextcha()
+        u = User(auth_username='SomeGuy')
+        assert not u.may.superuser()
+        assert not u.may.notextcha()
 
 class TestItemHierachicalAcls(object):
     """ security: real-life access control list on items testing

File MoinMoin/storage/middleware/_tests/test_serialization.py

View file
 from ..routing import Backend as RoutingBackend
 from ..serialization import serialize, deserialize
 
+from MoinMoin.constants.keys import NAME, CONTENTTYPE
+
 from MoinMoin.storage.backends.stores import MutableBackend
 from MoinMoin.storage.stores.memory import BytesStore, FileStore
 
 
 contents = [
-    (u'Foo', {'name': [u'Foo', ], 'contenttype': u'text/plain'}, ''),
-    (u'Foo', {'name': [u'Foo', ], 'contenttype': u'text/plain'}, '2nd'),
-    (u'Subdir', {'name': [u'Subdir', ], 'contenttype': u'text/plain'}, ''),
-    (u'Subdir/Foo', {'name': [u'Subdir/Foo', ], 'contenttype': u'text/plain'}, ''),
-    (u'Subdir/Bar', {'name': [u'Subdir/Bar', ], 'contenttype': u'text/plain'}, ''),
+    (u'Foo', {NAME: [u'Foo', ], CONTENTTYPE: u'text/plain'}, ''),
+    (u'Foo', {NAME: [u'Foo', ], CONTENTTYPE: u'text/plain'}, '2nd'),
+    (u'Subdir', {NAME: [u'Subdir', ], CONTENTTYPE: u'text/plain'}, ''),
+    (u'Subdir/Foo', {NAME: [u'Subdir/Foo', ], CONTENTTYPE: u'text/plain'}, ''),
+    (u'Subdir/Bar', {NAME: [u'Subdir/Bar', ], CONTENTTYPE: u'text/plain'}, ''),
 ]
 
 
 def test_serialize_deserialize(source, target):
     i = 0
     for name, meta, data in contents:
-        item = source[u'name']
+        item = source[name]
         item.store_revision(dict(meta, mtime=i), StringIO(data))
         i += 1
 

File MoinMoin/storage/middleware/indexing.py

View file
         """
         Return item with <name> (may be a new or existing item).
         """
-        return Item(self, name_exact=name)
+        return Item(self, **{NAME_EXACT: name})
 
     def get_item(self, **query):
         """
         """
         self.indexer = indexer
         self.backend = self.indexer.backend
-        self._name = query.get('name_exact')
+        self._name = query.get(NAME_EXACT)
         if latest_doc is None:
             # we need to call the method without acl check to avoid endless recursion:
             latest_doc = self.indexer._document(**query)
         """
         parent_ids = set()
         for parent_name in self.parentnames:
-            rev = self.indexer._document(idx_name=LATEST_REVS, name_exact=parent_name)
+            rev = self.indexer._document(idx_name=LATEST_REVS, **{NAME_EXACT: parent_name})
             if rev:
                 parent_ids.add(rev[ITEMID])
         return parent_ids

File MoinMoin/storage/middleware/protecting.py

View file
 from whoosh.util import lru_cache
 
 from MoinMoin.constants.rights import (CREATE, READ, PUBREAD, WRITE, DESTROY, ACL_RIGHTS_CONTENTS)
-from MoinMoin.constants.keys import ALL_REVS, LATEST_REVS
+from MoinMoin.constants.keys import ALL_REVS, LATEST_REVS, NAME_EXACT, ITEMID
 
 from MoinMoin.security import AccessControlList
 
         """
 
         if itemid is not None:
-            item = self.get_item(itemid=itemid)
+            q = {ITEMID: itemid}
         elif fqname is not None:
             # itemid might be None for new, not yet stored items,
             # but we have fqname then
-            item = self.get_item(name_exact=fqname)
+            q = {NAME_EXACT: fqname}
         else:
             raise ValueError("need itemid or fqname")
+        item = self.get_item(**q)
         acl = item.acl
         fqname = item.fqname
         if acl is not None:

File MoinMoin/storage/middleware/serialization.py

View file
 
 4 bytes length of meta (m)
 m bytes metadata (json serialization, utf-8 encoded)
-        (the metadata contains the data length d in meta['size'])
+        (the metadata contains the data length d in meta[SIZE])
 d bytes binary data
 ... (repeat for all meta/data)
 4 bytes 00 (== length of next meta -> there is none, this is the end)
 
 from werkzeug.wsgi import LimitedStream
 
+from MoinMoin.constants.keys import NAME, ITEMTYPE, SIZE
+from MoinMoin.constants.itemtypes import ITEMTYPE_DEFAULT
+
 
 def serialize(backend, dst):
     dst.writelines(serialize_iter(backend))
         meta_str = src.read(meta_size)
         text = meta_str.decode('utf-8')
         meta = json.loads(text)
-        name = meta.get('name')
+        name = meta.get(NAME)
         if isinstance(name, unicode):
             # if we encounter single names, make a list of names:
-            meta['name'] = [name, ]
-        if 'itemtype' not in meta:
+            meta[NAME] = [name, ]
+        if ITEMTYPE not in meta:
             # temporary hack to upgrade serialized item files:
-            meta['itemtype'] = u'default'
-        data_size = meta[u'size']
+            meta[ITEMTYPE] = ITEMTYPE_DEFAULT
+        data_size = meta[SIZE]
         curr_pos = src.tell()
         limited = LimitedStream(src, data_size)
         backend.store(meta, limited)

File MoinMoin/storage/middleware/validation.py

View file
 
 UserMetaSchema = DuckDict.named('UserMetaSchema').of(
     String.named(keys.CONTENTTYPE).validated_by(user_contenttype_validator),
-    String.named('email').using(optional=True),
-    String.named('openid').using(optional=True),
-    String.named('enc_password').using(optional=True),
-    String.named('recoverpass_key').using(optional=True),
-    String.named('theme_name').using(optional=True),
-    String.named('timezone').using(optional=True),
-    String.named('locale').using(optional=True),
-    String.named('css_url').using(optional=True),
-    Integer.named('results_per_page').using(optional=True),
-    Integer.named('edit_rows').using(optional=True),
-    Boolean.named('disabled').using(optional=True),
-    Boolean.named('want_trivial').using(optional=True),
-    Boolean.named('show_comments').using(optional=True),
-    Boolean.named('edit_on_doubleclick').using(optional=True),
-    Boolean.named('scroll_page_after_edit').using(optional=True),
-    Boolean.named('mailto_author').using(optional=True),
-    List.named('quicklinks').of(String.named('quicklinks')).using(optional=True),
-    List.named('subscribed_items').of(String.named('subscribed_item')).using(optional=True),
-    List.named('email_subscribed_events').of(String.named('email_subscribed_event')).using(optional=True),
+    String.named(keys.EMAIL).using(optional=True),
+    String.named(keys.OPENID).using(optional=True),
+    String.named(keys.ENC_PASSWORD).using(optional=True),
+    String.named(keys.RECOVERPASS_KEY).using(optional=True),
+    String.named(keys.THEME_NAME).using(optional=True),
+    String.named(keys.TIMEZONE).using(optional=True),
+    String.named(keys.LOCALE).using(optional=True),
+    String.named(keys.CSS_URL).using(optional=True),
+    Integer.named(keys.RESULTS_PER_PAGE).using(optional=True),
+    Integer.named(keys.EDIT_ROWS).using(optional=True),
+    Boolean.named(keys.DISABLED).using(optional=True),
+    Boolean.named(keys.WANT_TRIVIAL).using(optional=True),
+    Boolean.named(keys.SHOW_COMMENTS).using(optional=True),
+    Boolean.named(keys.EDIT_ON_DOUBLECLICK).using(optional=True),
+    Boolean.named(keys.SCROLL_PAGE_AFTER_EDIT).using(optional=True),
+    Boolean.named(keys.MAILTO_AUTHOR).using(optional=True),
+    List.named(keys.QUICKLINKS).of(String.named('quicklinks')).using(optional=True),
+    List.named(keys.SUBSCRIBED_ITEMS).of(String.named('subscribed_item')).using(optional=True),
+    List.named(keys.EMAIL_SUBSCRIBED_EVENTS).of(String.named('email_subscribed_event')).using(optional=True),
     #TODO: DuckDict.named('bookmarks').using(optional=True),
     *common_meta
 )

File MoinMoin/templates/blog/utils.html

View file
     {{ gen.form.open(form, id='moin-searchform', method='get', action=url_for('frontend.search')) }}
         <div>
             {{ forms.render(form['q']) }}
-            {{ gen.button(form['submit'], type='submit', id='moin-search-submit') }}
+            {{ forms.render_submit(form, id='moin-search-submit') }}
             {{ forms.render_errors(form) }}
             <br />
             <input type="checkbox" id="moin-blog-search-this"

File MoinMoin/templates/delete.html

View file
     {{ forms.render(form['comment']) }}
     {{ forms.render_textcha(gen, form) }}
   </dl>
-  {{ forms.render(form['submit']) }}
+  {{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 </div>
 {% endblock %}

File MoinMoin/templates/destroy.html

View file
         {{ forms.render(form['comment']) }}
         {{ forms.render_textcha(gen, form) }}
       </dl>
-      {{ forms.render(form['submit']) }}
+      {{ forms.render_submit(form) }}
     {{ gen.form.close() }}
     </div>
 {% else %}
         {{ forms.render(form['comment']) }}
         {{ forms.render_textcha(gen, form) }}
       </dl>
-      {{ forms.render(form['submit']) }}
+      {{ forms.render_submit(form) }}
     {{ gen.form.close() }}
     </div>
 {% endif %}

File MoinMoin/templates/forms.html

View file
   <input type="hidden" name="{{ name }}" value="{{ value }}" />
 {% endmacro %}
 
-{% macro render_button(text) %}
-  <button type="submit">{{ text }}</button>
+{% macro render_submit(form) %}
+  {{ gen.input(type='submit', value=form.submit_label, class='button', **kwargs) }}
 {% endmacro %}
 
 {% macro render_textcha(gen, form) %}
       'small_natural': small_natural,
       'datetime': datetime,
       'search': search,
-      'submit': raw_input,
       'hidden': raw_input,
       'select': select,
+      'select_submit': select_submit,
       'multi_select': multi_select,
       'readonly_string_list': readonly_string_list,
       'readonly_item_link_list': readonly_item_link_list,
   </dd>
 {% endmacro %}
 
+{% macro select_submit(field) %}
+  {% set labels = field.properties.get('labels', {}) %}
+  {% for value in field.valid_values %}
+    {{ gen.button(field, type='submit', value=value, contents=labels.get(value, value)) }}
+  {% endfor %}
+{% endmacro %}
+
 {% macro multi_select(field) %}
   {% set valid_values = field.member_schema.valid_values %}
   {% set labels = field.member_schema.properties.get('labels', {}) %}

File MoinMoin/templates/index.html

View file
                 </li>
                 {{ forms.render(form['contenttype']) }}
             </ul>
-            {{ forms.render(form['submit']) }}
+            {{ forms.render_submit(form) }}
             {{ gen.form.close() }}
         </div>
         </li>

File MoinMoin/templates/login.html

View file
   {% endif %}
 </dl>
 {{ forms.render(form['submit']) }}
+{{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 {% endif %}
 
   {{ forms.render(form['openid']) }}
 </dl>
 {{ forms.render(form['submit']) }}
+{{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 {% endif %}
 

File MoinMoin/templates/lookup.html

View file
                 {{ forms.render(lookup_form[e]) }}
             {% endfor %}
         </dl>
-        {{ gen.input(lookup_form['submit'], type='submit') }}
+        {{ forms.render_submit(lookup_form) }}
     {{ gen.form.close() }}
     {% else %}
     {% if not results %}

File MoinMoin/templates/lostpass.html

View file
     {{ forms.render(form['username']) }}
     {{ forms.render(form['email']) }}
   </dl>
-  {{ forms.render(form['submit']) }}
+  {{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 </div>
 {% endblock %}

File MoinMoin/templates/modify.html

View file
     {{ forms.render_errors(form) }}
     {#
        Workaround:
-       For *Draw content, hide form['submit'] and form['comment'], since *Draw
+       For *Draw content, hide submit button and form['comment'], since *Draw
        POSTs originate from their respective applets.
     #}
     {% if not form['content_form'].is_draw %}
-        {{ forms.render(form['submit']) }}
+        {{ forms.render_submit(form) }}
         <dl>
             {{ forms.render_textcha(gen, form) }}
             {{ forms.render(form['comment']) }}

File MoinMoin/templates/openid_register.html

View file
     {{ forms.render(form['email']) }}
     {{ forms.render_textcha(gen, form) }}
   </dl>
-  {{ forms.render(form['submit']) }}
+  {{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 </div>
 {% endblock %}

File MoinMoin/templates/recoverpass.html

View file
     {{ forms.render(form['password1']) }}
     {{ forms.render(form['password2']) }}
   </dl>
-  {{ forms.render(form['submit']) }}
+  {{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 {% endblock %}

File MoinMoin/templates/register.html

View file
     {{ forms.render(form['openid']) }}
     {{ forms.render_textcha(gen, form) }}
   </dl>
-  {{ forms.render(form['submit']) }}
+  {{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 </div>
 {% endblock %}

File MoinMoin/templates/rename.html

View file
     {{ forms.render(form['comment']) }}
     {{ forms.render_textcha(gen, form) }}
   </dl>
-  {{ forms.render(form['submit']) }}
+  {{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 </div>
 {% endblock %}

File MoinMoin/templates/revert.html

View file
     {{ forms.render(form['comment']) }}
     {{ forms.render_textcha(gen, form) }}
   </dl>
-  {{ forms.render(form['submit']) }}
+  {{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 </div>
 {% endblock %}

File MoinMoin/templates/search.html

View file
     {{ gen.form.open(medium_search_form, id='moin-long-searchform', method='get', action=url_for('frontend.search', item_name=item_name)) }}
         <p>
         {{ forms.render(medium_search_form['q']) }}
-        {{ forms.render(medium_search_form['submit']) }}
+        {{ forms.render_submit(medium_search_form) }}
         </p>
         <p>
         {{ forms.render(medium_search_form['history']) }}

File MoinMoin/templates/ticket.html

View file
         {{ forms.render(form['meta'][e]) }}
     {% endfor %}
     </dl>
-    {{ forms.render(form['submit']) }}
+    {{ forms.render_submit(form) }}
 
     <h2>Back references</h2>
     <dl>

File MoinMoin/templates/usersettings_forms.html

View file
     {{ forms.render(form['locale']) }}
 </dl>
 {{ forms.render_hidden('part', 'personal') }}
-{{ forms.render_button(_("Save")) }}
+{{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 {% endmacro %}
 
     {{ forms.render(form['password2']) }}
 </dl>
 {{ forms.render_hidden('part', 'password') }}
-{{ forms.render_button(_("Change password")) }}
+{{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 {% endmacro %}
 
     {{ forms.render(form['email']) }}
 </dl>
 {{ forms.render_hidden('part', 'notification') }}
-{{ forms.render_button(_("Save")) }}
+{{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 {% endmacro %}
 
     {{ forms.render(form['results_per_page']) }}
 </dl>
 {{ forms.render_hidden('part', 'ui') }}
-{{ forms.render_button(_("Save")) }}
+{{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 {% endmacro %}
 
     {# TODO: find a good way to handle quicklinks #}
 </dl>
 {{ forms.render_hidden('part', 'navigation') }}
-{{ forms.render_button(_("Save")) }}
+{{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 {% endmacro %}
 
     {{ forms.render(form['disabled']) }}
 </dl>
 {{ forms.render_hidden('part', 'options') }}
-{{ forms.render_button(_("Save")) }}
+{{ forms.render_submit(form) }}
 {{ gen.form.close() }}
 {% endmacro %}
 

File MoinMoin/templates/utils.html

View file
     {{ gen.form.open(form, id='moin-searchform', method='get', action=url_for('frontend.search')) }}
         <div>
             {{ forms.render(form['q']) }}
-            {{ gen.button(form['submit'], type='submit', id='moin-search-submit') }}
+            {{ forms.render_submit(form, id='moin-search-submit') }}
             {{ forms.render_errors(form) }}
         </div>
     {{ gen.form.close() }}

File MoinMoin/user.py

View file
 from MoinMoin import wikiutil
 from MoinMoin.constants.contenttypes import CONTENTTYPE_USER
 from MoinMoin.constants.keys import *
+from MoinMoin.constants.misc import ANON
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.mail import sendmail
 from MoinMoin.util.interwiki import getInterwikiHome, getInterwikiName, is_local_wiki
 space between words. Group page name is not allowed.""", name=username)
 
     # Name required to be unique. Check if name belong to another user.
-    if validate and search_users(name_exact=username):
+    if validate and search_users(**{NAME_EXACT: username}):
         return _("This user name already belongs to somebody else.")
 
     # XXX currently we just support creating with 1 name:
     """ Searches for a users with given query keys/values """
     # Since item name is a list, it's possible a list have been passed as parameter.
     # No problem, since user always have just one name (TODO: validate single name for user)
-    if q.get('name_exact') and isinstance(q.get('name_exact'), list):
-        q['name_exact'] = q['name_exact'][0]
+    if q.get(NAME_EXACT) and isinstance(q.get(NAME_EXACT), list):
+        q[NAME_EXACT] = q[NAME_EXACT][0]
     q = update_user_query(**q)
     backend = get_user_backend()
     docs = backend.documents(**q)
 
         itemid = uid
         if not itemid and auth_username:
-            users = search_users(name_exact=auth_username)
+            users = search_users(**{NAME_EXACT: auth_username})
             if users:
                 itemid = users[0].meta[ITEMID]
-        if not itemid and _name and _name != 'anonymous':
-            users = search_users(name_exact=_name)
+        if not itemid and _name and _name != ANON:
+            users = search_users(**{NAME_EXACT: _name})
             if users:
                 itemid = users[0].meta[ITEMID]
         if itemid:
             assert isinstance(names, list)
             return names[0]
         except IndexError:
-            return u'anonymous'
+            return ANON
 
     @property
     def language(self):

File setup.cfg

View file
 directory = MoinMoin/translations/
 
 [pytest]
-pep8maxlinelength = 120
+# Note: we need pytest-pep8 >= 1.0.4 to make pep8maxlinelength work, but that
+# needs a pytest >= 2.3, so, we can't use that until our test work with that.
+#pep8maxlinelength = 120
 norecursedirs = .hg _build tmp* env* dlc wiki
 minversion = 2.0
 pep8ignore =
  *.py E121 E122 E123 E124 E125 E126 E127 E128  # continuation line indentation
+ *.py E501  # maximum line length (see also pep8maxlinelength)
  */_tests/*.py E225 E226  # missing whitespace / missing optional whitespace around operator
  */_tests/*.py E261  # less than 2 blanks before inline comment
  */_tests/*.py E301 E302  # separate toplevel definitions with 2 empty lines, method defs inside class by 1 empty line
  */_tests/*.py E401  # imports on separate lines
- */_tests/*.py E501  # maximum line length
  wikiconfig_*.py ALL  # local stuff, not in the repo
  MoinMoin/config/default.py E501  # maximum line length (long lines expected there)
  MoinMoin/util/_tests/test_paramparser.py E241 # whitespace around comma (we have some "tabular" formatting there)