We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
button to add group lists from group page
**Is your feature request related to a problem? Please describe.**
At the moment if you want to add a group-curated list, you have to do it from the Lists page, rather than the home page of the Group. This makes sense if you know how the backend functionality works but is not very intuitive.

**Describe the solution you'd like**
  Add a `Create list` button (like the one in the lists view) to the Group page.

**Describe alternatives you've considered**
Leaving the current, obscure, functionality.

**Additional context**
I intended to add this once the initial group functionality was completed, but I forgot about it.
This Issue is to remind me to put in a PR to add this.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bookwyrm/views/group.py`
Content:
```
1 """group views"""
2 from django.apps import apps
3 from django.contrib.auth.decorators import login_required
4 from django.db import IntegrityError
5 from django.core.paginator import Paginator
6 from django.http import HttpResponseBadRequest
7 from django.shortcuts import get_object_or_404, redirect
8 from django.template.response import TemplateResponse
9 from django.utils.decorators import method_decorator
10 from django.views import View
11 from django.views.decorators.http import require_POST
12 from django.contrib.postgres.search import TrigramSimilarity
13 from django.db.models.functions import Greatest
14 
15 from bookwyrm import forms, models
16 from bookwyrm.suggested_users import suggested_users
17 from .helpers import get_user_from_username
18 
19 # pylint: disable=no-self-use
20 class Group(View):
21     """group page"""
22 
23     def get(self, request, group_id):
24         """display a group"""
25 
26         group = get_object_or_404(models.Group, id=group_id)
27         group.raise_visible_to_user(request.user)
28         lists = (
29             models.List.privacy_filter(request.user)
30             .filter(group=group)
31             .order_by("-updated_date")
32         )
33 
34         data = {
35             "group": group,
36             "lists": lists,
37             "group_form": forms.GroupForm(instance=group),
38             "path": "/group",
39         }
40         return TemplateResponse(request, "groups/group.html", data)
41 
42     @method_decorator(login_required, name="dispatch")
43     def post(self, request, group_id):
44         """edit a group"""
45         user_group = get_object_or_404(models.Group, id=group_id)
46         form = forms.GroupForm(request.POST, instance=user_group)
47         if not form.is_valid():
48             return redirect("group", user_group.id)
49         user_group = form.save()
50 
51         # let the other members know something about the group changed
52         memberships = models.GroupMember.objects.filter(group=user_group)
53         model = apps.get_model("bookwyrm.Notification", require_ready=True)
54         for field in form.changed_data:
55             notification_type = (
56                 "GROUP_PRIVACY"
57                 if field == "privacy"
58                 else "GROUP_NAME"
59                 if field == "name"
60                 else "GROUP_DESCRIPTION"
61                 if field == "description"
62                 else None
63             )
64             if notification_type:
65                 for membership in memberships:
66                     member = membership.user
67                     if member != request.user:
68                         model.objects.create(
69                             user=member,
70                             related_user=request.user,
71                             related_group=user_group,
72                             notification_type=notification_type,
73                         )
74 
75         return redirect("group", user_group.id)
76 
77 
78 @method_decorator(login_required, name="dispatch")
79 class UserGroups(View):
80     """a user's groups page"""
81 
82     def get(self, request, username):
83         """display a group"""
84         user = get_user_from_username(request.user, username)
85         groups = (
86             models.Group.privacy_filter(request.user)
87             .filter(memberships__user=user)
88             .order_by("-updated_date")
89         )
90         paginated = Paginator(groups, 12)
91 
92         data = {
93             "groups": paginated.get_page(request.GET.get("page")),
94             "is_self": request.user.id == user.id,
95             "user": user,
96             "group_form": forms.GroupForm(),
97             "path": user.local_path + "/group",
98         }
99         return TemplateResponse(request, "user/groups.html", data)
100 
101     @method_decorator(login_required, name="dispatch")
102     # pylint: disable=unused-argument
103     def post(self, request, username):
104         """create a user group"""
105         form = forms.GroupForm(request.POST)
106         if not form.is_valid():
107             return redirect(request.user.local_path + "/groups")
108         group = form.save()
109         # add the creator as a group member
110         models.GroupMember.objects.create(group=group, user=request.user)
111         return redirect("group", group.id)
112 
113 
114 @method_decorator(login_required, name="dispatch")
115 class FindUsers(View):
116     """find friends to add to your group"""
117 
118     # this is mostly borrowed from the Get Started friend finder
119 
120     def get(self, request, group_id):
121         """basic profile info"""
122         user_query = request.GET.get("user_query")
123         group = get_object_or_404(models.Group, id=group_id)
124 
125         if not group:
126             return HttpResponseBadRequest()
127 
128         if not group.user == request.user:
129             return HttpResponseBadRequest()
130 
131         user_results = (
132             models.User.viewer_aware_objects(request.user)
133             .exclude(
134                 memberships__in=group.memberships.all()
135             )  # don't suggest users who are already members
136             .annotate(
137                 similarity=Greatest(
138                     TrigramSimilarity("username", user_query),
139                     TrigramSimilarity("localname", user_query),
140                 )
141             )
142             .filter(similarity__gt=0.5, local=True)
143             .order_by("-similarity")[:5]
144         )
145         data = {"no_results": not user_results}
146 
147         if user_results.count() < 5:
148             user_results = list(user_results) + suggested_users.get_suggestions(
149                 request.user, local=True
150             )
151 
152         data = {
153             "suggested_users": user_results,
154             "group": group,
155             "group_form": forms.GroupForm(instance=group),
156             "user_query": user_query,
157             "requestor_is_manager": request.user == group.user,
158         }
159         return TemplateResponse(request, "groups/find_users.html", data)
160 
161 
162 @require_POST
163 @login_required
164 def delete_group(request, group_id):
165     """delete a group"""
166     group = get_object_or_404(models.Group, id=group_id)
167 
168     # only the owner can delete a group
169     group.raise_not_deletable(request.user)
170 
171     # deal with any group lists
172     models.List.objects.filter(group=group).update(curation="closed", group=None)
173 
174     group.delete()
175     return redirect(request.user.local_path + "/groups")
176 
177 
178 @require_POST
179 @login_required
180 def invite_member(request):
181     """invite a member to the group"""
182     group = get_object_or_404(models.Group, id=request.POST.get("group"))
183     user = get_user_from_username(request.user, request.POST["user"])
184 
185     if not group.user == request.user:
186         return HttpResponseBadRequest()
187 
188     try:
189         models.GroupMemberInvitation.objects.create(user=user, group=group)
190     except IntegrityError:
191         pass
192 
193     return redirect(user.local_path)
194 
195 
196 @require_POST
197 @login_required
198 def remove_member(request):
199     """remove a member from the group"""
200     group = get_object_or_404(models.Group, id=request.POST.get("group"))
201     user = get_user_from_username(request.user, request.POST["user"])
202 
203     # you can't be removed from your own group
204     if user == group.user:
205         return HttpResponseBadRequest()
206 
207     is_member = models.GroupMember.objects.filter(group=group, user=user).exists()
208     is_invited = models.GroupMemberInvitation.objects.filter(
209         group=group, user=user
210     ).exists()
211 
212     if is_invited:
213         try:
214             invitation = models.GroupMemberInvitation.objects.get(
215                 user=user, group=group
216             )
217 
218             invitation.reject()
219 
220         except IntegrityError:
221             pass
222 
223     if is_member:
224         try:
225             models.List.remove_from_group(group.user, user)
226             models.GroupMember.remove(group.user, user)
227         except IntegrityError:
228             pass
229 
230         memberships = models.GroupMember.objects.filter(group=group)
231         model = apps.get_model("bookwyrm.Notification", require_ready=True)
232         notification_type = "LEAVE" if user == request.user else "REMOVE"
233         # let the other members know about it
234         for membership in memberships:
235             member = membership.user
236             if member != request.user:
237                 model.objects.create(
238                     user=member,
239                     related_user=user,
240                     related_group=group,
241                     notification_type=notification_type,
242                 )
243 
244         # let the user (now ex-member) know as well, if they were removed
245         if notification_type == "REMOVE":
246             model.objects.create(
247                 user=user,
248                 related_group=group,
249                 notification_type=notification_type,
250             )
251 
252     return redirect(group.local_path)
253 
254 
255 @require_POST
256 @login_required
257 def accept_membership(request):
258     """accept an invitation to join a group"""
259     group = get_object_or_404(models.Group, id=request.POST.get("group"))
260     invite = get_object_or_404(
261         models.GroupMemberInvitation, group=group, user=request.user
262     )
263 
264     try:
265         invite.accept()
266     except IntegrityError:
267         pass
268 
269     return redirect(group.local_path)
270 
271 
272 @require_POST
273 @login_required
274 def reject_membership(request):
275     """reject an invitation to join a group"""
276     group = get_object_or_404(models.Group, id=request.POST.get("group"))
277     invite = get_object_or_404(
278         models.GroupMemberInvitation, group=group, user=request.user
279     )
280 
281     invite.reject()
282     return redirect(request.user.local_path)
283
```
--- 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/bookwyrm/views/group.py b/bookwyrm/views/group.py
--- a/bookwyrm/views/group.py
+++ b/bookwyrm/views/group.py
@@ -34,7 +34,8 @@
         data = {
             "group": group,
             "lists": lists,
-            "group_form": forms.GroupForm(instance=group),
+            "group_form": forms.GroupForm(instance=group, auto_id="group_form_id_%s"),
+            "list_form": forms.ListForm(),
             "path": "/group",
         }
         return TemplateResponse(request, "groups/group.html", data)
@@ -121,6 +122,11 @@
         """basic profile info"""
         user_query = request.GET.get("user_query")
         group = get_object_or_404(models.Group, id=group_id)
+        lists = (
+            models.List.privacy_filter(request.user)
+            .filter(group=group)
+            .order_by("-updated_date")
+        )
 
         if not group:
             return HttpResponseBadRequest()
@@ -142,7 +148,7 @@
             .filter(similarity__gt=0.5, local=True)
             .order_by("-similarity")[:5]
         )
-        data = {"no_results": not user_results}
+        no_results = not user_results
 
         if user_results.count() < 5:
             user_results = list(user_results) + suggested_users.get_suggestions(
@@ -151,8 +157,11 @@
 
         data = {
             "suggested_users": user_results,
+            "no_results": no_results,
             "group": group,
-            "group_form": forms.GroupForm(instance=group),
+            "lists": lists,
+            "group_form": forms.GroupForm(instance=group, auto_id="group_form_id_%s"),
+            "list_form": forms.ListForm(),
             "user_query": user_query,
             "requestor_is_manager": request.user == group.user,
         }

{"golden_diff": "diff --git a/bookwyrm/views/group.py b/bookwyrm/views/group.py\n--- a/bookwyrm/views/group.py\n+++ b/bookwyrm/views/group.py\n@@ -34,7 +34,8 @@\n         data = {\n             \"group\": group,\n             \"lists\": lists,\n-            \"group_form\": forms.GroupForm(instance=group),\n+            \"group_form\": forms.GroupForm(instance=group, auto_id=\"group_form_id_%s\"),\n+            \"list_form\": forms.ListForm(),\n             \"path\": \"/group\",\n         }\n         return TemplateResponse(request, \"groups/group.html\", data)\n@@ -121,6 +122,11 @@\n         \"\"\"basic profile info\"\"\"\n         user_query = request.GET.get(\"user_query\")\n         group = get_object_or_404(models.Group, id=group_id)\n+        lists = (\n+            models.List.privacy_filter(request.user)\n+            .filter(group=group)\n+            .order_by(\"-updated_date\")\n+        )\n \n         if not group:\n             return HttpResponseBadRequest()\n@@ -142,7 +148,7 @@\n             .filter(similarity__gt=0.5, local=True)\n             .order_by(\"-similarity\")[:5]\n         )\n-        data = {\"no_results\": not user_results}\n+        no_results = not user_results\n \n         if user_results.count() < 5:\n             user_results = list(user_results) + suggested_users.get_suggestions(\n@@ -151,8 +157,11 @@\n \n         data = {\n             \"suggested_users\": user_results,\n+            \"no_results\": no_results,\n             \"group\": group,\n-            \"group_form\": forms.GroupForm(instance=group),\n+            \"lists\": lists,\n+            \"group_form\": forms.GroupForm(instance=group, auto_id=\"group_form_id_%s\"),\n+            \"list_form\": forms.ListForm(),\n             \"user_query\": user_query,\n             \"requestor_is_manager\": request.user == group.user,\n         }\n", "issue": "button to add group lists from group page\n**Is your feature request related to a problem? Please describe.**\r\nAt the moment if you want to add a group-curated list, you have to do it from the Lists page, rather than the home page of the Group. This makes sense if you know how the backend functionality works but is not very intuitive.\r\n\r\n**Describe the solution you'd like**\r\n  Add a `Create list` button (like the one in the lists view) to the Group page.\r\n\r\n**Describe alternatives you've considered**\r\nLeaving the current, obscure, functionality.\r\n\r\n**Additional context**\r\nI intended to add this once the initial group functionality was completed, but I forgot about it.\r\nThis Issue is to remind me to put in a PR to add this.\r\n\n", "before_files": [{"content": "\"\"\"group views\"\"\"\nfrom django.apps import apps\nfrom django.contrib.auth.decorators import login_required\nfrom django.db import IntegrityError\nfrom django.core.paginator import Paginator\nfrom django.http import HttpResponseBadRequest\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.template.response import TemplateResponse\nfrom django.utils.decorators import method_decorator\nfrom django.views import View\nfrom django.views.decorators.http import require_POST\nfrom django.contrib.postgres.search import TrigramSimilarity\nfrom django.db.models.functions import Greatest\n\nfrom bookwyrm import forms, models\nfrom bookwyrm.suggested_users import suggested_users\nfrom .helpers import get_user_from_username\n\n# pylint: disable=no-self-use\nclass Group(View):\n    \"\"\"group page\"\"\"\n\n    def get(self, request, group_id):\n        \"\"\"display a group\"\"\"\n\n        group = get_object_or_404(models.Group, id=group_id)\n        group.raise_visible_to_user(request.user)\n        lists = (\n            models.List.privacy_filter(request.user)\n            .filter(group=group)\n            .order_by(\"-updated_date\")\n        )\n\n        data = {\n            \"group\": group,\n            \"lists\": lists,\n            \"group_form\": forms.GroupForm(instance=group),\n            \"path\": \"/group\",\n        }\n        return TemplateResponse(request, \"groups/group.html\", data)\n\n    @method_decorator(login_required, name=\"dispatch\")\n    def post(self, request, group_id):\n        \"\"\"edit a group\"\"\"\n        user_group = get_object_or_404(models.Group, id=group_id)\n        form = forms.GroupForm(request.POST, instance=user_group)\n        if not form.is_valid():\n            return redirect(\"group\", user_group.id)\n        user_group = form.save()\n\n        # let the other members know something about the group changed\n        memberships = models.GroupMember.objects.filter(group=user_group)\n        model = apps.get_model(\"bookwyrm.Notification\", require_ready=True)\n        for field in form.changed_data:\n            notification_type = (\n                \"GROUP_PRIVACY\"\n                if field == \"privacy\"\n                else \"GROUP_NAME\"\n                if field == \"name\"\n                else \"GROUP_DESCRIPTION\"\n                if field == \"description\"\n                else None\n            )\n            if notification_type:\n                for membership in memberships:\n                    member = membership.user\n                    if member != request.user:\n                        model.objects.create(\n                            user=member,\n                            related_user=request.user,\n                            related_group=user_group,\n                            notification_type=notification_type,\n                        )\n\n        return redirect(\"group\", user_group.id)\n\n\n@method_decorator(login_required, name=\"dispatch\")\nclass UserGroups(View):\n    \"\"\"a user's groups page\"\"\"\n\n    def get(self, request, username):\n        \"\"\"display a group\"\"\"\n        user = get_user_from_username(request.user, username)\n        groups = (\n            models.Group.privacy_filter(request.user)\n            .filter(memberships__user=user)\n            .order_by(\"-updated_date\")\n        )\n        paginated = Paginator(groups, 12)\n\n        data = {\n            \"groups\": paginated.get_page(request.GET.get(\"page\")),\n            \"is_self\": request.user.id == user.id,\n            \"user\": user,\n            \"group_form\": forms.GroupForm(),\n            \"path\": user.local_path + \"/group\",\n        }\n        return TemplateResponse(request, \"user/groups.html\", data)\n\n    @method_decorator(login_required, name=\"dispatch\")\n    # pylint: disable=unused-argument\n    def post(self, request, username):\n        \"\"\"create a user group\"\"\"\n        form = forms.GroupForm(request.POST)\n        if not form.is_valid():\n            return redirect(request.user.local_path + \"/groups\")\n        group = form.save()\n        # add the creator as a group member\n        models.GroupMember.objects.create(group=group, user=request.user)\n        return redirect(\"group\", group.id)\n\n\n@method_decorator(login_required, name=\"dispatch\")\nclass FindUsers(View):\n    \"\"\"find friends to add to your group\"\"\"\n\n    # this is mostly borrowed from the Get Started friend finder\n\n    def get(self, request, group_id):\n        \"\"\"basic profile info\"\"\"\n        user_query = request.GET.get(\"user_query\")\n        group = get_object_or_404(models.Group, id=group_id)\n\n        if not group:\n            return HttpResponseBadRequest()\n\n        if not group.user == request.user:\n            return HttpResponseBadRequest()\n\n        user_results = (\n            models.User.viewer_aware_objects(request.user)\n            .exclude(\n                memberships__in=group.memberships.all()\n            )  # don't suggest users who are already members\n            .annotate(\n                similarity=Greatest(\n                    TrigramSimilarity(\"username\", user_query),\n                    TrigramSimilarity(\"localname\", user_query),\n                )\n            )\n            .filter(similarity__gt=0.5, local=True)\n            .order_by(\"-similarity\")[:5]\n        )\n        data = {\"no_results\": not user_results}\n\n        if user_results.count() < 5:\n            user_results = list(user_results) + suggested_users.get_suggestions(\n                request.user, local=True\n            )\n\n        data = {\n            \"suggested_users\": user_results,\n            \"group\": group,\n            \"group_form\": forms.GroupForm(instance=group),\n            \"user_query\": user_query,\n            \"requestor_is_manager\": request.user == group.user,\n        }\n        return TemplateResponse(request, \"groups/find_users.html\", data)\n\n\n@require_POST\n@login_required\ndef delete_group(request, group_id):\n    \"\"\"delete a group\"\"\"\n    group = get_object_or_404(models.Group, id=group_id)\n\n    # only the owner can delete a group\n    group.raise_not_deletable(request.user)\n\n    # deal with any group lists\n    models.List.objects.filter(group=group).update(curation=\"closed\", group=None)\n\n    group.delete()\n    return redirect(request.user.local_path + \"/groups\")\n\n\n@require_POST\n@login_required\ndef invite_member(request):\n    \"\"\"invite a member to the group\"\"\"\n    group = get_object_or_404(models.Group, id=request.POST.get(\"group\"))\n    user = get_user_from_username(request.user, request.POST[\"user\"])\n\n    if not group.user == request.user:\n        return HttpResponseBadRequest()\n\n    try:\n        models.GroupMemberInvitation.objects.create(user=user, group=group)\n    except IntegrityError:\n        pass\n\n    return redirect(user.local_path)\n\n\n@require_POST\n@login_required\ndef remove_member(request):\n    \"\"\"remove a member from the group\"\"\"\n    group = get_object_or_404(models.Group, id=request.POST.get(\"group\"))\n    user = get_user_from_username(request.user, request.POST[\"user\"])\n\n    # you can't be removed from your own group\n    if user == group.user:\n        return HttpResponseBadRequest()\n\n    is_member = models.GroupMember.objects.filter(group=group, user=user).exists()\n    is_invited = models.GroupMemberInvitation.objects.filter(\n        group=group, user=user\n    ).exists()\n\n    if is_invited:\n        try:\n            invitation = models.GroupMemberInvitation.objects.get(\n                user=user, group=group\n            )\n\n            invitation.reject()\n\n        except IntegrityError:\n            pass\n\n    if is_member:\n        try:\n            models.List.remove_from_group(group.user, user)\n            models.GroupMember.remove(group.user, user)\n        except IntegrityError:\n            pass\n\n        memberships = models.GroupMember.objects.filter(group=group)\n        model = apps.get_model(\"bookwyrm.Notification\", require_ready=True)\n        notification_type = \"LEAVE\" if user == request.user else \"REMOVE\"\n        # let the other members know about it\n        for membership in memberships:\n            member = membership.user\n            if member != request.user:\n                model.objects.create(\n                    user=member,\n                    related_user=user,\n                    related_group=group,\n                    notification_type=notification_type,\n                )\n\n        # let the user (now ex-member) know as well, if they were removed\n        if notification_type == \"REMOVE\":\n            model.objects.create(\n                user=user,\n                related_group=group,\n                notification_type=notification_type,\n            )\n\n    return redirect(group.local_path)\n\n\n@require_POST\n@login_required\ndef accept_membership(request):\n    \"\"\"accept an invitation to join a group\"\"\"\n    group = get_object_or_404(models.Group, id=request.POST.get(\"group\"))\n    invite = get_object_or_404(\n        models.GroupMemberInvitation, group=group, user=request.user\n    )\n\n    try:\n        invite.accept()\n    except IntegrityError:\n        pass\n\n    return redirect(group.local_path)\n\n\n@require_POST\n@login_required\ndef reject_membership(request):\n    \"\"\"reject an invitation to join a group\"\"\"\n    group = get_object_or_404(models.Group, id=request.POST.get(\"group\"))\n    invite = get_object_or_404(\n        models.GroupMemberInvitation, group=group, user=request.user\n    )\n\n    invite.reject()\n    return redirect(request.user.local_path)\n", "path": "bookwyrm/views/group.py"}], "after_files": [{"content": "\"\"\"group views\"\"\"\nfrom django.apps import apps\nfrom django.contrib.auth.decorators import login_required\nfrom django.db import IntegrityError\nfrom django.core.paginator import Paginator\nfrom django.http import HttpResponseBadRequest\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.template.response import TemplateResponse\nfrom django.utils.decorators import method_decorator\nfrom django.views import View\nfrom django.views.decorators.http import require_POST\nfrom django.contrib.postgres.search import TrigramSimilarity\nfrom django.db.models.functions import Greatest\n\nfrom bookwyrm import forms, models\nfrom bookwyrm.suggested_users import suggested_users\nfrom .helpers import get_user_from_username\n\n# pylint: disable=no-self-use\nclass Group(View):\n    \"\"\"group page\"\"\"\n\n    def get(self, request, group_id):\n        \"\"\"display a group\"\"\"\n\n        group = get_object_or_404(models.Group, id=group_id)\n        group.raise_visible_to_user(request.user)\n        lists = (\n            models.List.privacy_filter(request.user)\n            .filter(group=group)\n            .order_by(\"-updated_date\")\n        )\n\n        data = {\n            \"group\": group,\n            \"lists\": lists,\n            \"group_form\": forms.GroupForm(instance=group, auto_id=\"group_form_id_%s\"),\n            \"list_form\": forms.ListForm(),\n            \"path\": \"/group\",\n        }\n        return TemplateResponse(request, \"groups/group.html\", data)\n\n    @method_decorator(login_required, name=\"dispatch\")\n    def post(self, request, group_id):\n        \"\"\"edit a group\"\"\"\n        user_group = get_object_or_404(models.Group, id=group_id)\n        form = forms.GroupForm(request.POST, instance=user_group)\n        if not form.is_valid():\n            return redirect(\"group\", user_group.id)\n        user_group = form.save()\n\n        # let the other members know something about the group changed\n        memberships = models.GroupMember.objects.filter(group=user_group)\n        model = apps.get_model(\"bookwyrm.Notification\", require_ready=True)\n        for field in form.changed_data:\n            notification_type = (\n                \"GROUP_PRIVACY\"\n                if field == \"privacy\"\n                else \"GROUP_NAME\"\n                if field == \"name\"\n                else \"GROUP_DESCRIPTION\"\n                if field == \"description\"\n                else None\n            )\n            if notification_type:\n                for membership in memberships:\n                    member = membership.user\n                    if member != request.user:\n                        model.objects.create(\n                            user=member,\n                            related_user=request.user,\n                            related_group=user_group,\n                            notification_type=notification_type,\n                        )\n\n        return redirect(\"group\", user_group.id)\n\n\n@method_decorator(login_required, name=\"dispatch\")\nclass UserGroups(View):\n    \"\"\"a user's groups page\"\"\"\n\n    def get(self, request, username):\n        \"\"\"display a group\"\"\"\n        user = get_user_from_username(request.user, username)\n        groups = (\n            models.Group.privacy_filter(request.user)\n            .filter(memberships__user=user)\n            .order_by(\"-updated_date\")\n        )\n        paginated = Paginator(groups, 12)\n\n        data = {\n            \"groups\": paginated.get_page(request.GET.get(\"page\")),\n            \"is_self\": request.user.id == user.id,\n            \"user\": user,\n            \"group_form\": forms.GroupForm(),\n            \"path\": user.local_path + \"/group\",\n        }\n        return TemplateResponse(request, \"user/groups.html\", data)\n\n    @method_decorator(login_required, name=\"dispatch\")\n    # pylint: disable=unused-argument\n    def post(self, request, username):\n        \"\"\"create a user group\"\"\"\n        form = forms.GroupForm(request.POST)\n        if not form.is_valid():\n            return redirect(request.user.local_path + \"/groups\")\n        group = form.save()\n        # add the creator as a group member\n        models.GroupMember.objects.create(group=group, user=request.user)\n        return redirect(\"group\", group.id)\n\n\n@method_decorator(login_required, name=\"dispatch\")\nclass FindUsers(View):\n    \"\"\"find friends to add to your group\"\"\"\n\n    # this is mostly borrowed from the Get Started friend finder\n\n    def get(self, request, group_id):\n        \"\"\"basic profile info\"\"\"\n        user_query = request.GET.get(\"user_query\")\n        group = get_object_or_404(models.Group, id=group_id)\n        lists = (\n            models.List.privacy_filter(request.user)\n            .filter(group=group)\n            .order_by(\"-updated_date\")\n        )\n\n        if not group:\n            return HttpResponseBadRequest()\n\n        if not group.user == request.user:\n            return HttpResponseBadRequest()\n\n        user_results = (\n            models.User.viewer_aware_objects(request.user)\n            .exclude(\n                memberships__in=group.memberships.all()\n            )  # don't suggest users who are already members\n            .annotate(\n                similarity=Greatest(\n                    TrigramSimilarity(\"username\", user_query),\n                    TrigramSimilarity(\"localname\", user_query),\n                )\n            )\n            .filter(similarity__gt=0.5, local=True)\n            .order_by(\"-similarity\")[:5]\n        )\n        no_results = not user_results\n\n        if user_results.count() < 5:\n            user_results = list(user_results) + suggested_users.get_suggestions(\n                request.user, local=True\n            )\n\n        data = {\n            \"suggested_users\": user_results,\n            \"no_results\": no_results,\n            \"group\": group,\n            \"lists\": lists,\n            \"group_form\": forms.GroupForm(instance=group, auto_id=\"group_form_id_%s\"),\n            \"list_form\": forms.ListForm(),\n            \"user_query\": user_query,\n            \"requestor_is_manager\": request.user == group.user,\n        }\n        return TemplateResponse(request, \"groups/find_users.html\", data)\n\n\n@require_POST\n@login_required\ndef delete_group(request, group_id):\n    \"\"\"delete a group\"\"\"\n    group = get_object_or_404(models.Group, id=group_id)\n\n    # only the owner can delete a group\n    group.raise_not_deletable(request.user)\n\n    # deal with any group lists\n    models.List.objects.filter(group=group).update(curation=\"closed\", group=None)\n\n    group.delete()\n    return redirect(request.user.local_path + \"/groups\")\n\n\n@require_POST\n@login_required\ndef invite_member(request):\n    \"\"\"invite a member to the group\"\"\"\n    group = get_object_or_404(models.Group, id=request.POST.get(\"group\"))\n    user = get_user_from_username(request.user, request.POST[\"user\"])\n\n    if not group.user == request.user:\n        return HttpResponseBadRequest()\n\n    try:\n        models.GroupMemberInvitation.objects.create(user=user, group=group)\n    except IntegrityError:\n        pass\n\n    return redirect(user.local_path)\n\n\n@require_POST\n@login_required\ndef remove_member(request):\n    \"\"\"remove a member from the group\"\"\"\n    group = get_object_or_404(models.Group, id=request.POST.get(\"group\"))\n    user = get_user_from_username(request.user, request.POST[\"user\"])\n\n    # you can't be removed from your own group\n    if user == group.user:\n        return HttpResponseBadRequest()\n\n    is_member = models.GroupMember.objects.filter(group=group, user=user).exists()\n    is_invited = models.GroupMemberInvitation.objects.filter(\n        group=group, user=user\n    ).exists()\n\n    if is_invited:\n        try:\n            invitation = models.GroupMemberInvitation.objects.get(\n                user=user, group=group\n            )\n\n            invitation.reject()\n\n        except IntegrityError:\n            pass\n\n    if is_member:\n        try:\n            models.List.remove_from_group(group.user, user)\n            models.GroupMember.remove(group.user, user)\n        except IntegrityError:\n            pass\n\n        memberships = models.GroupMember.objects.filter(group=group)\n        model = apps.get_model(\"bookwyrm.Notification\", require_ready=True)\n        notification_type = \"LEAVE\" if user == request.user else \"REMOVE\"\n        # let the other members know about it\n        for membership in memberships:\n            member = membership.user\n            if member != request.user:\n                model.objects.create(\n                    user=member,\n                    related_user=user,\n                    related_group=group,\n                    notification_type=notification_type,\n                )\n\n        # let the user (now ex-member) know as well, if they were removed\n        if notification_type == \"REMOVE\":\n            model.objects.create(\n                user=user,\n                related_group=group,\n                notification_type=notification_type,\n            )\n\n    return redirect(group.local_path)\n\n\n@require_POST\n@login_required\ndef accept_membership(request):\n    \"\"\"accept an invitation to join a group\"\"\"\n    group = get_object_or_404(models.Group, id=request.POST.get(\"group\"))\n    invite = get_object_or_404(\n        models.GroupMemberInvitation, group=group, user=request.user\n    )\n\n    try:\n        invite.accept()\n    except IntegrityError:\n        pass\n\n    return redirect(group.local_path)\n\n\n@require_POST\n@login_required\ndef reject_membership(request):\n    \"\"\"reject an invitation to join a group\"\"\"\n    group = get_object_or_404(models.Group, id=request.POST.get(\"group\"))\n    invite = get_object_or_404(\n        models.GroupMemberInvitation, group=group, user=request.user\n    )\n\n    invite.reject()\n    return redirect(request.user.local_path)\n", "path": "bookwyrm/views/group.py"}]}