We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Group administration: AttributeError: 'NoneType' object has no attribute 'getGroupTitleOrName'
/Plone2/@@usergroup-groupmembership?groupname=None

gives me

Here is the full error message:

Display traceback as text

Traceback (innermost last):

Module ZPublisher.Publish, line 138, in publish
Module ZPublisher.mapply, line 77, in mapply
Module ZPublisher.Publish, line 48, in call_object
Module Products.CMFPlone.controlpanel.browser.usergroups_groupmembership, line 69, in **call**
Module Products.CMFPlone.controlpanel.browser.usergroups_groupmembership, line 16, in update
AttributeError: 'NoneType' object has no attribute 'getGroupTitleOrName'

This happens when you click on "new group" and then on the "group members" tab.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `Products/CMFPlone/controlpanel/browser/usergroups_groupmembership.py`
Content:
```
1 from Products.CMFPlone import PloneMessageFactory as _
2 from zExceptions import Forbidden
3 from Products.CMFCore.utils import getToolByName
4 from Products.CMFPlone.controlpanel.browser.usergroups import \
5     UsersGroupsControlPanelView
6 from Products.CMFPlone.utils import normalizeString
7 
8 
9 class GroupMembershipControlPanel(UsersGroupsControlPanelView):
10 
11     def update(self):
12         self.groupname = getattr(self.request, 'groupname')
13         self.gtool = getToolByName(self, 'portal_groups')
14         self.mtool = getToolByName(self, 'portal_membership')
15         self.group = self.gtool.getGroupById(self.groupname)
16         self.grouptitle = self.group.getGroupTitleOrName() or self.groupname
17 
18         self.request.set('grouproles', self.group.getRoles() if self.group else [])
19         self.canAddUsers = True
20         if 'Manager' in self.request.get('grouproles') and not self.is_zope_manager:
21             self.canAddUsers = False
22 
23         self.groupquery = self.makeQuery(groupname=self.groupname)
24         self.groupkeyquery = self.makeQuery(key=self.groupname)
25 
26         form = self.request.form
27         submitted = form.get('form.submitted', False)
28 
29         self.searchResults = []
30         self.searchString = ''
31         self.newSearch = False
32 
33         if submitted:
34             # add/delete before we search so we don't show stale results
35             toAdd = form.get('add', [])
36             if toAdd:
37                 if not self.canAddUsers:
38                     raise Forbidden
39 
40                 for u in toAdd:
41                     self.gtool.addPrincipalToGroup(u, self.groupname, self.request)
42                 self.context.plone_utils.addPortalMessage(_(u'Changes made.'))
43 
44             toDelete = form.get('delete', [])
45             if toDelete:
46                 for u in toDelete:
47                     self.gtool.removePrincipalFromGroup(u, self.groupname, self.request)
48                 self.context.plone_utils.addPortalMessage(_(u'Changes made.'))
49 
50             search = form.get('form.button.Search', None) is not None
51             edit = form.get('form.button.Edit', None) is not None and toDelete
52             add = form.get('form.button.Add', None) is not None and toAdd
53             findAll = form.get('form.button.FindAll', None) is not None and \
54                 not self.many_users
55             # The search string should be cleared when one of the
56             # non-search buttons has been clicked.
57             if findAll or edit or add:
58                 form['searchstring'] = ''
59             self.searchString = form.get('searchstring', '')
60             if findAll or bool(self.searchString):
61                 self.searchResults = self.getPotentialMembers(self.searchString)
62 
63             if search or findAll:
64                 self.newSearch = True
65 
66         self.groupMembers = self.getMembers()
67 
68     def __call__(self):
69         self.update()
70         return self.index()
71 
72     def isGroup(self, itemName):
73         return self.gtool.isGroup(itemName)
74 
75     def getMembers(self):
76         searchResults = self.gtool.getGroupMembers(self.groupname)
77 
78         groupResults = [self.gtool.getGroupById(m) for m in searchResults]
79         groupResults.sort(key=lambda x: x is not None and normalizeString(x.getGroupTitleOrName()))
80 
81         userResults = [self.mtool.getMemberById(m) for m in searchResults]
82         userResults.sort(key=lambda x: x is not None and x.getProperty('fullname') is not None and normalizeString(x.getProperty('fullname')) or '')
83 
84         mergedResults = groupResults + userResults
85         return filter(None, mergedResults)
86 
87     def getPotentialMembers(self, searchString):
88         ignoredUsersGroups = [x.id for x in self.getMembers() + [self.group,] if x is not None]
89         return self.membershipSearch(searchString, ignore=ignoredUsersGroups)
90
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/Products/CMFPlone/controlpanel/browser/usergroups_groupmembership.py b/Products/CMFPlone/controlpanel/browser/usergroups_groupmembership.py
--- a/Products/CMFPlone/controlpanel/browser/usergroups_groupmembership.py
+++ b/Products/CMFPlone/controlpanel/browser/usergroups_groupmembership.py
@@ -13,6 +13,9 @@
         self.gtool = getToolByName(self, 'portal_groups')
         self.mtool = getToolByName(self, 'portal_membership')
         self.group = self.gtool.getGroupById(self.groupname)
+        if self.group is None:
+            return
+
         self.grouptitle = self.group.getGroupTitleOrName() or self.groupname
 
         self.request.set('grouproles', self.group.getRoles() if self.group else [])

{"golden_diff": "diff --git a/Products/CMFPlone/controlpanel/browser/usergroups_groupmembership.py b/Products/CMFPlone/controlpanel/browser/usergroups_groupmembership.py\n--- a/Products/CMFPlone/controlpanel/browser/usergroups_groupmembership.py\n+++ b/Products/CMFPlone/controlpanel/browser/usergroups_groupmembership.py\n@@ -13,6 +13,9 @@\n         self.gtool = getToolByName(self, 'portal_groups')\n         self.mtool = getToolByName(self, 'portal_membership')\n         self.group = self.gtool.getGroupById(self.groupname)\n+        if self.group is None:\n+            return\n+\n         self.grouptitle = self.group.getGroupTitleOrName() or self.groupname\n \n         self.request.set('grouproles', self.group.getRoles() if self.group else [])\n", "issue": "Group administration: AttributeError: 'NoneType' object has no attribute 'getGroupTitleOrName'\n/Plone2/@@usergroup-groupmembership?groupname=None\n\ngives me\n\nHere is the full error message:\n\nDisplay traceback as text\n\nTraceback (innermost last):\n\nModule ZPublisher.Publish, line 138, in publish\nModule ZPublisher.mapply, line 77, in mapply\nModule ZPublisher.Publish, line 48, in call_object\nModule Products.CMFPlone.controlpanel.browser.usergroups_groupmembership, line 69, in **call**\nModule Products.CMFPlone.controlpanel.browser.usergroups_groupmembership, line 16, in update\nAttributeError: 'NoneType' object has no attribute 'getGroupTitleOrName'\n\nThis happens when you click on \"new group\" and then on the \"group members\" tab.\n\n", "before_files": [{"content": "from Products.CMFPlone import PloneMessageFactory as _\nfrom zExceptions import Forbidden\nfrom Products.CMFCore.utils import getToolByName\nfrom Products.CMFPlone.controlpanel.browser.usergroups import \\\n    UsersGroupsControlPanelView\nfrom Products.CMFPlone.utils import normalizeString\n\n\nclass GroupMembershipControlPanel(UsersGroupsControlPanelView):\n\n    def update(self):\n        self.groupname = getattr(self.request, 'groupname')\n        self.gtool = getToolByName(self, 'portal_groups')\n        self.mtool = getToolByName(self, 'portal_membership')\n        self.group = self.gtool.getGroupById(self.groupname)\n        self.grouptitle = self.group.getGroupTitleOrName() or self.groupname\n\n        self.request.set('grouproles', self.group.getRoles() if self.group else [])\n        self.canAddUsers = True\n        if 'Manager' in self.request.get('grouproles') and not self.is_zope_manager:\n            self.canAddUsers = False\n\n        self.groupquery = self.makeQuery(groupname=self.groupname)\n        self.groupkeyquery = self.makeQuery(key=self.groupname)\n\n        form = self.request.form\n        submitted = form.get('form.submitted', False)\n\n        self.searchResults = []\n        self.searchString = ''\n        self.newSearch = False\n\n        if submitted:\n            # add/delete before we search so we don't show stale results\n            toAdd = form.get('add', [])\n            if toAdd:\n                if not self.canAddUsers:\n                    raise Forbidden\n\n                for u in toAdd:\n                    self.gtool.addPrincipalToGroup(u, self.groupname, self.request)\n                self.context.plone_utils.addPortalMessage(_(u'Changes made.'))\n\n            toDelete = form.get('delete', [])\n            if toDelete:\n                for u in toDelete:\n                    self.gtool.removePrincipalFromGroup(u, self.groupname, self.request)\n                self.context.plone_utils.addPortalMessage(_(u'Changes made.'))\n\n            search = form.get('form.button.Search', None) is not None\n            edit = form.get('form.button.Edit', None) is not None and toDelete\n            add = form.get('form.button.Add', None) is not None and toAdd\n            findAll = form.get('form.button.FindAll', None) is not None and \\\n                not self.many_users\n            # The search string should be cleared when one of the\n            # non-search buttons has been clicked.\n            if findAll or edit or add:\n                form['searchstring'] = ''\n            self.searchString = form.get('searchstring', '')\n            if findAll or bool(self.searchString):\n                self.searchResults = self.getPotentialMembers(self.searchString)\n\n            if search or findAll:\n                self.newSearch = True\n\n        self.groupMembers = self.getMembers()\n\n    def __call__(self):\n        self.update()\n        return self.index()\n\n    def isGroup(self, itemName):\n        return self.gtool.isGroup(itemName)\n\n    def getMembers(self):\n        searchResults = self.gtool.getGroupMembers(self.groupname)\n\n        groupResults = [self.gtool.getGroupById(m) for m in searchResults]\n        groupResults.sort(key=lambda x: x is not None and normalizeString(x.getGroupTitleOrName()))\n\n        userResults = [self.mtool.getMemberById(m) for m in searchResults]\n        userResults.sort(key=lambda x: x is not None and x.getProperty('fullname') is not None and normalizeString(x.getProperty('fullname')) or '')\n\n        mergedResults = groupResults + userResults\n        return filter(None, mergedResults)\n\n    def getPotentialMembers(self, searchString):\n        ignoredUsersGroups = [x.id for x in self.getMembers() + [self.group,] if x is not None]\n        return self.membershipSearch(searchString, ignore=ignoredUsersGroups)\n", "path": "Products/CMFPlone/controlpanel/browser/usergroups_groupmembership.py"}], "after_files": [{"content": "from Products.CMFPlone import PloneMessageFactory as _\nfrom zExceptions import Forbidden\nfrom Products.CMFCore.utils import getToolByName\nfrom Products.CMFPlone.controlpanel.browser.usergroups import \\\n    UsersGroupsControlPanelView\nfrom Products.CMFPlone.utils import normalizeString\n\n\nclass GroupMembershipControlPanel(UsersGroupsControlPanelView):\n\n    def update(self):\n        self.groupname = getattr(self.request, 'groupname')\n        self.gtool = getToolByName(self, 'portal_groups')\n        self.mtool = getToolByName(self, 'portal_membership')\n        self.group = self.gtool.getGroupById(self.groupname)\n        if self.group is None:\n            return\n\n        self.grouptitle = self.group.getGroupTitleOrName() or self.groupname\n\n        self.request.set('grouproles', self.group.getRoles() if self.group else [])\n        self.canAddUsers = True\n        if 'Manager' in self.request.get('grouproles') and not self.is_zope_manager:\n            self.canAddUsers = False\n\n        self.groupquery = self.makeQuery(groupname=self.groupname)\n        self.groupkeyquery = self.makeQuery(key=self.groupname)\n\n        form = self.request.form\n        submitted = form.get('form.submitted', False)\n\n        self.searchResults = []\n        self.searchString = ''\n        self.newSearch = False\n\n        if submitted:\n            # add/delete before we search so we don't show stale results\n            toAdd = form.get('add', [])\n            if toAdd:\n                if not self.canAddUsers:\n                    raise Forbidden\n\n                for u in toAdd:\n                    self.gtool.addPrincipalToGroup(u, self.groupname, self.request)\n                self.context.plone_utils.addPortalMessage(_(u'Changes made.'))\n\n            toDelete = form.get('delete', [])\n            if toDelete:\n                for u in toDelete:\n                    self.gtool.removePrincipalFromGroup(u, self.groupname, self.request)\n                self.context.plone_utils.addPortalMessage(_(u'Changes made.'))\n\n            search = form.get('form.button.Search', None) is not None\n            edit = form.get('form.button.Edit', None) is not None and toDelete\n            add = form.get('form.button.Add', None) is not None and toAdd\n            findAll = form.get('form.button.FindAll', None) is not None and \\\n                not self.many_users\n            # The search string should be cleared when one of the\n            # non-search buttons has been clicked.\n            if findAll or edit or add:\n                form['searchstring'] = ''\n            self.searchString = form.get('searchstring', '')\n            if findAll or bool(self.searchString):\n                self.searchResults = self.getPotentialMembers(self.searchString)\n\n            if search or findAll:\n                self.newSearch = True\n\n        self.groupMembers = self.getMembers()\n\n    def __call__(self):\n        self.update()\n        return self.index()\n\n    def isGroup(self, itemName):\n        return self.gtool.isGroup(itemName)\n\n    def getMembers(self):\n        searchResults = self.gtool.getGroupMembers(self.groupname)\n\n        groupResults = [self.gtool.getGroupById(m) for m in searchResults]\n        groupResults.sort(key=lambda x: x is not None and normalizeString(x.getGroupTitleOrName()))\n\n        userResults = [self.mtool.getMemberById(m) for m in searchResults]\n        userResults.sort(key=lambda x: x is not None and x.getProperty('fullname') is not None and normalizeString(x.getProperty('fullname')) or '')\n\n        mergedResults = groupResults + userResults\n        return filter(None, mergedResults)\n\n    def getPotentialMembers(self, searchString):\n        ignoredUsersGroups = [x.id for x in self.getMembers() + [self.group,] if x is not None]\n        return self.membershipSearch(searchString, ignore=ignoredUsersGroups)\n", "path": "Products/CMFPlone/controlpanel/browser/usergroups_groupmembership.py"}]}