We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Can not change link from free to lendable
**Describe the bug**
I tried to correct a link on a book from "free" to lendable in the tinkliest. This throws a 500 error. I get this form the logs

```equest, *callback_args, **callback_kwargs)
  File "/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/views/generic/base.py", line 98, in dispatch
    return handler(request, *args, **kwargs)
  File "/home/wyrm/bookwyrm/bookwyrm/views/books/links.py", line 37, in post
    form.save(request)
  File "/home/wyrm/bookwyrm/bookwyrm/forms/custom_form.py", line 36, in save
    return super().save(*args, **kwargs)
  File "/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/forms/models.py", line 460, in save
    raise ValueError(
ValueError: The FileLink could not be changed because the data didn't validate.
```
**To Reproduce**
Go to Link list of a book
Change link action fromm anything to anything

**Expected behavior**
Link is saved. 

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Instance**
wyrm.jascha.wtf, version 5.0.1
local instance on main branch



--- 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/forms/links.py`
Content:
```
1 """ using django model forms """
2 from urllib.parse import urlparse
3 
4 from django.utils.translation import gettext_lazy as _
5 
6 from bookwyrm import models
7 from .custom_form import CustomForm
8 
9 
10 # pylint: disable=missing-class-docstring
11 class LinkDomainForm(CustomForm):
12     class Meta:
13         model = models.LinkDomain
14         fields = ["name"]
15 
16 
17 class FileLinkForm(CustomForm):
18     class Meta:
19         model = models.FileLink
20         fields = ["url", "filetype", "availability", "book", "added_by"]
21 
22     def clean(self):
23         """make sure the domain isn't blocked or pending"""
24         cleaned_data = super().clean()
25         url = cleaned_data.get("url")
26         filetype = cleaned_data.get("filetype")
27         book = cleaned_data.get("book")
28         domain = urlparse(url).netloc
29         if models.LinkDomain.objects.filter(domain=domain).exists():
30             status = models.LinkDomain.objects.get(domain=domain).status
31             if status == "blocked":
32                 # pylint: disable=line-too-long
33                 self.add_error(
34                     "url",
35                     _(
36                         "This domain is blocked. Please contact your administrator if you think this is an error."
37                     ),
38                 )
39             elif models.FileLink.objects.filter(
40                 url=url, book=book, filetype=filetype
41             ).exists():
42                 # pylint: disable=line-too-long
43                 self.add_error(
44                     "url",
45                     _(
46                         "This link with file type has already been added for this book. If it is not visible, the domain is still pending."
47                     ),
48                 )
49
```

Path: `bookwyrm/views/books/links.py`
Content:
```
1 """ the good stuff! the books! """
2 from django.contrib.auth.decorators import login_required, permission_required
3 from django.db import transaction
4 from django.shortcuts import get_object_or_404, redirect
5 from django.template.response import TemplateResponse
6 from django.views import View
7 from django.utils.decorators import method_decorator
8 from django.views.decorators.http import require_POST
9 
10 from bookwyrm import forms, models
11 
12 
13 # pylint: disable=no-self-use
14 @method_decorator(login_required, name="dispatch")
15 @method_decorator(
16     permission_required("bookwyrm.edit_book", raise_exception=True), name="dispatch"
17 )
18 class BookFileLinks(View):
19     """View all links"""
20 
21     def get(self, request, book_id):
22         """view links"""
23         book = get_object_or_404(models.Edition, id=book_id)
24         links = book.file_links.order_by("domain__status", "created_date")
25         annotated_links = []
26         for link in links.all():
27             link.form = forms.FileLinkForm(instance=link)
28             annotated_links.append(link)
29 
30         data = {"book": book, "links": annotated_links}
31         return TemplateResponse(request, "book/file_links/edit_links.html", data)
32 
33     def post(self, request, book_id, link_id):
34         """Edit a link"""
35         link = get_object_or_404(models.FileLink, id=link_id, book=book_id)
36         form = forms.FileLinkForm(request.POST, instance=link)
37         form.save(request)
38         return self.get(request, book_id)
39 
40 
41 @require_POST
42 @login_required
43 # pylint: disable=unused-argument
44 def delete_link(request, book_id, link_id):
45     """delete link"""
46     link = get_object_or_404(models.FileLink, id=link_id, book=book_id)
47     link.delete()
48     return redirect("file-link", book_id)
49 
50 
51 @method_decorator(login_required, name="dispatch")
52 @method_decorator(
53     permission_required("bookwyrm.edit_book", raise_exception=True), name="dispatch"
54 )
55 class AddFileLink(View):
56     """a book! this is the stuff"""
57 
58     def get(self, request, book_id):
59         """Create link form"""
60         book = get_object_or_404(models.Edition, id=book_id)
61         data = {
62             "file_link_form": forms.FileLinkForm(),
63             "book": book,
64         }
65         return TemplateResponse(request, "book/file_links/file_link_page.html", data)
66 
67     @transaction.atomic
68     def post(self, request, book_id, link_id=None):
69         """Add a link to a copy of the book you can read"""
70         book = get_object_or_404(models.Book.objects.select_subclasses(), id=book_id)
71         link = get_object_or_404(models.FileLink, id=link_id) if link_id else None
72         form = forms.FileLinkForm(request.POST, instance=link)
73         if not form.is_valid():
74             data = {"file_link_form": form, "book": book}
75             return TemplateResponse(
76                 request, "book/file_links/file_link_page.html", data
77             )
78 
79         link = form.save(request)
80         book.file_links.add(link)
81         book.last_edited_by = request.user
82         book.save()
83         return redirect("book", book.id)
84
```
--- 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/forms/links.py b/bookwyrm/forms/links.py
--- a/bookwyrm/forms/links.py
+++ b/bookwyrm/forms/links.py
@@ -36,13 +36,16 @@
                         "This domain is blocked. Please contact your administrator if you think this is an error."
                     ),
                 )
-            elif models.FileLink.objects.filter(
+        if (
+            not self.instance
+            and models.FileLink.objects.filter(
                 url=url, book=book, filetype=filetype
-            ).exists():
-                # pylint: disable=line-too-long
-                self.add_error(
-                    "url",
-                    _(
-                        "This link with file type has already been added for this book. If it is not visible, the domain is still pending."
-                    ),
-                )
+            ).exists()
+        ):
+            # pylint: disable=line-too-long
+            self.add_error(
+                "url",
+                _(
+                    "This link with file type has already been added for this book. If it is not visible, the domain is still pending."
+                ),
+            )
diff --git a/bookwyrm/views/books/links.py b/bookwyrm/views/books/links.py
--- a/bookwyrm/views/books/links.py
+++ b/bookwyrm/views/books/links.py
@@ -21,11 +21,7 @@
     def get(self, request, book_id):
         """view links"""
         book = get_object_or_404(models.Edition, id=book_id)
-        links = book.file_links.order_by("domain__status", "created_date")
-        annotated_links = []
-        for link in links.all():
-            link.form = forms.FileLinkForm(instance=link)
-            annotated_links.append(link)
+        annotated_links = get_annotated_links(book)
 
         data = {"book": book, "links": annotated_links}
         return TemplateResponse(request, "book/file_links/edit_links.html", data)
@@ -34,8 +30,30 @@
         """Edit a link"""
         link = get_object_or_404(models.FileLink, id=link_id, book=book_id)
         form = forms.FileLinkForm(request.POST, instance=link)
-        form.save(request)
-        return self.get(request, book_id)
+        if form.is_valid():
+            form.save(request)
+            return redirect("file-link", book_id)
+
+        # this form shouldn't ever really get here, since it's just a dropdown
+        # get the data again rather than redirecting
+        book = get_object_or_404(models.Edition, id=book_id)
+        annotated_links = get_annotated_links(book, form=form)
+
+        data = {"book": book, "links": annotated_links}
+        return TemplateResponse(request, "book/file_links/edit_links.html", data)
+
+
+def get_annotated_links(book, form=None):
+    """The links for this book, plus the forms to edit those links"""
+    links = book.file_links.order_by("domain__status", "created_date")
+    annotated_links = []
+    for link in links.all():
+        if form and link.id == form.instance.id:
+            link.form = form
+        else:
+            link.form = forms.FileLinkForm(instance=link)
+        annotated_links.append(link)
+    return annotated_links
 
 
 @require_POST

{"golden_diff": "diff --git a/bookwyrm/forms/links.py b/bookwyrm/forms/links.py\n--- a/bookwyrm/forms/links.py\n+++ b/bookwyrm/forms/links.py\n@@ -36,13 +36,16 @@\n                         \"This domain is blocked. Please contact your administrator if you think this is an error.\"\n                     ),\n                 )\n-            elif models.FileLink.objects.filter(\n+        if (\n+            not self.instance\n+            and models.FileLink.objects.filter(\n                 url=url, book=book, filetype=filetype\n-            ).exists():\n-                # pylint: disable=line-too-long\n-                self.add_error(\n-                    \"url\",\n-                    _(\n-                        \"This link with file type has already been added for this book. If it is not visible, the domain is still pending.\"\n-                    ),\n-                )\n+            ).exists()\n+        ):\n+            # pylint: disable=line-too-long\n+            self.add_error(\n+                \"url\",\n+                _(\n+                    \"This link with file type has already been added for this book. If it is not visible, the domain is still pending.\"\n+                ),\n+            )\ndiff --git a/bookwyrm/views/books/links.py b/bookwyrm/views/books/links.py\n--- a/bookwyrm/views/books/links.py\n+++ b/bookwyrm/views/books/links.py\n@@ -21,11 +21,7 @@\n     def get(self, request, book_id):\n         \"\"\"view links\"\"\"\n         book = get_object_or_404(models.Edition, id=book_id)\n-        links = book.file_links.order_by(\"domain__status\", \"created_date\")\n-        annotated_links = []\n-        for link in links.all():\n-            link.form = forms.FileLinkForm(instance=link)\n-            annotated_links.append(link)\n+        annotated_links = get_annotated_links(book)\n \n         data = {\"book\": book, \"links\": annotated_links}\n         return TemplateResponse(request, \"book/file_links/edit_links.html\", data)\n@@ -34,8 +30,30 @@\n         \"\"\"Edit a link\"\"\"\n         link = get_object_or_404(models.FileLink, id=link_id, book=book_id)\n         form = forms.FileLinkForm(request.POST, instance=link)\n-        form.save(request)\n-        return self.get(request, book_id)\n+        if form.is_valid():\n+            form.save(request)\n+            return redirect(\"file-link\", book_id)\n+\n+        # this form shouldn't ever really get here, since it's just a dropdown\n+        # get the data again rather than redirecting\n+        book = get_object_or_404(models.Edition, id=book_id)\n+        annotated_links = get_annotated_links(book, form=form)\n+\n+        data = {\"book\": book, \"links\": annotated_links}\n+        return TemplateResponse(request, \"book/file_links/edit_links.html\", data)\n+\n+\n+def get_annotated_links(book, form=None):\n+    \"\"\"The links for this book, plus the forms to edit those links\"\"\"\n+    links = book.file_links.order_by(\"domain__status\", \"created_date\")\n+    annotated_links = []\n+    for link in links.all():\n+        if form and link.id == form.instance.id:\n+            link.form = form\n+        else:\n+            link.form = forms.FileLinkForm(instance=link)\n+        annotated_links.append(link)\n+    return annotated_links\n \n \n @require_POST\n", "issue": "Can not change link from free to lendable\n**Describe the bug**\r\nI tried to correct a link on a book from \"free\" to lendable in the tinkliest. This throws a 500 error. I get this form the logs\r\n\r\n```equest, *callback_args, **callback_kwargs)\r\n  File \"/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/views/generic/base.py\", line 70, in view\r\n    return self.dispatch(request, *args, **kwargs)\r\n  File \"/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/utils/decorators.py\", line 43, in _wrapper\r\n    return bound_method(*args, **kwargs)\r\n  File \"/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/contrib/auth/decorators.py\", line 21, in _wrapped_view\r\n    return view_func(request, *args, **kwargs)\r\n  File \"/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/utils/decorators.py\", line 43, in _wrapper\r\n    return bound_method(*args, **kwargs)\r\n  File \"/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/contrib/auth/decorators.py\", line 21, in _wrapped_view\r\n    return view_func(request, *args, **kwargs)\r\n  File \"/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/views/generic/base.py\", line 98, in dispatch\r\n    return handler(request, *args, **kwargs)\r\n  File \"/home/wyrm/bookwyrm/bookwyrm/views/books/links.py\", line 37, in post\r\n    form.save(request)\r\n  File \"/home/wyrm/bookwyrm/bookwyrm/forms/custom_form.py\", line 36, in save\r\n    return super().save(*args, **kwargs)\r\n  File \"/home/wyrm/bookwyrm/venv/lib64/python3.9/site-packages/django/forms/models.py\", line 460, in save\r\n    raise ValueError(\r\nValueError: The FileLink could not be changed because the data didn't validate.\r\n```\r\n**To Reproduce**\r\nGo to Link list of a book\r\nChange link action fromm anything to anything\r\n\r\n**Expected behavior**\r\nLink is saved. \r\n\r\n**Screenshots**\r\nIf applicable, add screenshots to help explain your problem.\r\n\r\n**Instance**\r\nwyrm.jascha.wtf, version 5.0.1\r\nlocal instance on main branch\r\n\r\n\n", "before_files": [{"content": "\"\"\" using django model forms \"\"\"\nfrom urllib.parse import urlparse\n\nfrom django.utils.translation import gettext_lazy as _\n\nfrom bookwyrm import models\nfrom .custom_form import CustomForm\n\n\n# pylint: disable=missing-class-docstring\nclass LinkDomainForm(CustomForm):\n    class Meta:\n        model = models.LinkDomain\n        fields = [\"name\"]\n\n\nclass FileLinkForm(CustomForm):\n    class Meta:\n        model = models.FileLink\n        fields = [\"url\", \"filetype\", \"availability\", \"book\", \"added_by\"]\n\n    def clean(self):\n        \"\"\"make sure the domain isn't blocked or pending\"\"\"\n        cleaned_data = super().clean()\n        url = cleaned_data.get(\"url\")\n        filetype = cleaned_data.get(\"filetype\")\n        book = cleaned_data.get(\"book\")\n        domain = urlparse(url).netloc\n        if models.LinkDomain.objects.filter(domain=domain).exists():\n            status = models.LinkDomain.objects.get(domain=domain).status\n            if status == \"blocked\":\n                # pylint: disable=line-too-long\n                self.add_error(\n                    \"url\",\n                    _(\n                        \"This domain is blocked. Please contact your administrator if you think this is an error.\"\n                    ),\n                )\n            elif models.FileLink.objects.filter(\n                url=url, book=book, filetype=filetype\n            ).exists():\n                # pylint: disable=line-too-long\n                self.add_error(\n                    \"url\",\n                    _(\n                        \"This link with file type has already been added for this book. If it is not visible, the domain is still pending.\"\n                    ),\n                )\n", "path": "bookwyrm/forms/links.py"}, {"content": "\"\"\" the good stuff! the books! \"\"\"\nfrom django.contrib.auth.decorators import login_required, permission_required\nfrom django.db import transaction\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.template.response import TemplateResponse\nfrom django.views import View\nfrom django.utils.decorators import method_decorator\nfrom django.views.decorators.http import require_POST\n\nfrom bookwyrm import forms, models\n\n\n# pylint: disable=no-self-use\n@method_decorator(login_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"bookwyrm.edit_book\", raise_exception=True), name=\"dispatch\"\n)\nclass BookFileLinks(View):\n    \"\"\"View all links\"\"\"\n\n    def get(self, request, book_id):\n        \"\"\"view links\"\"\"\n        book = get_object_or_404(models.Edition, id=book_id)\n        links = book.file_links.order_by(\"domain__status\", \"created_date\")\n        annotated_links = []\n        for link in links.all():\n            link.form = forms.FileLinkForm(instance=link)\n            annotated_links.append(link)\n\n        data = {\"book\": book, \"links\": annotated_links}\n        return TemplateResponse(request, \"book/file_links/edit_links.html\", data)\n\n    def post(self, request, book_id, link_id):\n        \"\"\"Edit a link\"\"\"\n        link = get_object_or_404(models.FileLink, id=link_id, book=book_id)\n        form = forms.FileLinkForm(request.POST, instance=link)\n        form.save(request)\n        return self.get(request, book_id)\n\n\n@require_POST\n@login_required\n# pylint: disable=unused-argument\ndef delete_link(request, book_id, link_id):\n    \"\"\"delete link\"\"\"\n    link = get_object_or_404(models.FileLink, id=link_id, book=book_id)\n    link.delete()\n    return redirect(\"file-link\", book_id)\n\n\n@method_decorator(login_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"bookwyrm.edit_book\", raise_exception=True), name=\"dispatch\"\n)\nclass AddFileLink(View):\n    \"\"\"a book! this is the stuff\"\"\"\n\n    def get(self, request, book_id):\n        \"\"\"Create link form\"\"\"\n        book = get_object_or_404(models.Edition, id=book_id)\n        data = {\n            \"file_link_form\": forms.FileLinkForm(),\n            \"book\": book,\n        }\n        return TemplateResponse(request, \"book/file_links/file_link_page.html\", data)\n\n    @transaction.atomic\n    def post(self, request, book_id, link_id=None):\n        \"\"\"Add a link to a copy of the book you can read\"\"\"\n        book = get_object_or_404(models.Book.objects.select_subclasses(), id=book_id)\n        link = get_object_or_404(models.FileLink, id=link_id) if link_id else None\n        form = forms.FileLinkForm(request.POST, instance=link)\n        if not form.is_valid():\n            data = {\"file_link_form\": form, \"book\": book}\n            return TemplateResponse(\n                request, \"book/file_links/file_link_page.html\", data\n            )\n\n        link = form.save(request)\n        book.file_links.add(link)\n        book.last_edited_by = request.user\n        book.save()\n        return redirect(\"book\", book.id)\n", "path": "bookwyrm/views/books/links.py"}], "after_files": [{"content": "\"\"\" using django model forms \"\"\"\nfrom urllib.parse import urlparse\n\nfrom django.utils.translation import gettext_lazy as _\n\nfrom bookwyrm import models\nfrom .custom_form import CustomForm\n\n\n# pylint: disable=missing-class-docstring\nclass LinkDomainForm(CustomForm):\n    class Meta:\n        model = models.LinkDomain\n        fields = [\"name\"]\n\n\nclass FileLinkForm(CustomForm):\n    class Meta:\n        model = models.FileLink\n        fields = [\"url\", \"filetype\", \"availability\", \"book\", \"added_by\"]\n\n    def clean(self):\n        \"\"\"make sure the domain isn't blocked or pending\"\"\"\n        cleaned_data = super().clean()\n        url = cleaned_data.get(\"url\")\n        filetype = cleaned_data.get(\"filetype\")\n        book = cleaned_data.get(\"book\")\n        domain = urlparse(url).netloc\n        if models.LinkDomain.objects.filter(domain=domain).exists():\n            status = models.LinkDomain.objects.get(domain=domain).status\n            if status == \"blocked\":\n                # pylint: disable=line-too-long\n                self.add_error(\n                    \"url\",\n                    _(\n                        \"This domain is blocked. Please contact your administrator if you think this is an error.\"\n                    ),\n                )\n        if (\n            not self.instance\n            and models.FileLink.objects.filter(\n                url=url, book=book, filetype=filetype\n            ).exists()\n        ):\n            # pylint: disable=line-too-long\n            self.add_error(\n                \"url\",\n                _(\n                    \"This link with file type has already been added for this book. If it is not visible, the domain is still pending.\"\n                ),\n            )\n", "path": "bookwyrm/forms/links.py"}, {"content": "\"\"\" the good stuff! the books! \"\"\"\nfrom django.contrib.auth.decorators import login_required, permission_required\nfrom django.db import transaction\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.template.response import TemplateResponse\nfrom django.views import View\nfrom django.utils.decorators import method_decorator\nfrom django.views.decorators.http import require_POST\n\nfrom bookwyrm import forms, models\n\n\n# pylint: disable=no-self-use\n@method_decorator(login_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"bookwyrm.edit_book\", raise_exception=True), name=\"dispatch\"\n)\nclass BookFileLinks(View):\n    \"\"\"View all links\"\"\"\n\n    def get(self, request, book_id):\n        \"\"\"view links\"\"\"\n        book = get_object_or_404(models.Edition, id=book_id)\n        annotated_links = get_annotated_links(book)\n\n        data = {\"book\": book, \"links\": annotated_links}\n        return TemplateResponse(request, \"book/file_links/edit_links.html\", data)\n\n    def post(self, request, book_id, link_id):\n        \"\"\"Edit a link\"\"\"\n        link = get_object_or_404(models.FileLink, id=link_id, book=book_id)\n        form = forms.FileLinkForm(request.POST, instance=link)\n        if form.is_valid():\n            form.save(request)\n            return redirect(\"file-link\", book_id)\n\n        # this form shouldn't ever really get here, since it's just a dropdown\n        # get the data again rather than redirecting\n        book = get_object_or_404(models.Edition, id=book_id)\n        annotated_links = get_annotated_links(book, form=form)\n\n        data = {\"book\": book, \"links\": annotated_links}\n        return TemplateResponse(request, \"book/file_links/edit_links.html\", data)\n\n\ndef get_annotated_links(book, form=None):\n    \"\"\"The links for this book, plus the forms to edit those links\"\"\"\n    links = book.file_links.order_by(\"domain__status\", \"created_date\")\n    annotated_links = []\n    for link in links.all():\n        if form and link.id == form.instance.id:\n            link.form = form\n        else:\n            link.form = forms.FileLinkForm(instance=link)\n        annotated_links.append(link)\n    return annotated_links\n\n\n@require_POST\n@login_required\n# pylint: disable=unused-argument\ndef delete_link(request, book_id, link_id):\n    \"\"\"delete link\"\"\"\n    link = get_object_or_404(models.FileLink, id=link_id, book=book_id)\n    link.delete()\n    return redirect(\"file-link\", book_id)\n\n\n@method_decorator(login_required, name=\"dispatch\")\n@method_decorator(\n    permission_required(\"bookwyrm.edit_book\", raise_exception=True), name=\"dispatch\"\n)\nclass AddFileLink(View):\n    \"\"\"a book! this is the stuff\"\"\"\n\n    def get(self, request, book_id):\n        \"\"\"Create link form\"\"\"\n        book = get_object_or_404(models.Edition, id=book_id)\n        data = {\n            \"file_link_form\": forms.FileLinkForm(),\n            \"book\": book,\n        }\n        return TemplateResponse(request, \"book/file_links/file_link_page.html\", data)\n\n    @transaction.atomic\n    def post(self, request, book_id, link_id=None):\n        \"\"\"Add a link to a copy of the book you can read\"\"\"\n        book = get_object_or_404(models.Book.objects.select_subclasses(), id=book_id)\n        link = get_object_or_404(models.FileLink, id=link_id) if link_id else None\n        form = forms.FileLinkForm(request.POST, instance=link)\n        if not form.is_valid():\n            data = {\"file_link_form\": form, \"book\": book}\n            return TemplateResponse(\n                request, \"book/file_links/file_link_page.html\", data\n            )\n\n        link = form.save(request)\n        book.file_links.add(link)\n        book.last_edited_by = request.user\n        book.save()\n        return redirect(\"book\", book.id)\n", "path": "bookwyrm/views/books/links.py"}]}