changeset 2225:87c9fd50cec1

Added composite name support to indexing and protecting middleware files.
author Ashutosh Singla <ashu1461@gmail.com>
date Fri, 19 Jul 2013 23:15:39 +0530
parents ef06927aefa5
children 09a9bffa1581
files MoinMoin/storage/middleware/indexing.py MoinMoin/storage/middleware/protecting.py
diffstat 2 files changed, 140 insertions(+), 125 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/storage/middleware/indexing.py	Tue Jul 16 06:48:11 2013 +0530
+++ b/MoinMoin/storage/middleware/indexing.py	Fri Jul 19 23:15:39 2013 +0530
@@ -81,7 +81,7 @@
 from MoinMoin.themes import utctimestamp
 from MoinMoin.storage.middleware.validation import ContentMetaSchema, UserMetaSchema, validate_data
 from MoinMoin.storage.error import NoSuchItemError, ItemAlreadyExistsError
-
+from MoinMoin.util.interwiki import split_fqname, CompositeName
 
 WHOOSH_FILESTORAGE = 'FileStorage'
 INDEXES = [LATEST_REVS, ALL_REVS, ]
@@ -118,9 +118,7 @@
     elif not isinstance(names, list):
         raise TypeError("NAME is not a list but %r - fix this!" % names)
     if not names:
-        # we currently never return an empty list, some code
-        # might not be able to deal with it:
-        names = [u'DoesNotExist', ]
+        names = []
     return names
 
 
@@ -168,6 +166,8 @@
                    metadata as a side effect
     :returns: indexable content, text/plain, unicode object
     """
+    fqname = split_fqname(item_name)
+
     class PseudoRev(object):
         def __init__(self, meta, data):
             self.meta = meta
@@ -175,9 +175,9 @@
             self.revid = meta.get(REVID)
 
             class PseudoItem(object):
-                def __init__(self, name):
-                    self.name = name
-            self.item = PseudoItem(item_name)
+                def __init__(self, fqname):
+                    self.fqname = fqname
+            self.item = PseudoItem(fqname)
 
         def read(self, *args, **kw):
             return self.data.read(*args, **kw)
@@ -193,7 +193,10 @@
         return u''
 
     if not item_name:
-        item_name = get_names(meta)[0]
+        try:
+            item_name = get_names(meta)[0]
+        except IndexError:
+            item_name = u'DoesNotExist'
 
     rev = PseudoRev(meta, data)
     try:
@@ -808,7 +811,98 @@
         return Item.existing(self, **query)
 
 
-class Item(object):
+class PropertiesMixin(object):
+    """
+    PropertiesMixin offers methods to find out some additional information from meta.
+    """
+    @property
+    def name(self):
+        if self._name and self._name in self.names:
+            name = self._name
+        else:
+            try:
+                name = self.names[0]
+            except IndexError:
+                # empty name list, no name:
+                name = None
+        assert isinstance(name, unicode) or not name
+        return name
+
+    @property
+    def namespace(self):
+        return self.meta.get(NAMESPACE) or u''
+
+    def _fqname(self, name=None):
+        """
+        return the fully qualified name including the namespace: NS:NAME
+        """
+        if name is not None:
+            return CompositeName(self.namespace, NAME_EXACT, name)
+        else:
+            return CompositeName(self.namespace, ITEMID, self.meta[ITEMID])
+
+    @property
+    def fqname(self):
+        """
+        return the fully qualified name including the namespace: NS:NAME
+        """
+        return self._fqname(self.name)
+
+    @property
+    def fqnames(self):
+        """
+        return the fully qualified names including the namespace: NS:NAME
+        """
+        if self.names:
+            return [self._fqname(name) for name in self.names]
+        else:
+            return [self.fqname]
+
+    @property
+    def parentnames(self):
+        """
+        compute list of parent names (same order as in names, but no dupes)
+
+        :return: parent names (list of unicode)
+        """
+        parent_names = []
+        for name in self.names:
+            parentname_tail = name.rsplit('/', 1)
+            if len(parentname_tail) == 2:
+                parent_name = parentname_tail[0]
+                if parent_name not in parent_names:
+                    parent_names.append(parent_name)
+        return parent_names
+
+    @property
+    def fqparentnames(self):
+        """
+        return the fully qualified parent names including the namespace: NS:NAME
+        """
+        return [self._fqname(name) for name in self.parentnames]
+
+    @property
+    def acl(self):
+        return self.meta.get(ACL)
+
+    @property
+    def ptime(self):
+        dt = self.meta.get(PTIME)
+        if dt is not None:
+            return utctimestamp(dt)
+
+    @property
+    def names(self):
+        return get_names(self.meta)
+
+    @property
+    def mtime(self):
+        dt = self.meta.get(MTIME)
+        if dt is not None:
+            return utctimestamp(dt)
+
+
+class Item(PropertiesMixin):
     def __init__(self, indexer, latest_doc=None, **query):
         """
         :param indexer: indexer middleware instance
@@ -830,11 +924,10 @@
                 # avoid issues in the name(s) property code. if this was a
                 # lookup for some specific item (using a name_exact query), we
                 # put that name into the NAME list, otherwise it'll be empty:
-                if self._name is not None:
-                    names = [self._name, ]
-                else:
-                    names = []
-                latest_doc = {NAME: names}
+                latest_doc = {}
+                for field, value in query.items():
+                    latest_doc[field] = [value] if field in UFIELDS_TYPELIST else value
+                latest_doc[NAME] = latest_doc[NAME_EXACT] if NAME_EXACT in query else []
         self._current = latest_doc
 
     def _get_itemid(self):
@@ -845,38 +938,8 @@
     itemid = property(_get_itemid, _set_itemid)
 
     @property
-    def acl(self):
-        return self._current.get(ACL)
-
-    @property
-    def namespace(self):
-        return self._current.get(NAMESPACE)
-
-    @property
-    def ptime(self):
-        dt = self._current.get(PTIME)
-        if dt is not None:
-            return utctimestamp(dt)
-
-    @property
-    def names(self):
-        return get_names(self._current)
-
-    @property
-    def parentnames(self):
-        """
-        compute list of parent names (same order as in names, but no dupes)
-
-        :return: parent names (list of unicode)
-        """
-        parent_names = []
-        for name in self.names:
-            parentname_tail = name.rsplit('/', 1)
-            if len(parentname_tail) == 2:
-                parent_name = parentname_tail[0]
-                if parent_name not in parent_names:
-                    parent_names.append(parent_name)
-        return parent_names
+    def meta(self):
+        return self._current
 
     @property
     def parentids(self):
@@ -892,59 +955,6 @@
                 parent_ids.add(rev[ITEMID])
         return parent_ids
 
-    @property
-    def mtime(self):
-        dt = self._current.get(MTIME)
-        if dt is not None:
-            return utctimestamp(dt)
-
-    @property
-    def name(self):
-        if self._name and self._name in self.names:
-            name = self._name
-        else:
-            try:
-                name = self.names[0]
-            except IndexError:
-                # empty name list, no name:
-                name = None
-        assert name is None or isinstance(name, unicode)
-        return name
-
-    def _fqname(self, name):
-        """
-        return the fully qualified name including the namespace: NS:NAME
-        """
-        ns = self.namespace
-        name = name or u''
-        if ns:
-            fqn = ns + u':' + name
-        else:
-            fqn = name
-        assert isinstance(fqn, unicode)
-        return fqn
-
-    @property
-    def fqname(self):
-        """
-        return the fully qualified name including the namespace: NS:NAME
-        """
-        return self._fqname(self.name)
-
-    @property
-    def fqnames(self):
-        """
-        return the fully qualified names including the namespace: NS:NAME
-        """
-        return [self._fqname(name) for name in self.names]
-
-    @property
-    def fqparentnames(self):
-        """
-        return the fully qualified parent names including the namespace: NS:NAME
-        """
-        return [self._fqname(name) for name in self.parentnames]
-
     @classmethod
     def create(cls, indexer, **query):
         """
@@ -1010,7 +1020,7 @@
                        contenttype_guessed=None,
                        acl_parent=None,
                        return_rev=False,
-                       fqname=None
+                       fqname=None,
                        ):
         """
         Store a revision into the backend, write metadata and data to it.
@@ -1124,7 +1134,7 @@
             self.destroy_revision(rev.revid)
 
 
-class Revision(object):
+class Revision(PropertiesMixin):
     """
     An existing revision (exists in the backend).
     """
@@ -1155,21 +1165,6 @@
         # Note: this does not immediately raise a KeyError for non-existing revs any more
         # If you access data or meta, it will, though.
 
-    @property
-    def names(self):
-        return get_names(self.meta)
-
-    @property
-    def name(self):
-        name = self._name
-        if name is None:
-            try:
-                name = self.names[0]
-            except IndexError:
-                # empty name list, no name:
-                name = None
-        return name
-
     def set_context(self, context):
         for name in self.names:
             if name.startswith(context):
--- a/MoinMoin/storage/middleware/protecting.py	Tue Jul 16 06:48:11 2013 +0530
+++ b/MoinMoin/storage/middleware/protecting.py	Fri Jul 19 23:15:39 2013 +0530
@@ -26,6 +26,8 @@
 
 from MoinMoin.security import AccessControlList
 
+from MoinMoin.util.interwiki import split_fqname
+
 # max sizes of some lru caches:
 LOOKUP_CACHE = 100  # ACL lookup for some itemname
 PARSE_CACHE = 100  # ACL string -> ACL object parsing
@@ -79,19 +81,20 @@
         # ACL lookups afterwards will fetch fresh info from the lower layers.
         self.get_acls.cache_clear()
 
-    def _get_configured_acls(self, itemname):
+    def _get_configured_acls(self, fqname):
         """
         for a fully-qualified itemname (namespace:name), get the acl configuration
         for that (part of the) namespace.
 
-        @param itemname: fully qualified itemname
+        @param fqname: fully qualified itemname
         @returns: acl configuration (acl dict from the acl_mapping)
         """
+        itemname = fqname.value if fqname.field == NAME_EXACT else u''
         for prefix, acls in self.acl_mapping:
             if itemname.startswith(prefix):
                 return acls
         else:
-            raise ValueError('No acl_mapping entry found for item {0!r}'.format(itemname))
+            raise ValueError('No acl_mapping entry found for item {0!r}'.format(fqname))
 
     def _get_acls(self, itemid=None, fqname=None):
         """
@@ -109,7 +112,7 @@
         elif fqname is not None:
             # itemid might be None for new, not yet stored items,
             # but we have fqname then
-            q = {NAME_EXACT: fqname}
+            q = fqname.query
         else:
             raise ValueError("need itemid or fqname")
         item = self.get_item(**q)
@@ -183,16 +186,17 @@
         item = self.indexer.existing_item(**query)
         return ProtectedItem(self, item)
 
-    def may(self, itemname, capability, usernames=None):
+    def may(self, fqname, capability, usernames=None):
         if usernames is not None and isinstance(usernames, (str, unicode)):
             # we got a single username (maybe str), make a list of unicode:
             if isinstance(usernames, str):
                 usernames = usernames.decode('utf-8')
             usernames = [usernames, ]
-        if isinstance(itemname, list):
-            # if we get a list of names, just use first one to fetch item
-            itemname = itemname[0]
-        item = self[itemname]
+        # TODO Make sure that fqname must be a CompositeName class instance, not unicode or list.
+        fqname = fqname[0] if isinstance(fqname, list) else fqname
+        if isinstance(fqname, unicode):
+            fqname = split_fqname(fqname)
+        item = self.get_item(**fqname.query)
         allowed = item.allows(capability, user_names=usernames)
         return allowed
 
@@ -227,6 +231,14 @@
         return self.item.name
 
     @property
+    def fqname(self):
+        return self.item.fqname
+
+    @property
+    def fqnames(self):
+        return self.item.fqnames
+
+    @property
     def acl(self):
         return self.item.acl
 
@@ -348,6 +360,14 @@
         return self.rev.name
 
     @property
+    def fqname(self):
+        return self.rev.fqname
+
+    @property
+    def fqnames(self):
+        return self.rev.fqnames
+
+    @property
     def meta(self):
         self.require(READ, PUBREAD)
         return self.rev.meta