Commits

sislau committed 7e2a29e Merge

merge

Comments (0)

Files changed (9)

MoinMoin/apps/admin/views.py

 
 @admin.route('/')
 def index():
-    return render_template('admin/index.html', item_name="+admin")
+    return render_template('admin/index.html', title_name=_(u"Admin"))
 
 
 @admin.route('/userbrowser')
                           groups=[groupname for groupname in groups if doc[NAME] in groups[groupname]],
                      )
                      for doc in docs]
-    return render_template('admin/userbrowser.html', user_accounts=user_accounts, item_name="+admin/Userbrowser")
+    return render_template('admin/userbrowser.html', user_accounts=user_accounts, title_name=_(u"User Browser"))
 
 
 @admin.route('/userprofile/<user_name>', methods=['GET', 'POST', ])
         action = 'syspages_upgrade'
         label = 'Upgrade System Pages'
         return render_template('admin/sysitems_upgrade.html',
-                               item_name="+admin/System items upgrade")
+                               title_name=_(u"System items upgrade"))
     if request.method == 'POST':
         xmlfile = request.files.get('xmlfile')
         try:
 
     found.sort()
     return render_template('admin/wikiconfig.html',
-                           item_name="+admin/wikiconfig",
+                           title_name=_(u"Wiki Configuration"),
                            found=found, settings=settings)
 
 
         groups.append((heading, desc, opts))
     groups.sort()
     return render_template('admin/wikiconfighelp.html',
-                           item_name="+admin/wikiconfighelp",
+                           title_name=_(u"Wiki Configuration Help"),
                            groups=groups)
 
 
     rows = sorted([[desc, ' '.join(names), ' '.join(patterns), ' '.join(mimetypes), ]
                    for desc, names, patterns, mimetypes in lexers])
     return render_template('admin/highlighterhelp.html',
-                           item_name="+admin/highlighterhelp",
+                           title_name=_(u"Highlighter Help"),
                            headings=headings,
                            rows=rows)
 
                ]
     rows = sorted(app.cfg.interwiki_map.items())
     return render_template('admin/interwikihelp.html',
-                           item_name="+admin/interwikihelp",
+                           title_name=_(u"Interwiki Help"),
                            headings=headings,
                            rows=rows)
 
             for rev in flaskg.storage.documents(wikiname=app.cfg.interwikiname)]
     rows = sorted(rows, reverse=True)
     return render_template('admin/itemsize.html',
-                           item_name="+admin/itemsize",
+                           title_name=_(u"Item Size"),
                            headings=headings,
                            rows=rows)
 

MoinMoin/apps/frontend/views.py

 
 @frontend.route('/+search', methods=['GET', 'POST'])
 def search():
+    title_name = _("Search")
     search_form = SearchForm.from_flat(request.values)
     valid = search_form.validate()
     search_form['submit'].set_default() # XXX from_flat() kills all values
                                    content_suggestions=content_suggestions,
                                    query=query,
                                    medium_search_form=search_form,
-                                   item_name='+search', # XXX
+                                   title_name=title_name,
                                   )
             flaskg.clock.stop('search render')
     else:
         html = render_template('search.html',
                                query=query,
                                medium_search_form=search_form,
-                               item_name='+search', # XXX
+                               title_name=title_name,
                               )
     return html
 
     detailed_index = sorted(detailed_index, key=lambda name: name[0].lower())
 
     item_names = item_name.split(u'/')
+    if item_name:
+        args = dict(item_name=item_name)
+    else:
+        args = dict(item_name=u'', title_name=_(u'Global Index'))
     return render_template(item.index_template,
-                           item_name=item_name,
                            item_names=item_names,
                            index=detailed_index,
                            initials=initials,
                            startswith=startswith,
                            contenttype_groups=ct_groups,
                            form=form,
+                           **args
                           )
 
 
     """
     my_changes = _mychanges(flaskg.user.itemid)
     return render_template('item_link_list.html',
-                           item_name='+mychanges', # XXX
+                           title_name=_(u'My Changes'),
                            headline=_(u'My Changes'),
                            item_names=my_changes
                           )
         history.append(dh)
     del history[0]  # kill the dummy
 
-    item_name = request.values.get('item_name', '') # actions menu puts it into qs
+    title_name = _(u'Global History')
     current_timestamp = int(time.time())
     return render_template('global_history.html',
-                           item_name=item_name, # XXX no item
+                           title_name=title_name,
                            history=history,
                            current_timestamp=current_timestamp,
                            bookmark_time=bookmark_time,
     existing, linked, transcluded = _compute_item_sets()
     referred = linked | transcluded
     wanteds = referred - existing
-    item_name = request.values.get('item_name', '') # actions menu puts it into qs
+    title_name = _(u'Wanted Items')
     return render_template('item_link_list.html',
                            headline=_(u'Wanted Items'),
-                           item_name=item_name,
+                           title_name=title_name,
                            item_names=wanteds)
 
 
     existing, linked, transcluded = _compute_item_sets()
     referred = linked | transcluded
     orphans = existing - referred
-    item_name = request.values.get('item_name', '') # actions menu puts it into qs
+    title_name = _('Orphaned Items')
     return render_template('item_link_list.html',
-                           item_name=item_name,
+                           title_name=title_name,
                            headline=_(u'Orphaned Items'),
                            item_names=orphans)
 
 
 @frontend.route('/+register', methods=['GET', 'POST'])
 def register():
-    item_name = 'Register' # XXX
+    title_name = _(u'Register')
     # is openid_submit in the form?
     isOpenID = 'openid_submit' in request.values
 
                     return redirect(url_for('.show_root'))
 
     return render_template(template,
-                           item_name=item_name,
+                           title_name=title_name,
                            form=form,
                           )
 
 @frontend.route('/+lostpass', methods=['GET', 'POST'])
 def lostpass():
     # TODO use ?next=next_location check if target is in the wiki and not outside domain
-    item_name = 'LostPass' # XXX
+    title_name = _(u'Lost Password')
 
     if not _using_moin_auth():
         return Response('No MoinAuth in auth list', 403)
             flash(_("If this account exists, you will be notified."), "info")
             return redirect(url_for('.show_root'))
     return render_template('lostpass.html',
-                           item_name=item_name,
+                           title_name=title_name,
                            form=form,
                           )
 
 @frontend.route('/+recoverpass', methods=['GET', 'POST'])
 def recoverpass():
     # TODO use ?next=next_location check if target is in the wiki and not outside domain
-    item_name = 'RecoverPass' # XXX
+    title_name = _(u'Recover Password')
 
     if not _using_moin_auth():
         return Response('No MoinAuth in auth list', 403)
                 flash(_('Your token is invalid!'), "error")
             return redirect(url_for('.show_root'))
     return render_template('recoverpass.html',
-                           item_name=item_name,
+                           title_name=title_name,
                            form=form,
                           )
 
 @frontend.route('/+login', methods=['GET', 'POST'])
 def login():
     # TODO use ?next=next_location check if target is in the wiki and not outside domain
-    item_name = 'Login' # XXX
+    title_name = _(u'Login')
 
     # multistage return
     if flaskg._login_multistage_name == 'openid':
         for msg in flaskg._login_messages:
             flash(msg, "error")
     return render_template('login.html',
-                           item_name=item_name,
+                           title_name=title_name,
                            login_inputs=app.cfg.auth_login_inputs,
                            form=form,
                           )
 @frontend.route('/+usersettings/<part>', methods=['GET', 'POST'])
 def usersettings(part):
     # TODO use ?next=next_location check if target is in the wiki and not outside domain
-    item_name = 'User Settings' # XXX
+    title_name = _('User Settings')
 
     # these forms can't be global because we need app object, which is only available within a request:
     class UserSettingsPersonalForm(Form):
         # 'main' part or some invalid part
         return render_template('usersettings.html',
                                part='main',
-                               item_name=item_name,
+                               title_name=title_name,
                               )
     if request.method == 'GET':
         form = FormClass.from_object(flaskg.user)
                     form = FormClass.from_object(flaskg.user)
                     form['submit'].set_default() # XXX from_object() kills all values
     return render_template('usersettings.html',
-                           item_name=item_name,
+                           title_name=title_name,
                            part=part,
                            form=form,
                           )
     """
     show a list or tag cloud of all tags in this wiki
     """
-    item_name = request.values.get('item_name', '') # actions menu puts it into qs
+    title_name = _(u'All tags in this wiki')
     revs = flaskg.storage.documents(wikiname=app.cfg.interwikiname)
     tags_counts = {}
     for rev in revs:
         tags = []
     return render_template("global_tags.html",
                            headline=_("All tags in this wiki"),
-                           item_name=item_name,
+                           title_name=title_name,
                            tags=tags)
 
 
 def page_not_found(e):
     return render_template('404.html',
                            item_name=e.description), 404
+

MoinMoin/converter/_tests/test_include.py

 import pytest
 
 from MoinMoin.converter.include import *
+from MoinMoin.items import MoinWiki
+from MoinMoin.config import CONTENTTYPE
+from MoinMoin._tests import wikiconfig, update_item
 
-def test_XPointer():
-    x = XPointer('a')
-    assert len(x) == 1
-    e = x[0]
-    assert e.name == 'a'
-    assert e.data is None
+class TestInclude(object):
+    class Config(wikiconfig.Config):
+        """
+        we just have this so the test framework creates a new app with empty backends for us.
+        """
 
-    x = XPointer('a(b)')
-    assert len(x) == 1
-    e = x[0]
-    assert e.name == 'a'
-    assert e.data == 'b'
+    def test_XPointer(self):
+        x = XPointer('a')
+        assert len(x) == 1
+        e = x[0]
+        assert e.name == 'a'
+        assert e.data is None
 
-    x = XPointer('a(^(b^)^^)')
-    assert len(x) == 1
-    e = x[0]
-    assert e.name == 'a'
-    assert e.data == '^(b^)^^'
-    assert e.data_unescape == '(b)^'
+        x = XPointer('a(b)')
+        assert len(x) == 1
+        e = x[0]
+        assert e.name == 'a'
+        assert e.data == 'b'
 
-    x = XPointer('a(b)c(d)')
-    assert len(x) == 2
-    e = x[0]
-    assert e.name == 'a'
-    assert e.data == 'b'
-    e = x[1]
-    assert e.name == 'c'
-    assert e.data == 'd'
+        x = XPointer('a(^(b^)^^)')
+        assert len(x) == 1
+        e = x[0]
+        assert e.name == 'a'
+        assert e.data == '^(b^)^^'
+        assert e.data_unescape == '(b)^'
 
-    x = XPointer('a(b) c(d)')
-    assert len(x) == 2
-    e = x[0]
-    assert e.name == 'a'
-    assert e.data == 'b'
-    e = x[1]
-    assert e.name == 'c'
-    assert e.data == 'd'
+        x = XPointer('a(b)c(d)')
+        assert len(x) == 2
+        e = x[0]
+        assert e.name == 'a'
+        assert e.data == 'b'
+        e = x[1]
+        assert e.name == 'c'
+        assert e.data == 'd'
 
-    x = XPointer('a(a(b))')
-    assert len(x) == 1
-    e = x[0]
-    assert e.name == 'a'
-    assert e.data == 'a(b)'
+        x = XPointer('a(b) c(d)')
+        assert len(x) == 2
+        e = x[0]
+        assert e.name == 'a'
+        assert e.data == 'b'
+        e = x[1]
+        assert e.name == 'c'
+        assert e.data == 'd'
 
+        x = XPointer('a(a(b))')
+        assert len(x) == 1
+        e = x[0]
+        assert e.name == 'a'
+        assert e.data == 'a(b)'
+
+    def test_IncludeHandlesCircularRecursion(self):
+        # issue #80
+        # we choosed MoinWiki items so tests get simpler
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page2}}')
+        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page3}}')
+        update_item(u'page3', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page1}}')
+
+        page1 = MoinWiki.create(u'page1')
+
+        page1._render_data()

MoinMoin/storage/backends/_tests/test_fileserver.py

 
 import pytest
 
-from MoinMoin.config import MTIME
+from MoinMoin.config import NAME, MTIME, REVID, ITEMID, HASH_ALGORITHM
 from ..fileserver import Backend
 from . import BackendTestBase
 
                 pass
             with open(fn, 'wb') as f:
                 f.write(data)
+            meta[NAME] = name
             meta = tuple(sorted(meta.items()))
             expected_result.add((meta, data))
         return expected_result
 
+    def test_iter(self):
+        # for the fileserver store, even if the directory is empty,
+        # we will get a revid for the root directory:
+        contents = list(self.be)
+        assert len(contents) == 1
+        root_revid = contents[0]
+        # revids are like relpath.mtime
+        relpath, mtime = root_revid.split('.')
+        assert relpath == ''
+
     def test_files(self):
         # note: as we can only store the data into the file system, meta can
         # only have items that are generated by the fileserver backend:
         items = [#name,  meta,   data
-                 ('foo.png', dict(size=11, contenttype='image/png'), 'png content'),
-                 ('bar.txt', dict(size=12, contenttype='text/plain'), 'text content'),
+                 (u'foo.png', dict(size=11, contenttype=u'image/png'), 'png content'),
+                 (u'bar.txt', dict(size=12, contenttype=u'text/plain'), 'text content'),
                 ]
         expected_result = self._prepare(items)
-        result = set()
-        for i in self.be:
-            meta, data = self.be.retrieve(i)
-            # we don't want to check mtime
-            del meta[MTIME]
-            meta = tuple(sorted(meta.items()))
-            data = data.read()
-            result.add((meta, data))
-        assert result == expected_result
-
-    def test_dir(self):
-        # note: as we can only store the data into the file system, meta can
-        # only have items that are generated by the fileserver backend:
-        items = [#name,  meta,   data
-                 ('dir/foo.png', dict(size=11, contenttype='image/png'), 'png content'),
-                 ('dir/bar.txt', dict(size=12, contenttype='text/plain'), 'text content'),
-                ]
-        expected_result = self._prepare(items)
-        dir_meta = tuple(sorted(dict(size=0, contenttype='text/x.moin.wiki;charset=utf-8').items()))
+        dir_meta = tuple(sorted(dict(name=u'', size=0, contenttype=u'text/x.moin.wiki;charset=utf-8').items()))
         dir_data = """\
 = Directory contents =
  * [[../]]
         result = set()
         for i in self.be:
             meta, data = self.be.retrieve(i)
-            # we don't want to check mtime
+            # we don't want to check some meta values
             del meta[MTIME]
+            del meta[HASH_ALGORITHM]
+            del meta[ITEMID]
+            del meta[REVID]
             meta = tuple(sorted(meta.items()))
             data = data.read()
             result.add((meta, data))
         assert result == expected_result
 
+    def test_dir(self):
+        # note: as we can only store the data into the file system, meta can
+        # only have items that are generated by the fileserver backend:
+        items = [#name,  meta,   data
+                 (u'dir/foo.png', dict(size=11, contenttype=u'image/png'), 'png content'),
+                 (u'dir/bar.txt', dict(size=12, contenttype=u'text/plain'), 'text content'),
+                ]
+        expected_result = self._prepare(items)
+        dir_meta = tuple(sorted(dict(name=u'', size=0, contenttype=u'text/x.moin.wiki;charset=utf-8').items()))
+        dir_data = """\
+= Directory contents =
+ * [[../]]
+ * [[/dir|dir/]]
+""".replace('\n', '\r\n')
+        expected_result.add((dir_meta, dir_data))
+        dir_meta = tuple(sorted(dict(name=u'dir', size=0, contenttype=u'text/x.moin.wiki;charset=utf-8').items()))
+        dir_data = """\
+= Directory contents =
+ * [[../]]
+ * [[/bar.txt|bar.txt]]
+ * [[/foo.png|foo.png]]
+""".replace('\n', '\r\n')
+        expected_result.add((dir_meta, dir_data))
+        result = set()
+        for i in self.be:
+            meta, data = self.be.retrieve(i)
+            # we don't want to check some meta values
+            del meta[MTIME]
+            del meta[HASH_ALGORITHM]
+            del meta[ITEMID]
+            del meta[REVID]
+            meta = tuple(sorted(meta.items()))
+            data = data.read()
+            result.add((meta, data))
+        assert result == expected_result
 

MoinMoin/storage/backends/fileserver.py

 import errno
 import stat
 from StringIO import StringIO
+from werkzeug import url_quote, url_unquote
 
-from MoinMoin.config import MTIME, SIZE, CONTENTTYPE
+from MoinMoin.config import NAME, ITEMID, REVID, MTIME, SIZE, CONTENTTYPE, HASH_ALGORITHM
 from . import BackendBase
 
 from MoinMoin.util.mimetype import MimeType
         pass
 
     def _mkpath(self, key):
+        """
+        key -> rel path, absolute path (strip mtime)
+        """
         # XXX unsafe keys?
-        return os.path.join(self.path, key)
+        try:
+            relpath, mtime = key.rsplit('.', 1)
+        except ValueError:
+            # we only generate revids that look like path.mtime,
+            # so if the split does not work, the revid is invalid
+            # and we raise KeyError like if the rev is not there
+            raise KeyError(key)
+        return relpath, os.path.join(self.path, relpath)
 
     def _mkkey(self, path):
+        """
+        absolute path -> relpath, mtime
+        """
+        st = os.stat(path)
         root = self.path
         assert path.startswith(root)
-        key = path[len(root)+1:]
-        return key
+        return path[len(root)+1:], int(st.st_mtime)
 
-    def __iter__(self):
-        # note: instead of just yielding the relative <path>, yield <path>/<mtime>,
-        # so if the file is updated, the revid will change (and the indexer's
-        # update() method can efficiently update the index).
-        for dirpath, dirnames, filenames in os.walk(self.path):
-            key = self._mkkey(dirpath)
-            if key:
-                yield key
-            for filename in filenames:
-                yield self._mkkey(os.path.join(dirpath, filename))
+    def _encode(self, key):
+        """
+        we need to get rid of slashes in revids because we put them into URLs
+        and it would confuse the URL routing.
+        """
+        return url_quote(key, safe='')
 
-    def _get_meta(self, fn):
-        path = self._mkpath(fn)
+    def _decode(self, qkey):
+        return url_unquote(qkey)
+
+    def _get_meta(self, fn, path):
         try:
             st = os.stat(path)
         except OSError as e:
                 raise KeyError(fn)
             raise
         meta = {}
+        meta[NAME] = fn
         meta[MTIME] = int(st.st_mtime) # use int, not float
+        meta[REVID] = unicode(self._encode('%s.%d' % (meta[NAME], meta[MTIME])))
+        meta[ITEMID] = meta[REVID]
+        meta[HASH_ALGORITHM] = u'' # XXX crap, but sendfile needs it for etag
         if stat.S_ISDIR(st.st_mode):
             # directory
             # we create a virtual wiki page listing links to subitems:
-            ct = 'text/x.moin.wiki;charset=utf-8'
+            ct = u'text/x.moin.wiki;charset=utf-8'
             size = 0
         elif stat.S_ISREG(st.st_mode):
             # normal file
-            ct = MimeType(filename=fn).content_type()
+            ct = unicode(MimeType(filename=fn).content_type())
             size = int(st.st_size) # use int instead of long
         else:
             # symlink, device file, etc.
-            ct = 'application/octet-stream'
+            ct = u'application/octet-stream'
             size = 0
         meta[CONTENTTYPE] = ct
         meta[SIZE] = size
             content = unicode(err)
         return content
 
-    def _get_data(self, fn):
-        path = self._mkpath(fn)
+    def _get_data(self, fn, path):
         try:
             st = os.stat(path)
             if stat.S_ISDIR(st.st_mode):
                 raise KeyError(fn)
             raise
 
-    def retrieve(self, fn):
-        meta = self._get_meta(fn)
-        data = self._get_data(fn)
+    def __iter__(self):
+        # note: instead of just yielding the relative <path>, yield <path>.<mtime>,
+        # so if the file is updated, the revid will change (and the indexer's
+        # update() method can efficiently update the index).
+        for dirpath, dirnames, filenames in os.walk(self.path):
+            key, mtime = self._mkkey(dirpath)
+            if 1: # key:
+                yield self._encode('%s.%d' % (key, mtime))
+            for filename in filenames:
+                yield self._encode('%s.%d' % self._mkkey(os.path.join(dirpath, filename)))
+
+    def retrieve(self, key):
+        key = self._decode(key)
+        fn, path = self._mkpath(key)
+        meta = self._get_meta(fn, path)
+        data = self._get_data(fn, path)
         return meta, data
 

MoinMoin/storage/middleware/indexing.py

 from whoosh.index import open_dir, create_in, EmptyIndexError
 from whoosh.writing import AsyncWriter
 from whoosh.filedb.multiproc import MultiSegmentWriter
-from whoosh.qparser import QueryParser, MultifieldParser, RegexPlugin
+from whoosh.qparser import QueryParser, MultifieldParser, RegexPlugin, \
+                           PseudoFieldPlugin
+from whoosh.qparser import WordNode
 from whoosh.query import Every, Term
 from whoosh.sorting import FieldFacet
 
                             CONTENT, ITEMLINKS, ITEMTRANSCLUSIONS, ACL, EMAIL, OPENID, \
                             ITEMID, REVID, CURRENT, PARENTID, \
                             LATEST_REVS, ALL_REVS
-
+from MoinMoin import user
 from MoinMoin.search.analyzers import item_name_analyzer, MimeTokenizer, AclTokenizer
 from MoinMoin.themes import utctimestamp
 from MoinMoin.util.crypto import make_uuid
             raise ValueError("default_fields list must at least contain one field name")
         # TODO before using the RegexPlugin, require a whoosh release that fixes whoosh issues #205 and #206
         #qp.add_plugin(RegexPlugin())
+        def username_pseudo_field(node):
+            username = node.text
+            users = user.search_users(**{NAME_EXACT: username})
+            if users:
+                userid = users[0].meta['userid']
+                node = WordNode(userid)
+                node.set_fieldname("userid")
+                return node
+            return node
+        qp.add_plugin(PseudoFieldPlugin({'username': username_pseudo_field}))
         return qp
 
     def search(self, q, idx_name=LATEST_REVS, **kw):

MoinMoin/templates/error.html

 {% extends theme("layout.html") %}
 {% block content %}
 <h1>{{ title }}</h1>
-{{ description }}
+<p>{{ description }}</p>
 {% endblock %}
 

MoinMoin/templates/layout.html

         <span id="moin-pagelocation">
             <span class="moin-pagepath">
                 {% for segment_name, segment_path, exists in theme_supp.location_breadcrumbs(item_name) -%}
-                    <a href="{{ url_for('frontend.show_item', item_name=segment_path) }}" {% if not exists %}class="moin-nonexistent"{% endif %}>
-                        {{ segment_name|shorten_item_name }}
-                    </a>
-                    {% if not loop.last -%}<span class="sep">/</span>{%- endif %}
+                    {% if not loop.last -%}
+                        <a href="{{ url_for('frontend.show_item', item_name=segment_path) }}" {% if not exists %}class="moin-nonexistent"{% endif %}>
+                            {{ segment_name|shorten_item_name }}
+                        </a>
+			<span class="sep">/</span>
+                    {% else %}
+		        {% if title_name %}
+                            {{ title_name }}
+                        {% else %}
+                        <a href="{{ url_for('frontend.show_item', item_name=segment_path) }}" {% if not exists %}class="moin-nonexistent"{% endif %}>
+                            {{ segment_name|shorten_item_name }}
+                        </a>
+		        {%- endif %}
+                    {%- endif %}
                 {%- endfor %}
             </span>
         </span>

MoinMoin/templates/usersettings.html

 
 {% block item %}
 {% if part == 'main' %}
-<h1>{{ _("Settings") }}</h1>
+<h1>{{ _("User Settings") }}</h1>
 <ul>
     <li><a href="{{ url_for('frontend.usersettings', part='personal') }}">{{ _("Personal Settings") }}</a></li>
     <li><a href="{{ url_for('frontend.usersettings', part='password') }}">{{ _("Change password") }}</a></li>