changeset 4776:3f9147c23d9c

Groups2009: MoinMoin.GroupDict became MoinMoin.groups.backends.wiki_pages.Backend. request.groups proviedes access to group definitions. request.dicts was removed.
author Dmitrijs Milajevs <dimazest@gmail.com>
date Mon, 01 Jun 2009 20:11:17 +0200
parents 0a0387a9eb1e
children 6162b0cd1da8
files MoinMoin/PageEditor.py MoinMoin/_tests/test_wikidicts.py MoinMoin/events/wikidictsrescan.py MoinMoin/groups/__init__.py MoinMoin/groups/backends/__init__.py MoinMoin/groups/backends/_tests/test_wiki_group.py MoinMoin/groups/backends/wiki_group.py MoinMoin/i18n/__init__.py MoinMoin/web/contexts.py MoinMoin/wikidicts.py docs/CHANGES.dmilajevs
diffstat 10 files changed, 502 insertions(+), 437 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/PageEditor.py	Sun May 31 21:17:49 2009 +0200
+++ b/MoinMoin/PageEditor.py	Mon Jun 01 20:11:17 2009 +0200
@@ -782,7 +782,7 @@
             # Users can define their own variables via
             # UserHomepage/MyDict, which override the default variables.
             userDictPage = u.name + "/MyDict"
-            if request.dicts.has_dict(userDictPage):
+            if userDictPage in request.groups:
                 variables.update(request.dicts.dict(userDictPage))
 
         for name in variables:
--- a/MoinMoin/_tests/test_wikidicts.py	Sun May 31 21:17:49 2009 +0200
+++ b/MoinMoin/_tests/test_wikidicts.py	Mon Jun 01 20:11:17 2009 +0200
@@ -17,61 +17,6 @@
 from MoinMoin.user import User
 from MoinMoin._tests import append_page, become_trusted, create_page, create_random_string_list, nuke_page, nuke_user
 
-class TestGroupPage:
-
-    def testCamelCase(self):
-        """ wikidicts: initFromText: CamelCase links """
-        text = """
- * CamelCase
-"""
-        assert self.getMembers(text) == ['CamelCase']
-
-    def testExtendedName(self):
-        """ wikidicts: initFromText: extended names """
-        text = """
- * extended name
-"""
-        assert self.getMembers(text) == ['extended name']
-
-    def testExtendedLink(self):
-        """ wikidicts: initFromText: extended link """
-        text = """
- * [[extended link]]
-"""
-        assert self.getMembers(text) == ['extended link']
-
-    def testIgnoreSecondLevelList(self):
-        """ wikidicts: initFromText: ignore non first level items """
-        text = """
-  * second level
-   * third level
-    * forth level
-     * and then some...
-"""
-        assert self.getMembers(text) == []
-
-    def testIgnoreOther(self):
-        """ wikidicts: initFromText: ignore anything but first level list itmes """
-        text = """
-= ignore this =
- * take this
-
-Ignore previous line and this text.
-"""
-        assert self.getMembers(text) == ['take this']
-
-    def testStripWhitespace(self):
-        """ wikidicts: initFromText: strip whitespace around items """
-        text = """
- *   take this
-"""
-        assert self.getMembers(text) == ['take this']
-
-    def getMembers(self, text):
-        group = wikidicts.Group(self.request, '')
-        group.initFromText(text)
-        return group.members()
-
 
 class TestDictPage:
 
@@ -97,135 +42,6 @@
         assert d['Last'] == 'last item'
         assert len(d) == 4
 
-class TestGroupDicts:
-
-    def testSystemPagesGroupInDicts(self):
-        """ wikidict: names in SystemPagesGroup should be in request.dicts
-
-        Get a list of all pages, and check that the dicts list all of them.
-
-        Assume that the SystemPagesGroup is in the data or the underlay dir.
-        """
-        assert Page.Page(self.request, 'SystemPagesGroup').exists(), "SystemPagesGroup is missing, Can't run test"
-        systemPages = wikidicts.Group(self.request, 'SystemPagesGroup')
-        #print repr(systemPages)
-        #print repr(self.request.dicts['SystemPagesGroup'])
-        for member in systemPages.members():
-            assert self.request.dicts.has_member('SystemPagesGroup', member), '%s should be in request.dict' % member
-
-        members, groups = self.request.dicts.expand_group('SystemPagesGroup')
-        assert 'SystemPagesInEnglishGroup' in groups
-        assert 'RecentChanges' in members
-        assert 'HelpContents' in members
-
-    def testRenameGroupPage(self):
-        """
-         tests if the dict cache for groups is refreshed after renaming a Group page
-        """
-        request = self.request
-        become_trusted(request)
-        page = create_page(request, u'SomeGroup', u" * ExampleUser")
-        page.renamePage('AnotherGroup')
-        group = wikidicts.Group(request, '')
-        isgroup = request.cfg.cache.page_group_regexact.search
-        grouppages = request.rootpage.getPageList(user='', filter=isgroup)
-        result = request.dicts.has_member(u'AnotherGroup', u'ExampleUser')
-        nuke_page(request, u'AnotherGroup')
-
-        assert result is True
-
-    def testCopyGroupPage(self):
-        """
-         tests if the dict cache for groups is refreshed after copying a Group page
-        """
-        request = self.request
-        become_trusted(request)
-        page = create_page(request, u'SomeGroup', u" * ExampleUser")
-        page.copyPage(u'OtherGroup')
-        group = wikidicts.Group(request, '')
-        isgroup = request.cfg.cache.page_group_regexact.search
-        grouppages = request.rootpage.getPageList(user='', filter=isgroup)
-        result = request.dicts.has_member(u'OtherGroup', u'ExampleUser')
-        nuke_page(request, u'OtherGroup')
-        nuke_page(request, u'SomeGroup')
-
-        assert result is True
-
-    def testAppendingGroupPage(self):
-        """
-         tests scalability by appending a name to a large list of group members
-        """
-        # long list of users
-        page_content = [u" * %s" % member for member in create_random_string_list(length=15, count=30000)]
-        request = self.request
-        become_trusted(request)
-        test_user = create_random_string_list(length=15, count=1)[0]
-        page = create_page(request, u'UserGroup', "\n".join(page_content))
-        page = append_page(request, u'UserGroup', u' * %s' % test_user)
-        result = request.dicts.has_member('UserGroup', test_user)
-        nuke_page(request, u'UserGroup')
-
-        assert result is True
-
-    def testUserAppendingGroupPage(self):
-        """
-         tests appending a username to a large list of group members and user creation
-        """
-        # long list of users
-        page_content = [u" * %s" % member for member in create_random_string_list()]
-        request = self.request
-        become_trusted(request)
-        test_user = create_random_string_list(length=15, count=1)[0]
-        page = create_page(request, u'UserGroup', "\n".join(page_content))
-        page = append_page(request, u'UserGroup', u' * %s' % test_user)
-
-        # now shortly later we create a user object
-        user = User(request, name=test_user)
-        if not user.exists():
-            User(request, name=test_user, password=test_user).save()
-
-        result = request.dicts.has_member('UserGroup', test_user)
-        nuke_page(request, u'UserGroup')
-        nuke_user(request, test_user)
-
-        assert result is True
-
-    def testMemberRemovedFromGroupPage(self):
-        """
-         tests appending a member to a large list of group members and recreating the page without the member
-        """
-        # long list of users
-        page_content = [u" * %s" % member for member in create_random_string_list()]
-        page_content = "\n".join(page_content)
-        request = self.request
-        become_trusted(request)
-        test_user = create_random_string_list(length=15, count=1)[0]
-        page = create_page(request, u'UserGroup', page_content)
-        page = append_page(request, u'UserGroup', u' * %s' % test_user)
-        # saves the text without test_user
-        page.saveText(page_content, 0)
-        result = request.dicts.has_member('UserGroup', test_user)
-        nuke_page(request, u'UserGroup')
-
-        assert result is False
-
-    def testGroupPageTrivialChange(self):
-        """
-         tests appending a username to a group page by trivial change
-        """
-        request = self.request
-        become_trusted(request)
-        test_user = create_random_string_list(length=15, count=1)[0]
-        member = u" * %s\n" % test_user
-        page = create_page(request, u'UserGroup', member)
-        # next member saved  as trivial change
-        test_user = create_random_string_list(length=15, count=1)[0]
-        member = u" * %s\n" % test_user
-        page.saveText(member, 0, trivial=1)
-        result = request.dicts.has_member('UserGroup', test_user)
-        nuke_page(request, u'UserGroup')
-
-        assert result is True
 
 coverage_modules = ['MoinMoin.wikidicts']
 
--- a/MoinMoin/events/wikidictsrescan.py	Sun May 31 21:17:49 2009 +0200
+++ b/MoinMoin/events/wikidictsrescan.py	Mon Jun 01 20:11:17 2009 +0200
@@ -12,7 +12,7 @@
 logging = log.getLogger(__name__)
 
 from MoinMoin import events as ev
-from MoinMoin import wikidicts
+from MoinMoin.groups.backends import wiki_group
 
 def handle(event):
     # "changed" includes creation, deletion, renamed and copied
@@ -33,9 +33,7 @@
     request = event.request
     page = event.page
 
-    logging.debug("groupsdicts changed: %r, scan_dicts started", page.page_name)
-    del request.dicts
-    gd = wikidicts.GroupDict(request)
-    gd.scan_dicts()
+    logging.debug("groups changed: %r, scan_dicts started", page.page_name)
+    request.groups.update_cache()
     logging.debug("groupsdicts changed: scan_dicts finished")
 
--- a/MoinMoin/groups/__init__.py	Sun May 31 21:17:49 2009 +0200
+++ b/MoinMoin/groups/__init__.py	Mon Jun 01 20:11:17 2009 +0200
@@ -128,3 +128,18 @@
         """
         return [group_name for group_name in self
                          if member in self[group_name]]
+
+    def update_cache(self):
+        for backend in self._backends:
+            try:
+                backend.update_cache()
+            except AttributeError:
+                pass
+
+    def load_cache(self):
+        for backend in self._backends:
+            try:
+                backend.load_cache()
+            except AttributeError:
+                # XXX Log that backend doesn't provide this service.
+                pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/groups/backends/_tests/test_wiki_group.py	Mon Jun 01 20:11:17 2009 +0200
@@ -0,0 +1,215 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - MoinMoin.wikidicts tests
+
+    @copyright: 2003-2004 by Juergen Hermann <jh@web.de>,
+                2007 by MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+
+    XXX Update docstrings
+"""
+
+import py
+import re
+import shutil
+
+from MoinMoin.groups.backends import wiki_group
+from MoinMoin import Page
+from MoinMoin.PageEditor import PageEditor
+from MoinMoin.user import User
+from MoinMoin._tests import append_page, become_trusted, create_page, create_random_string_list, nuke_page, nuke_user
+from MoinMoin.groups import GroupManager
+
+class TestWikiGroupPage:
+
+    def testCamelCase(self):
+        """ wikidicts: initFromText: CamelCase links """
+        text = """
+ * CamelCase
+"""
+        assert self.getMembers(text) == ['CamelCase']
+
+    def testExtendedName(self):
+        """ wikidicts: initFromText: extended names """
+        text = """
+ * extended name
+"""
+        assert self.getMembers(text) == ['extended name']
+
+    def testExtendedLink(self):
+        """ wikidicts: initFromText: extended link """
+        text = """
+ * [[extended link]]
+"""
+        assert self.getMembers(text) == ['extended link']
+
+    def testIgnoreSecondLevelList(self):
+        """ wikidicts: initFromText: ignore non first level items """
+        text = """
+  * second level
+   * third level
+    * forth level
+     * and then some...
+"""
+        assert self.getMembers(text) == []
+
+    def testIgnoreOther(self):
+        """ wikidicts: initFromText: ignore anything but first level list itmes """
+        text = """
+= ignore this =
+ * take this
+
+Ignore previous line and this text.
+"""
+        assert self.getMembers(text) == ['take this']
+
+    def testStripWhitespace(self):
+        """ wikidicts: initFromText: strip whitespace around items """
+        text = """
+ *   take this
+"""
+        assert self.getMembers(text) == ['take this']
+
+    def getMembers(self, text):
+        group = wiki_group.Group(self.request, '')
+        group.initFromText(text)
+        return group.members()
+
+
+class TestWikiGroupBackend:
+
+    from MoinMoin._tests import wikiconfig
+    class Config(wikiconfig.Config):
+        def group_manager_init(self, request):
+            return GroupManager(backends=[wiki_group.Backend(request)])
+
+    def testSystemPagesGroupInDicts(self):
+        """ wikidict: names in SystemPagesGroup should be in request.dicts
+
+        Get a list of all pages, and check that the dicts list all of them.
+
+        Assume that the SystemPagesGroup is in the data or the underlay dir.
+        """
+        assert Page.Page(self.request, 'SystemPagesGroup').exists(), "SystemPagesGroup is missing, Can't run test"
+        systemPages = wiki_group.Group(self.request, 'SystemPagesGroup')
+        #print repr(systemPages)
+        #print repr(self.request.dicts['SystemPagesGroup'])
+        for member in systemPages.members():
+            assert member in self.request.groups[u'SystemPagesGroup'], '%s should be in request.dict' % member
+
+        assert 'SystemPagesInEnglishGroup' in self.request.groups
+        assert 'RecentChanges' in self.request.groups[u'SystemPagesGroup']
+        assert 'HelpContents' in self.request.groups[u'SystemPagesGroup']
+
+    def testRenameGroupPage(self):
+        """
+         tests if the dict cache for groups is refreshed after renaming a Group page
+        """
+        request = self.request
+        become_trusted(request)
+        page = create_page(request, u'SomeGroup', u" * ExampleUser")
+        page.renamePage('AnotherGroup')
+        group = wiki_group.Group(request, '')
+        isgroup = request.cfg.cache.page_group_regexact.search
+        grouppages = request.rootpage.getPageList(user='', filter=isgroup)
+        result = u'ExampleUser' in request.groups[u'AnotherGroup']
+        nuke_page(request, u'AnotherGroup')
+
+        assert result is True
+
+    def testCopyGroupPage(self):
+        """
+         tests if the dict cache for groups is refreshed after copying a Group page
+        """
+        request = self.request
+        become_trusted(request)
+        page = create_page(request, u'SomeGroup', u" * ExampleUser")
+        page.copyPage(u'OtherGroup')
+        group = wiki_group.Group(request, '')
+        isgroup = request.cfg.cache.page_group_regexact.search
+        grouppages = request.rootpage.getPageList(user='', filter=isgroup)
+        result = u'ExampleUser' in request.groups[u'OtherGroup']
+        nuke_page(request, u'OtherGroup')
+        nuke_page(request, u'SomeGroup')
+
+        assert result is True
+
+    def testAppendingGroupPage(self):
+        """
+         tests scalability by appending a name to a large list of group members
+        """
+        # long list of users
+        page_content = [u" * %s" % member for member in create_random_string_list(length=15, count=30000)]
+        request = self.request
+        become_trusted(request)
+        test_user = create_random_string_list(length=15, count=1)[0]
+        page = create_page(request, u'UserGroup', "\n".join(page_content))
+        page = append_page(request, u'UserGroup', u' * %s' % test_user)
+        result = test_user in request.groups['UserGroup']
+        nuke_page(request, u'UserGroup')
+
+        assert result is True
+
+    def testUserAppendingGroupPage(self):
+        """
+         tests appending a username to a large list of group members and user creation
+        """
+        # long list of users
+        page_content = [u" * %s" % member for member in create_random_string_list()]
+        request = self.request
+        become_trusted(request)
+        test_user = create_random_string_list(length=15, count=1)[0]
+        page = create_page(request, u'UserGroup', "\n".join(page_content))
+        page = append_page(request, u'UserGroup', u' * %s' % test_user)
+
+        # now shortly later we create a user object
+        user = User(request, name=test_user)
+        if not user.exists():
+            User(request, name=test_user, password=test_user).save()
+
+        result = test_user in request.groups[u'UserGroup']
+        nuke_page(request, u'UserGroup')
+        nuke_user(request, test_user)
+
+        assert result is True
+
+    def testMemberRemovedFromGroupPage(self):
+        """
+         tests appending a member to a large list of group members and recreating the page without the member
+        """
+        # long list of users
+        page_content = [u" * %s" % member for member in create_random_string_list()]
+        page_content = "\n".join(page_content)
+        request = self.request
+        become_trusted(request)
+        test_user = create_random_string_list(length=15, count=1)[0]
+        page = create_page(request, u'UserGroup', page_content)
+        page = append_page(request, u'UserGroup', u' * %s' % test_user)
+        # saves the text without test_user
+        page.saveText(page_content, 0)
+        result = test_user in request.groups[u'UserGroup']
+        nuke_page(request, u'UserGroup')
+
+        assert result is False
+
+    def testGroupPageTrivialChange(self):
+        """
+         tests appending a username to a group page by trivial change
+        """
+        request = self.request
+        become_trusted(request)
+        test_user = create_random_string_list(length=15, count=1)[0]
+        member = u" * %s\n" % test_user
+        page = create_page(request, u'UserGroup', member)
+        # next member saved  as trivial change
+        test_user = create_random_string_list(length=15, count=1)[0]
+        member = u" * %s\n" % test_user
+        page.saveText(member, 0, trivial=1)
+        result = test_user in request.groups[u'UserGroup']
+        nuke_page(request, u'UserGroup')
+
+        assert result is True
+
+
+coverage_modules = ['MoinMoin.groups.backends.wiki_group']
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/groups/backends/wiki_group.py	Mon Jun 01 20:11:17 2009 +0200
@@ -0,0 +1,255 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - wiki_group backend provides access to the group definitions on a wiki pages.
+
+    @copyright: 2003-2007 MoinMoin:ThomasWaldmann,
+                2003 by Gustavo Niemeyer
+                2009 MoinMoin:DmitrijsMilajevs
+    @license: GNU GPL, see COPYING for details.
+"""
+import re, time
+
+from MoinMoin import caching, Page
+from MoinMoin.wikidicts import DictBase, DictDict
+
+# Version of the internal data structure which is pickled.
+# Please increment if you have changed the structure.
+DICTS_PICKLE_VERSION = 6
+
+class Group(DictBase):
+    """ Group of users, of pages, of whatever.
+
+    How a Group definition page should look like:
+
+    any text ignored
+     * member1
+      * ignored, too
+     * member2
+     * ....
+     * memberN
+    any text ignored
+
+    If there are any free links using [[free link]] notation, the markup
+    is stripped from the member.
+    """
+    # * Member - ignore all but first level list items, strip whitespace, strip free links markup
+    regex = re.compile(ur'^ \* +(?:\[\[)?(?P<member>.+?)(?:\]\])? *$', re.MULTILINE | re.UNICODE)
+
+    def __init__(self, request=None, pagename=None):
+        self._list = []
+        DictBase.__init__(self, request, pagename)
+
+    def initFromText(self, text):
+        for match in self.regex.finditer(text):
+            member = match.group('member')
+            self.addmember(member)
+
+    def update(self, members):
+        self.addmembers(members.keys())
+
+    def __iter__(self):
+        return iter(self._list)
+
+    def members(self):
+        """ return the group's members """
+        return self._list[:]
+
+    def addmembers(self, members):
+        """ add a list of members to the group """
+        for m in members:
+            self.addmember(m)
+
+    def addmember(self, member):
+        """ add a member to the group """
+        self[member] = 1
+        self._list.append(member)
+
+    def has_member(self, member):
+        """ check if the group has member <member> """
+        return self.has_key(member)
+
+    def __repr__(self):
+        return "<Group name=%r items=%r>" % (self.name, self._list)
+
+class Backend(DictDict):
+    """
+    a dictionary of Group objects
+
+    Config:
+         cfg.page_group_regex
+            Default: ".*Group$"
+
+    XXX Group expanding. Groups should be expanded in the
+        initialization, of the backend (which probably bad idea,
+        because all groups may take a lot of memory, or expanded when
+        needed. This must be transparent outside of the class.
+
+    XXX Some methods were deleted (has_member, hasgroup), load_dicts
+        was renamed to load_cache, scan_dicts to update_cache methods
+        of super-class may be used somewhere in the code.
+    """
+
+    def __init__(self, request):
+        self.cfg = request.cfg
+        self.request = request
+
+        # XXX probably, not the best place
+        self.update_cache()
+
+    def reset(self):
+        self.dictdict = {}
+        self.groupdict = {} # unexpanded groups
+        self.picklever = DICTS_PICKLE_VERSION
+        self.disk_cache_id = None
+
+    def members(self, groupname):
+        """ get members of group <groupname> """
+        try:
+            group = self.dictdict[groupname]
+        except KeyError:
+            return []
+        return group.members()
+
+    def addgroup(self, request, groupname):
+        """ add a new group (will be read from the wiki page) """
+        grp = Group(request, groupname)
+        self.groupdict[groupname] = grp
+        self.expand_groups()
+
+    def __contains__(self, groupname):
+        """
+        Check if group groupname is defined in some wikipage.
+        """
+        return self.groupdict.has_key(groupname)
+
+    def __getitem__(self, name):
+        return self.groupdict[name]
+
+    def membergroups(self, member):
+        """ list all groups where member is a member of """
+        grouplist = []
+        for group in self.dictdict.values():
+            if group.has_member(member):
+                grouplist.append(group.name)
+        return grouplist
+
+    def expand_groups(self):
+        """ copy expanded groups to self.dictdict """
+        for name in self.groupdict:
+            members, groups = self.expand_group(name)
+            members.update(groups)
+            grp = Group()
+            grp.update(members)
+            self.dictdict[name] = grp
+
+    def expand_group(self, name):
+        """ Recursively expand group <name>, using the groupdict (which is a not expanded
+            dict of all group names -> group dicts). We return a flat list of group member
+            names and group names.
+
+        Given a groupdict (self) with two groups:
+
+            MainGroup: [A, SubGroup]
+            SubGroup: [B, C]
+
+        MainGroup is expanded to:
+
+            self.expand_group('MainGroup') -> [A, B, C], [MainGroup, SubGroup]
+        """
+        groups = {name: 1}
+        members = {}
+        groupmembers = self[name].keys()
+        for member in groupmembers:
+            # Skip duplicates
+            if member in groups:
+                continue
+            # Add member and its children
+            if member in self:
+                new_members, new_groups = self.expand_group(member)
+                groups.update(new_groups)
+                members.update(new_members)
+            else:
+                members[member] = 1
+        return members, groups
+
+    def load_cache(self):
+        """ load the dict from the cache """
+        request = self.request
+        rescan = False
+        arena = 'wikidicts'
+        key = 'dicts_groups'
+        cache = caching.CacheEntry(request, arena, key, scope='wiki', use_pickle=True)
+        current_disk_cache_id = cache.uid()
+        try:
+            self.__dict__.update(self.cfg.cache.DICTS_DATA)
+            if (current_disk_cache_id is None or
+                current_disk_cache_id != self.disk_cache_id):
+                self.reset()
+                raise AttributeError # not fresh, force load from disk
+            else:
+                return
+        except AttributeError:
+            try:
+                data = cache.content()
+                self.__dict__.update(data)
+                self.disk_cache_id = current_disk_cache_id
+
+                # invalidate the cache if the pickle version changed
+                if self.picklever != DICTS_PICKLE_VERSION:
+                    raise # force rescan
+            except:
+                self.reset()
+                rescan = True
+
+        if rescan:
+            self.scan_dicts()
+            self.load_dicts() # try again
+            return
+
+        data = {
+            "disk_cache_id": self.disk_cache_id,
+            "dictdict": self.dictdict,
+            "groupdict": self.groupdict,
+            "picklever": self.picklever
+        }
+
+        # remember it (persistent environments)
+        self.cfg.cache.DICTS_DATA = data
+
+    def update_cache(self):
+        """ scan all pages matching the dict / group regex and
+            cache the results on disk
+        """
+        request = self.request
+        self.reset()
+
+        # XXX get cache write lock here
+        scan_begin_time = time.time()
+
+        # Get all pages in the wiki - without user filtering using filter
+        # function - this makes the page list about 10 times faster.
+        isdict = self.cfg.cache.page_dict_regexact.search
+        dictpages = request.rootpage.getPageList(user='', filter=isdict)
+        for pagename in dictpages:
+            self.adddict(request, pagename)
+
+        isgroup = self.cfg.cache.page_group_regexact.search
+        grouppages = request.rootpage.getPageList(user='', filter=isgroup)
+        for pagename in grouppages:
+            self.addgroup(request, pagename)
+
+        scan_end_time = time.time()
+        self.expand_groups()
+
+        arena = 'wikidicts'
+        key = 'dicts_groups'
+        cache = caching.CacheEntry(request, arena, key, scope='wiki', use_pickle=True)
+        data = {
+            "scan_begin_time": scan_begin_time,
+            "scan_end_time": scan_end_time,
+            "dictdict": self.dictdict,
+            "groupdict": self.groupdict,
+            "picklever": self.picklever
+        }
+        cache.update(data)
+        # XXX release cache write lock here
--- a/MoinMoin/i18n/__init__.py	Sun May 31 21:17:49 2009 +0200
+++ b/MoinMoin/i18n/__init__.py	Mon Jun 01 20:11:17 2009 +0200
@@ -302,10 +302,10 @@
     else:
         try:
             language = languages[lang]['x-language-in-english']
-            dictpagename = "%sDict" % language.replace(' ', '')
-            dicts = request.dicts
-            if dicts.has_dict(dictpagename):
-                userdict = dicts.dict(dictpagename)
+            grouppagename = "%sDict" % language.replace(' ', '')
+            groups = request.groups
+            if grouppagename in groups:
+                userdict = dicts.dict(grouppagename)
                 translated = userdict[original]
             else:
                 raise KeyError
--- a/MoinMoin/web/contexts.py	Sun May 31 21:17:49 2009 +0200
+++ b/MoinMoin/web/contexts.py	Mon Jun 01 20:11:17 2009 +0200
@@ -369,13 +369,12 @@
         return UniqueIDGenerator(pagename=pagename)
     uid_generator = EnvironProxy(uid_generator)
 
-    def dicts(self):
-        """ Lazy initialize the dicts on the first access """
-        from MoinMoin import wikidicts
-        dicts = wikidicts.GroupDict(self)
-        dicts.load_dicts()
-        return dicts
-    dicts = EnvironProxy(dicts)
+    def groups(self):
+        """ Lazy initialize the groups on the first access """
+        groups = self.request.cfg.group_manager_init(self)
+        groups.load_cache()
+        return groups
+    groups = EnvironProxy(groups)
 
     def reset(self):
         self.current_lang = self.cfg.language_default
--- a/MoinMoin/wikidicts.py	Sun May 31 21:17:49 2009 +0200
+++ b/MoinMoin/wikidicts.py	Mon Jun 01 20:11:17 2009 +0200
@@ -8,11 +8,7 @@
 """
 import re, time
 
-from MoinMoin import caching, Page
-
-# Version of the internal data structure which is pickled.
-# Please increment if you have changed the structure.
-DICTS_PICKLE_VERSION = 6
+from MoinMoin import Page
 
 
 class DictBase(dict):
@@ -65,62 +61,6 @@
         return "<Dict name=%r items=%r>" % (self.name, self.items())
 
 
-class Group(DictBase):
-    """ Group of users, of pages, of whatever.
-
-    How a Group definition page should look like:
-
-    any text ignored
-     * member1
-      * ignored, too
-     * member2
-     * ....
-     * memberN
-    any text ignored
-
-    If there are any free links using [[free link]] notation, the markup
-    is stripped from the member.
-    """
-    # * Member - ignore all but first level list items, strip whitespace, strip free links markup
-    regex = re.compile(ur'^ \* +(?:\[\[)?(?P<member>.+?)(?:\]\])? *$', re.MULTILINE | re.UNICODE)
-
-    def __init__(self, request=None, pagename=None):
-        self._list = []
-        DictBase.__init__(self, request, pagename)
-
-    def initFromText(self, text):
-        for match in self.regex.finditer(text):
-            member = match.group('member')
-            self.addmember(member)
-
-    def update(self, members):
-        self.addmembers(members.keys())
-
-    def __iter__(self):
-        return iter(self._list)
-
-    def members(self):
-        """ return the group's members """
-        return self._list[:]
-
-    def addmembers(self, members):
-        """ add a list of members to the group """
-        for m in members:
-            self.addmember(m)
-
-    def addmember(self, member):
-        """ add a member to the group """
-        self[member] = 1
-        self._list.append(member)
-
-    def has_member(self, member):
-        """ check if the group has member <member> """
-        return self.has_key(member)
-
-    def __repr__(self):
-        return "<Group name=%r items=%r>" % (self.name, self._list)
-
-
 class DictDict:
     """ a dictionary of Dict objects
 
@@ -183,177 +123,3 @@
             if d.has_key(key):
                 dictlist.append(d.name)
         return dictlist
-
-
-class GroupDict(DictDict):
-    """ a dictionary of Group objects
-
-       Config:
-           cfg.page_group_regex
-               Default: ".*Group$"
-    """
-
-    def __init__(self, request):
-        self.cfg = request.cfg
-        self.request = request
-
-    def reset(self):
-        self.dictdict = {}
-        self.groupdict = {} # unexpanded groups
-        self.picklever = DICTS_PICKLE_VERSION
-        self.disk_cache_id = None
-
-    def has_member(self, groupname, member):
-        """ check if we have <member> as a member of group <groupname> """
-        group = self.dictdict.get(groupname)
-        return group and group.has_member(member)
-
-    def members(self, groupname):
-        """ get members of group <groupname> """
-        try:
-            group = self.dictdict[groupname]
-        except KeyError:
-            return []
-        return group.members()
-
-    def addgroup(self, request, groupname):
-        """ add a new group (will be read from the wiki page) """
-        grp = Group(request, groupname)
-        self.groupdict[groupname] = grp
-        self.expand_groups()
-
-    def hasgroup(self, groupname):
-        """ check if we have a dict <dictname> """
-        return self.groupdict.has_key(groupname)
-
-    def __getitem__(self, name):
-        return self.groupdict[name]
-
-    def membergroups(self, member):
-        """ list all groups where member is a member of """
-        grouplist = []
-        for group in self.dictdict.values():
-            if group.has_member(member):
-                grouplist.append(group.name)
-        return grouplist
-
-    def expand_groups(self):
-        """ copy expanded groups to self.dictdict """
-        for name in self.groupdict:
-            members, groups = self.expand_group(name)
-            members.update(groups)
-            grp = Group()
-            grp.update(members)
-            self.dictdict[name] = grp
-
-    def expand_group(self, name):
-        """ Recursively expand group <name>, using the groupdict (which is a not expanded
-            dict of all group names -> group dicts). We return a flat list of group member
-            names and group names.
-
-        Given a groupdict (self) with two groups:
-
-            MainGroup: [A, SubGroup]
-            SubGroup: [B, C]
-
-        MainGroup is expanded to:
-
-            self.expand_group('MainGroup') -> [A, B, C], [MainGroup, SubGroup]
-        """
-        groups = {name: 1}
-        members = {}
-        groupmembers = self[name].keys()
-        for member in groupmembers:
-            # Skip duplicates
-            if member in groups:
-                continue
-            # Add member and its children
-            if self.hasgroup(member):
-                new_members, new_groups = self.expand_group(member)
-                groups.update(new_groups)
-                members.update(new_members)
-            else:
-                members[member] = 1
-        return members, groups
-
-    def load_dicts(self):
-        """ load the dict from the cache """
-        request = self.request
-        rescan = False
-        arena = 'wikidicts'
-        key = 'dicts_groups'
-        cache = caching.CacheEntry(request, arena, key, scope='wiki', use_pickle=True)
-        current_disk_cache_id = cache.uid()
-        try:
-            self.__dict__.update(self.cfg.cache.DICTS_DATA)
-            if (current_disk_cache_id is None or
-                current_disk_cache_id != self.disk_cache_id):
-                self.reset()
-                raise AttributeError # not fresh, force load from disk
-            else:
-                return
-        except AttributeError:
-            try:
-                data = cache.content()
-                self.__dict__.update(data)
-                self.disk_cache_id = current_disk_cache_id
-
-                # invalidate the cache if the pickle version changed
-                if self.picklever != DICTS_PICKLE_VERSION:
-                    raise # force rescan
-            except:
-                self.reset()
-                rescan = True
-
-        if rescan:
-            self.scan_dicts()
-            self.load_dicts() # try again
-            return
-
-        data = {
-            "disk_cache_id": self.disk_cache_id,
-            "dictdict": self.dictdict,
-            "groupdict": self.groupdict,
-            "picklever": self.picklever
-        }
-
-        # remember it (persistent environments)
-        self.cfg.cache.DICTS_DATA = data
-
-    def scan_dicts(self):
-        """ scan all pages matching the dict / group regex and
-            cache the results on disk
-        """
-        request = self.request
-        self.reset()
-
-        # XXX get cache write lock here
-        scan_begin_time = time.time()
-
-        # Get all pages in the wiki - without user filtering using filter
-        # function - this makes the page list about 10 times faster.
-        isdict = self.cfg.cache.page_dict_regexact.search
-        dictpages = request.rootpage.getPageList(user='', filter=isdict)
-        for pagename in dictpages:
-            self.adddict(request, pagename)
-
-        isgroup = self.cfg.cache.page_group_regexact.search
-        grouppages = request.rootpage.getPageList(user='', filter=isgroup)
-        for pagename in grouppages:
-            self.addgroup(request, pagename)
-
-        scan_end_time = time.time()
-        self.expand_groups()
-
-        arena = 'wikidicts'
-        key = 'dicts_groups'
-        cache = caching.CacheEntry(request, arena, key, scope='wiki', use_pickle=True)
-        data = {
-            "scan_begin_time": scan_begin_time,
-            "scan_end_time": scan_end_time,
-            "dictdict": self.dictdict,
-            "groupdict": self.groupdict,
-            "picklever": self.picklever
-        }
-        cache.update(data)
-        # XXX release cache write lock here
--- a/docs/CHANGES.dmilajevs	Sun May 31 21:17:49 2009 +0200
+++ b/docs/CHANGES.dmilajevs	Mon Jun 01 20:11:17 2009 +0200
@@ -2,4 +2,5 @@
 
    New features:
    * Group backends. Group definitions can be stored outside of MoinMoin.
-   * MoinMoin.security.AccessControlList works with the new GroupManager.
\ No newline at end of file
+   * MoinMoin.security.AccessControlList works with the new GroupManager.
+   * GroupDict became MoinMoin.groups.backends.wiki_pages.Backend
\ No newline at end of file