changeset 1004:9fd0a6a988bb

merge
author Bruno Martin <bruno@hacklab.com.br>
date Sat, 29 Oct 2011 01:43:06 -0200
parents 8621c3e273a1 (current diff) d4c6cb532f72 (diff)
children c36f61f85e97
files MoinMoin/items/__init__.py
diffstat 19 files changed, 196 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/apps/admin/views.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/apps/admin/views.py	Sat Oct 29 01:43:06 2011 -0200
@@ -226,8 +226,8 @@
     headings = [_('Size'),
                 _('Item name'),
                ]
-    rows = [(doc[SIZE], doc[NAME])
-            for doc in flaskg.storage.documents(wikiname=app.cfg.interwikiname)]
+    rows = [(rev.meta[SIZE], rev.meta[NAME])
+            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",
--- a/MoinMoin/apps/feed/_tests/test_feed.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/apps/feed/_tests/test_feed.py	Sat Oct 29 01:43:06 2011 -0200
@@ -7,6 +7,9 @@
 
 from MoinMoin._tests import wikiconfig
 
+from MoinMoin.items import Item
+from MoinMoin.config import CONTENTTYPE, COMMENT
+from MoinMoin._tests import update_item
 
 class TestFeeds(object):
     class Config(wikiconfig.Config):
@@ -23,3 +26,22 @@
             assert '<feed xmlns="http://www.w3.org/2005/Atom">' in rv.data
             assert '</feed>' in rv.data
 
+    def test_global_atom_with_an_item(self):
+        basename = u'Foo'
+        item = update_item(basename, {COMMENT: u"foo data for feed item"}, '')
+        with self.app.test_client() as c:
+            rv = c.get('/+feed/atom')
+            assert rv.status == '200 OK'
+            assert rv.headers['Content-Type'] == 'application/atom+xml'
+            assert rv.data.startswith('<?xml')
+            assert "foo data for feed item" in rv.data
+
+        # tests the cache invalidation
+        update_item(basename, {COMMENT: u"checking if the cache invalidation works"}, '')
+        with self.app.test_client() as c:
+            rv = c.get('/+feed/atom')
+            assert rv.status == '200 OK'
+            assert rv.headers['Content-Type'] == 'application/atom+xml'
+            assert rv.data.startswith('<?xml')
+            assert "checking if the cache invalidation works" in rv.data
+
--- a/MoinMoin/apps/feed/views.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/apps/feed/views.py	Sat Oct 29 01:43:06 2011 -0200
@@ -25,13 +25,14 @@
 from MoinMoin import wikiutil
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.apps.feed import feed
-from MoinMoin.config import NAME, NAME_EXACT, WIKINAME, ACL, ACTION, ADDRESS, HOSTNAME, USERID, COMMENT, MTIME, REVID, ALL_REVS
+from MoinMoin.config import (NAME, NAME_EXACT, WIKINAME, ACL, ACTION, ADDRESS,
+                            HOSTNAME, USERID, COMMENT, MTIME, REVID, ALL_REVS,
+                            PARENTID, LATEST_REVS)
 from MoinMoin.themes import get_editor_info
 from MoinMoin.items import Item
 from MoinMoin.util.crypto import cache_key
 from MoinMoin.util.interwiki import url_for_item
 
-
 @feed.route('/atom/<itemname:item_name>')
 @feed.route('/atom', defaults=dict(item_name=''))
 def atom(item_name):
@@ -40,8 +41,17 @@
     # - full item in html is nice
     # - diffs in textmode are OK, but look very simple
     # - full-item content in textmode is OK, but looks very simple
-    cid = cache_key(usage="atom", item_name=item_name)
-    content = app.cache.get(cid)
+    query = Term(WIKINAME, app.cfg.interwikiname)
+    if item_name:
+        query = And([query, Term(NAME_EXACT, item_name), ])
+    revs = list(flaskg.storage.search(query, idx_name=LATEST_REVS, sortedby=[MTIME], reverse=True, limit=1))
+    if revs:
+        rev = revs[0]
+        cid = cache_key(usage="atom", revid=rev.revid, item_name=item_name)
+        content = app.cache.get(cid)
+    else:
+        content = None
+        cid = None
     if content is None:
         title = app.cfg.sitename
         feed = AtomFeed(title=title, feed_url=request.url, url=request.host_url)
@@ -75,9 +85,10 @@
                      content=content, content_type=content_type,
                      author=get_editor_info(rev.meta, external=True),
                      url=url_for_item(name, rev=this_revid, _external=True),
-                     updated=rev.meta[MTIME],
+                     updated=datetime.fromtimestamp(rev.meta[MTIME]),
                     )
         content = feed.to_string()
-        app.cache.set(cid, content)
+        if cid is not None:
+            app.cache.set(cid, content)
     return Response(content, content_type='application/atom+xml')
 
--- a/MoinMoin/converter/include.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/converter/include.py	Sat Oct 29 01:43:06 2011 -0200
@@ -26,7 +26,7 @@
 from MoinMoin import wikiutil
 from MoinMoin.items import Item
 from MoinMoin.util.mime import type_moin_document
-from MoinMoin.util.iri import Iri
+from MoinMoin.util.iri import Iri, IriPath
 from MoinMoin.util.tree import html, moin_page, xinclude, xlink
 
 from MoinMoin.converter.html_out import wrap_object_with_overlay
@@ -220,9 +220,13 @@
 
                 included_elements = []
                 for page, page_href in pages:
+                    if page_href.path[0] != '/':
+                        page_href.path = IriPath('/' + '/'.join(page_href.path))
                     if page_href in self.stack:
-                        w = ('<p xmlns="{0}"><strong class="error">Recursive include of "{1}" forbidden</strong></p>'.format(html.namespace, page.name))
-                        div.append(ET.XML(w))
+                        attrib = {getattr(html, 'class'): 'error'}
+                        msg = 'Recursive include of "{0}" forbidden'.format(page.name)
+                        strong = ET.Element(html.strong, attrib, (msg, ))
+                        included_elements.append(strong)
                         continue
                     # TODO: Is this correct?
                     if not flaskg.user.may.read(page.name):
@@ -234,7 +238,7 @@
                         elem_a = ET.Element(self.tag_a, attrib, children=children)
                         attrib = {self.tag_outline_level: xp_include_level or '1'}
                         elem_h = ET.Element(self.tag_h, attrib, children=(elem_a, ))
-                        div.append(elem_h)
+                        included_elements.append(elem_h)
 
                     page_doc = page.internal_representation()
                     # page_doc.tag = self.tag_div # XXX why did we have this?
--- a/MoinMoin/items/__init__.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/items/__init__.py	Sat Oct 29 01:43:06 2011 -0200
@@ -525,13 +525,34 @@
                  for rev in revs]
         return items
 
+    def _connect_levels(self, index):
+        new_index = []
+        last = self.name
+        for item in index:
+            name = item[0]
+
+            while not name.startswith(last):
+                last = last.rpartition('/')[0]
+
+            missing_layers = name.split('/')[last.count('/')+1:-1]
+
+            for layer in missing_layers:
+                last = '/'.join([last, layer])
+                new_index.append((last, last[len(self.name)+1:], u'application/x-nonexistent'))
+
+            last = item[0]
+            new_index.append(item)
+
+        return new_index
+
     def flat_index(self, startswith=None, selected_groups=None):
         """
-        creates an top level index of sub items of this item
+        creates a top level index of sub items of this item
         if startswith is set, filtering is done on the basis of starting letter of item name
         if selected_groups is set, items whose contentype belonging to the selected contenttype_groups, are filtered.
         """
         index = self.get_index()
+        index = self._connect_levels(index)
 
         all_ctypes = [[ctype for ctype, clabel in contenttypes]
                       for gname, contenttypes in CONTENTTYPE_GROUPS]
--- a/MoinMoin/items/_tests/test_Item.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/items/_tests/test_Item.py	Sat Oct 29 01:43:06 2011 -0200
@@ -94,6 +94,7 @@
                         ]
         flat_index = baseitem.flat_index()
         assert flat_index == [(u'Foo/ab', u'ab', u'text/plain;charset=utf-8'),
+                              (u'Foo/cd', u'cd', u'application/x-nonexistent'),
                               (u'Foo/gh', u'gh', u'text/plain;charset=utf-8'),
                               (u'Foo/ij', u'ij', u'text/plain;charset=utf-8'),
                               (u'Foo/mn', u'mn', u'image/jpeg'),
@@ -101,17 +102,52 @@
         # check index when startswith param is passed
         flat_index = baseitem.flat_index(startswith=u'a')
         assert flat_index == [(u'Foo/ab', u'ab', 'text/plain;charset=utf-8')]
+
+        #check that virtual container item is returned with startswith
+        flat_index = baseitem.flat_index(startswith=u'c')
+        assert flat_index == [(u'Foo/cd', u'cd', u'application/x-nonexistent')]
+
         # check index when contenttype_groups is passed
         ctgroups = ["image items"]
         flat_index = baseitem.flat_index(selected_groups=ctgroups)
         assert flat_index == [(u'Foo/mn', u'mn', 'image/jpeg')]
+
+        # If we ask for text/plain type, should Foo/cd be returned?
+
+        # check detailed index
         detailed_index = baseitem.get_detailed_index(baseitem.flat_index())
         assert detailed_index == [(u'Foo/ab', u'ab', 'text/plain;charset=utf-8', False),
+                                  (u'Foo/cd', u'cd', 'application/x-nonexistent', True),
                                   (u'Foo/gh', u'gh', 'text/plain;charset=utf-8', False),
                                   (u'Foo/ij', u'ij', 'text/plain;charset=utf-8', True),
                                   (u'Foo/mn', u'mn', 'image/jpeg', False),
                                   ]
 
+    def testIndexOnDisconnectedLevels(self):
+        # create a toplevel and some sub-items
+        basename = u'Bar'
+        for name in ['', '/ab', '/ab/cd/ef/gh', '/ij/kl/mn/op', '/ij/kl/rs']:
+            item = Item.create(basename + name)
+            item._save({CONTENTTYPE: u'text/plain;charset=utf-8'}, "foo")
+
+        baseitem = Item.create(basename)
+        index = baseitem.get_index()
+        index = baseitem._connect_levels(index)
+
+        assert index == [(u'Bar/ab', u'ab', u'text/plain;charset=utf-8'),
+                         (u'Bar/ab/cd', u'ab/cd', u'application/x-nonexistent'),
+                         (u'Bar/ab/cd/ef', u'ab/cd/ef', u'application/x-nonexistent'),
+                         (u'Bar/ab/cd/ef/gh', u'ab/cd/ef/gh', u'text/plain;charset=utf-8'),
+                         (u'Bar/ij', u'ij', u'application/x-nonexistent'),
+                         (u'Bar/ij/kl', u'ij/kl', u'application/x-nonexistent'),
+                         (u'Bar/ij/kl/mn', u'ij/kl/mn', u'application/x-nonexistent'),
+                         (u'Bar/ij/kl/mn/op', u'ij/kl/mn/op', u'text/plain;charset=utf-8'),
+                         (u'Bar/ij/kl/rs', u'ij/kl/rs', u'text/plain;charset=utf-8')]
+
+        flat_index = baseitem.flat_index()
+        assert flat_index == [(u'Bar/ab', u'ab', u'text/plain;charset=utf-8'),
+                              (u'Bar/ij', u'ij', u'application/x-nonexistent'),
+                             ]
 
     def test_meta_filter(self):
         name = u'Test_item'
--- a/MoinMoin/storage/__init__.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/storage/__init__.py	Sat Oct 29 01:43:06 2011 -0200
@@ -41,7 +41,7 @@
 
 def create_mapping(uri, mounts, acls):
     namespace_mapping = [(mounts[nsname],
-                          backend_from_uri(uri % dict(nsname=nsname)))
+                          backend_from_uri(uri % dict(nsname=nsname, kind="%(kind)s")))
                          for nsname in mounts]
     acl_mapping = acls.items()
     # we need the longest mountpoints first, shortest last (-> '' is very last)
--- a/MoinMoin/storage/stores/_tests/test_fs.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/storage/stores/_tests/test_fs.py	Sat Oct 29 01:43:06 2011 -0200
@@ -34,3 +34,8 @@
     store.destroy()
     assert not target.check()
 
+
+@pytest.mark.multi(Store=[BytesStore, FileStore])
+def test_from_uri(tmpdir, Store):
+    store = Store.from_uri("%s" % tmpdir)
+    assert store.path == tmpdir
--- a/MoinMoin/storage/stores/_tests/test_kt.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/storage/stores/_tests/test_kt.py	Sat Oct 29 01:43:06 2011 -0200
@@ -35,3 +35,13 @@
     store = Store()
     store.destroy()
 
+@pytest.mark.multi(Store=[BytesStore, FileStore])
+def test_from_uri(Store):
+    store = Store.from_uri("localhost")
+    assert store.host == 'localhost'
+    assert store.port == 1978
+
+    store = Store.from_uri("localhost:1970")
+    assert store.host == 'localhost'
+    assert store.port == 1970
+
--- a/MoinMoin/storage/stores/_tests/test_memory.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/storage/stores/_tests/test_memory.py	Sat Oct 29 01:43:06 2011 -0200
@@ -29,3 +29,7 @@
     store.destroy()
     assert store._st is None
 
+@pytest.mark.multi(Store=[BytesStore, FileStore])
+def test_from_uri(Store):
+    store = Store.from_uri("mem://")
+    assert isinstance(store, Store)
--- a/MoinMoin/storage/stores/_tests/test_sqla.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/storage/stores/_tests/test_sqla.py	Sat Oct 29 01:43:06 2011 -0200
@@ -8,8 +8,7 @@
 
 
 import pytest
-
-pytest.importorskip('storage.stores.sqla')
+pytest.importorskip('MoinMoin.storage.stores.sqla')
 from ..sqla import BytesStore, FileStore
 
 @pytest.mark.multi(Store=[BytesStore, FileStore])
@@ -29,3 +28,8 @@
     store.destroy()
     # XXX: check for dropped table
 
+@pytest.mark.multi(Store=[BytesStore, FileStore])
+def test_from_uri(tmpdir, Store):
+    store = Store.from_uri("sqlite://%s/test_base" % tmpdir)
+    assert store.db_uri == "sqlite://%s/test_base" % tmpdir
+    assert store.table_name == "test_base"
--- a/MoinMoin/storage/stores/_tests/test_sqlite.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/storage/stores/_tests/test_sqlite.py	Sat Oct 29 01:43:06 2011 -0200
@@ -46,3 +46,14 @@
     store.destroy()
     # XXX: check for dropped table
 
+@pytest.mark.multi(Store=[BytesStore, FileStore])
+def test_from_uri(tmpdir, Store):
+    store = Store.from_uri("%s:test_table:0" % tmpdir)
+    assert store.db_name == tmpdir
+    assert store.table_name == 'test_table'
+    assert store.compression_level == 0
+
+    store = Store.from_uri("%s:test_table:2" % tmpdir)
+    assert store.db_name == tmpdir
+    assert store.table_name == 'test_table'
+    assert store.compression_level == 2
--- a/MoinMoin/storage/stores/kc.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/storage/stores/kc.py	Sat Oct 29 01:43:06 2011 -0200
@@ -17,7 +17,7 @@
 
 from __future__ import absolute_import, division
 
-import os
+import os, errno
 from StringIO import StringIO
 
 from kyotocabinet import *
@@ -50,6 +50,12 @@
         self.db_opts = db_opts
 
     def create(self):
+        basedir = os.path.dirname(self.path)
+        try:
+            os.makedirs(basedir)
+        except OSError as e:
+            if e.errno != errno.EEXIST:
+                raise
         self.open(mode=self.mode|DB.OCREATE)
         self.close()
 
--- a/MoinMoin/storage/stores/kt.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/storage/stores/kt.py	Sat Oct 29 01:43:06 2011 -0200
@@ -26,7 +26,10 @@
     """
     @classmethod
     def from_uri(cls, uri):
-        return cls(uri)
+        params = uri.split(':')
+        if len(params) == 2:
+            params[1] = int(params[1])
+        return cls(*params)
 
     def __init__(self, host='127.0.0.1', port=1978, timeout=30):
         """
--- a/MoinMoin/storage/stores/sqla.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/storage/stores/sqla.py	Sat Oct 29 01:43:06 2011 -0200
@@ -27,7 +27,18 @@
     """
     @classmethod
     def from_uri(cls, uri):
-        return cls(uri)
+        """
+        Create a new cls instance from the using the uri
+
+        :param cls: Class to create
+        :param uri: The database uri that we pass on to SQLAlchemy.
+        """
+
+        params = [uri]
+        if '/' in uri.rsplit("//")[-1]:
+            table_name = uri.rsplit("/")[-1]
+            params.append(table_name)
+        return cls(*params)
 
     def __init__(self, db_uri=None, table_name='store', verbose=False):
         """
--- a/MoinMoin/storage/stores/sqlite.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/storage/stores/sqlite.py	Sat Oct 29 01:43:06 2011 -0200
@@ -27,7 +27,18 @@
     """
     @classmethod
     def from_uri(cls, uri):
-        return cls(uri)
+        """
+        Create a new cls instance using the parameters provided in the uri
+
+        :param cls: Class to create
+        :param uri: The URI should follow the following template
+                    db_name:table_name:compression_level
+                    where table_name and compression level are optional
+        """
+        params = uri.split(":")
+        if len(params) == 3:
+            params[2] = int(params[2])
+        return cls(*params)
 
     def __init__(self, db_name, table_name='store', compression_level=0):
         """
--- a/MoinMoin/themes/modernized/static/css/common.css	Fri Oct 28 19:32:53 2011 -0200
+++ b/MoinMoin/themes/modernized/static/css/common.css	Sat Oct 29 01:43:06 2011 -0200
@@ -107,7 +107,7 @@
 
 /* pre */
 pre { border: 1px solid #AEBDCC; background-color: #F3F5F7; padding: 5px; clear: both;
-            font-family: courier, monospace; margin: .33em 0; white-space: pre; }
+            font-family: monospace; margin: .33em 0; white-space: pre; }
 pre.comment { background-color: #CCC; color: red; padding: 0; margin: 0; border: 0; }
 pre.comment:before { content: url(../img/attention.png); }
 
@@ -333,9 +333,9 @@
 
 .moin-diff-line-number { background-color: #C0C0C0; }
 .moin-diff-added { background-color: #E0FFE0; vertical-align: top; width: 50%; white-space: pre-wrap; word-wrap: break-word;
-            font-family: courier, monospace; }
+            font-family: monospace; }
 .moin-diff-removed { background-color: #FFFFE0; vertical-align: top; width: 50%; white-space: pre-wrap; word-wrap: break-word;
-            font-family: courier, monospace; }
+            font-family: monospace; }
 .moin-diff-added span { background-color: #80FF80; }
 .moin-diff-removed span { background-color: #FFFF80; }
 
@@ -670,7 +670,7 @@
 
 @media print {
 
-html { font-family: Times, serif; font-size: 12pt; width: 100%; }
+html { font-family: serif; font-size: 12pt; width: 100%; }
 body, #moin-page, #moin-page, #moin-content-data { margin: 0; padding: 0; }
 
 a, a:visited,
--- a/docs/admin/configure.rst	Fri Oct 28 19:32:53 2011 -0200
+++ b/docs/admin/configure.rst	Sat Oct 29 01:43:06 2011 -0200
@@ -1031,14 +1031,14 @@
 The `uri` depends on the kind of storage backend and stores you want to use
 (see below). Usually it is a URL-like string that looks like::
 
-    stores:fs:/srv/mywiki/%(nsname)s/%%(kind)s
+    stores:fs:/srv/mywiki/%(nsname)s/%(kind)s
     
 `stores` is the name of the backend, followed by a colon, followed by a store
 specification. `fs` is the name of the store, followed by a specification
 that makes sense for the fs (filesystem) store (== a path with placeholders).
 
 `%(nsname)s` placeholder will be replaced 'content' or 'userprofiles' for
-the respective backend. `%%(kind)s` will be replaced by 'meta' or 'data'
+the respective backend. `%(kind)s` will be replaced by 'meta' or 'data'
 later.
 
 In this case, the mapping created will look like this:
@@ -1100,7 +1100,7 @@
 
     data_dir = '/srv/mywiki/data'
     namespace_mapping, acl_mapping = create_simple_mapping(
-        uri='stores:fs:%s/%%(nsname)s/%%%%(kind)s' % data_dir,
+        uri='stores:fs:{0}/%(nsname)s/%(kind)s'.format(data_dir),
         content_acl=dict(before=u'WikiAdmin:read,write,create,destroy',
                          default=u'All:read,write,create',
                          after=u'', ),
@@ -1142,9 +1142,16 @@
 * very fast
 * single-process only, local only
 
-.. todo:
+`uri` for `create_simple_mapping` looks like e.g.::
 
-   add kc store configuration example
+    stores:kc:/srv/mywiki/data/%(nsname)s_%(kind)s.kch
+
+Please see the kyoto cabinet docs about the part after `kc:`.
+
+If you use kc with the builtin server of moin, you must not use the reloader,
+but disable it by commandline option::
+
+  moin moin -r
 
 
 kt store
--- a/wikiconfig.py	Fri Oct 28 19:32:53 2011 -0200
+++ b/wikiconfig.py	Sat Oct 29 01:43:06 2011 -0200
@@ -29,7 +29,7 @@
     # 'hg:' instead to indicate that you want to use the mercurial backend.
     # Alternatively you can set up the mapping yourself (see HelpOnStorageConfiguration).
     namespace_mapping, acl_mapping = create_simple_mapping(
-                            uri='stores:fs:%s/%%(nsname)s/%%%%(kind)s' % data_dir,
+                            uri='stores:fs:{0}/%(nsname)s/%(kind)s'.format(data_dir),
                             # XXX we use rather relaxed ACLs for the development wiki:
                             content_acl=dict(before=u'',
                                              default=u'All:read,write,create,destroy,admin',