We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Editing an author deletes author data not in form
**Describe the bug**
Editing an author saves the form data as an update. `isni` and `viaf_id` are not part of the form, so these values are deleted when the author details are edited.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to 'author page'
2. Click on 'Edit'
3. Make a change
4. Save
5. Most users will not see any change because these values are not displayed, however they have been removed.

**Expected behavior**
Only values that have changed should change. ISNI and VIAF data should be retained on edits.

**Instance**
Development site but it presumably would be replicated on any production server.

**Additional context**
I only noticed this because I am working on a PR that will display a link to the ISNI record, and it disappears whenever I edit an author.



Editing an author deletes author data not in form
**Describe the bug**
Editing an author saves the form data as an update. `isni` and `viaf_id` are not part of the form, so these values are deleted when the author details are edited.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to 'author page'
2. Click on 'Edit'
3. Make a change
4. Save
5. Most users will not see any change because these values are not displayed, however they have been removed.

**Expected behavior**
Only values that have changed should change. ISNI and VIAF data should be retained on edits.

**Instance**
Development site but it presumably would be replicated on any production server.

**Additional context**
I only noticed this because I am working on a PR that will display a link to the ISNI record, and it disappears whenever I edit an author.




--- 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.py`
Content:
```
1 """ using django model forms """
2 import datetime
3 from collections import defaultdict
4 
5 from django import forms
6 from django.forms import ModelForm, PasswordInput, widgets, ChoiceField
7 from django.forms.widgets import Textarea
8 from django.utils import timezone
9 from django.utils.translation import gettext_lazy as _
10 
11 from bookwyrm import models
12 
13 
14 class CustomForm(ModelForm):
15     """add css classes to the forms"""
16 
17     def __init__(self, *args, **kwargs):
18         css_classes = defaultdict(lambda: "")
19         css_classes["text"] = "input"
20         css_classes["password"] = "input"
21         css_classes["email"] = "input"
22         css_classes["number"] = "input"
23         css_classes["checkbox"] = "checkbox"
24         css_classes["textarea"] = "textarea"
25         # pylint: disable=super-with-arguments
26         super(CustomForm, self).__init__(*args, **kwargs)
27         for visible in self.visible_fields():
28             if hasattr(visible.field.widget, "input_type"):
29                 input_type = visible.field.widget.input_type
30             if isinstance(visible.field.widget, Textarea):
31                 input_type = "textarea"
32                 visible.field.widget.attrs["rows"] = 5
33             visible.field.widget.attrs["class"] = css_classes[input_type]
34 
35 
36 # pylint: disable=missing-class-docstring
37 class LoginForm(CustomForm):
38     class Meta:
39         model = models.User
40         fields = ["localname", "password"]
41         help_texts = {f: None for f in fields}
42         widgets = {
43             "password": PasswordInput(),
44         }
45 
46 
47 class RegisterForm(CustomForm):
48     class Meta:
49         model = models.User
50         fields = ["localname", "email", "password"]
51         help_texts = {f: None for f in fields}
52         widgets = {"password": PasswordInput()}
53 
54 
55 class RatingForm(CustomForm):
56     class Meta:
57         model = models.ReviewRating
58         fields = ["user", "book", "rating", "privacy"]
59 
60 
61 class ReviewForm(CustomForm):
62     class Meta:
63         model = models.Review
64         fields = [
65             "user",
66             "book",
67             "name",
68             "content",
69             "rating",
70             "content_warning",
71             "sensitive",
72             "privacy",
73         ]
74 
75 
76 class CommentForm(CustomForm):
77     class Meta:
78         model = models.Comment
79         fields = [
80             "user",
81             "book",
82             "content",
83             "content_warning",
84             "sensitive",
85             "privacy",
86             "progress",
87             "progress_mode",
88             "reading_status",
89         ]
90 
91 
92 class QuotationForm(CustomForm):
93     class Meta:
94         model = models.Quotation
95         fields = [
96             "user",
97             "book",
98             "quote",
99             "content",
100             "content_warning",
101             "sensitive",
102             "privacy",
103             "position",
104             "position_mode",
105         ]
106 
107 
108 class ReplyForm(CustomForm):
109     class Meta:
110         model = models.Status
111         fields = [
112             "user",
113             "content",
114             "content_warning",
115             "sensitive",
116             "reply_parent",
117             "privacy",
118         ]
119 
120 
121 class StatusForm(CustomForm):
122     class Meta:
123         model = models.Status
124         fields = ["user", "content", "content_warning", "sensitive", "privacy"]
125 
126 
127 class DirectForm(CustomForm):
128     class Meta:
129         model = models.Status
130         fields = ["user", "content", "content_warning", "sensitive", "privacy"]
131 
132 
133 class EditUserForm(CustomForm):
134     class Meta:
135         model = models.User
136         fields = [
137             "avatar",
138             "name",
139             "email",
140             "summary",
141             "show_goal",
142             "show_suggested_users",
143             "manually_approves_followers",
144             "default_post_privacy",
145             "discoverable",
146             "preferred_timezone",
147             "preferred_language",
148         ]
149         help_texts = {f: None for f in fields}
150 
151 
152 class LimitedEditUserForm(CustomForm):
153     class Meta:
154         model = models.User
155         fields = [
156             "avatar",
157             "name",
158             "summary",
159             "manually_approves_followers",
160             "discoverable",
161         ]
162         help_texts = {f: None for f in fields}
163 
164 
165 class DeleteUserForm(CustomForm):
166     class Meta:
167         model = models.User
168         fields = ["password"]
169 
170 
171 class UserGroupForm(CustomForm):
172     class Meta:
173         model = models.User
174         fields = ["groups"]
175 
176 
177 class CoverForm(CustomForm):
178     class Meta:
179         model = models.Book
180         fields = ["cover"]
181         help_texts = {f: None for f in fields}
182 
183 
184 class EditionForm(CustomForm):
185     class Meta:
186         model = models.Edition
187         exclude = [
188             "remote_id",
189             "origin_id",
190             "created_date",
191             "updated_date",
192             "edition_rank",
193             "authors",
194             "parent_work",
195             "shelves",
196             "connector",
197             "search_vector",
198         ]
199 
200 
201 class AuthorForm(CustomForm):
202     class Meta:
203         model = models.Author
204         exclude = [
205             "remote_id",
206             "origin_id",
207             "created_date",
208             "updated_date",
209             "search_vector",
210         ]
211 
212 
213 class ImportForm(forms.Form):
214     csv_file = forms.FileField()
215 
216 
217 class ExpiryWidget(widgets.Select):
218     def value_from_datadict(self, data, files, name):
219         """human-readable exiration time buckets"""
220         selected_string = super().value_from_datadict(data, files, name)
221 
222         if selected_string == "day":
223             interval = datetime.timedelta(days=1)
224         elif selected_string == "week":
225             interval = datetime.timedelta(days=7)
226         elif selected_string == "month":
227             interval = datetime.timedelta(days=31)  # Close enough?
228         elif selected_string == "forever":
229             return None
230         else:
231             return selected_string  # This will raise
232 
233         return timezone.now() + interval
234 
235 
236 class InviteRequestForm(CustomForm):
237     def clean(self):
238         """make sure the email isn't in use by a registered user"""
239         cleaned_data = super().clean()
240         email = cleaned_data.get("email")
241         if email and models.User.objects.filter(email=email).exists():
242             self.add_error("email", _("A user with this email already exists."))
243 
244     class Meta:
245         model = models.InviteRequest
246         fields = ["email"]
247 
248 
249 class CreateInviteForm(CustomForm):
250     class Meta:
251         model = models.SiteInvite
252         exclude = ["code", "user", "times_used", "invitees"]
253         widgets = {
254             "expiry": ExpiryWidget(
255                 choices=[
256                     ("day", _("One Day")),
257                     ("week", _("One Week")),
258                     ("month", _("One Month")),
259                     ("forever", _("Does Not Expire")),
260                 ]
261             ),
262             "use_limit": widgets.Select(
263                 choices=[(i, _(f"{i} uses")) for i in [1, 5, 10, 25, 50, 100]]
264                 + [(None, _("Unlimited"))]
265             ),
266         }
267 
268 
269 class ShelfForm(CustomForm):
270     class Meta:
271         model = models.Shelf
272         fields = ["user", "name", "privacy", "description"]
273 
274 
275 class GoalForm(CustomForm):
276     class Meta:
277         model = models.AnnualGoal
278         fields = ["user", "year", "goal", "privacy"]
279 
280 
281 class SiteForm(CustomForm):
282     class Meta:
283         model = models.SiteSettings
284         exclude = []
285 
286 
287 class AnnouncementForm(CustomForm):
288     class Meta:
289         model = models.Announcement
290         exclude = ["remote_id"]
291 
292 
293 class ListForm(CustomForm):
294     class Meta:
295         model = models.List
296         fields = ["user", "name", "description", "curation", "privacy", "group"]
297 
298 
299 class GroupForm(CustomForm):
300     class Meta:
301         model = models.Group
302         fields = ["user", "privacy", "name", "description"]
303 
304 
305 class ReportForm(CustomForm):
306     class Meta:
307         model = models.Report
308         fields = ["user", "reporter", "statuses", "note"]
309 
310 
311 class EmailBlocklistForm(CustomForm):
312     class Meta:
313         model = models.EmailBlocklist
314         fields = ["domain"]
315 
316 
317 class IPBlocklistForm(CustomForm):
318     class Meta:
319         model = models.IPBlocklist
320         fields = ["address"]
321 
322 
323 class ServerForm(CustomForm):
324     class Meta:
325         model = models.FederatedServer
326         exclude = ["remote_id"]
327 
328 
329 class SortListForm(forms.Form):
330     sort_by = ChoiceField(
331         choices=(
332             ("order", _("List Order")),
333             ("title", _("Book Title")),
334             ("rating", _("Rating")),
335         ),
336         label=_("Sort By"),
337     )
338     direction = ChoiceField(
339         choices=(
340             ("ascending", _("Ascending")),
341             ("descending", _("Descending")),
342         ),
343     )
344
```
--- 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.py b/bookwyrm/forms.py
--- a/bookwyrm/forms.py
+++ b/bookwyrm/forms.py
@@ -201,12 +201,18 @@
 class AuthorForm(CustomForm):
     class Meta:
         model = models.Author
-        exclude = [
-            "remote_id",
-            "origin_id",
-            "created_date",
-            "updated_date",
-            "search_vector",
+        fields = [
+            "last_edited_by",
+            "name",
+            "aliases",
+            "bio",
+            "wikipedia_link",
+            "born",
+            "died",
+            "openlibrary_key",
+            "inventaire_id",
+            "librarything_key",
+            "goodreads_key",
         ]

{"golden_diff": "diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py\n--- a/bookwyrm/forms.py\n+++ b/bookwyrm/forms.py\n@@ -201,12 +201,18 @@\n class AuthorForm(CustomForm):\n     class Meta:\n         model = models.Author\n-        exclude = [\n-            \"remote_id\",\n-            \"origin_id\",\n-            \"created_date\",\n-            \"updated_date\",\n-            \"search_vector\",\n+        fields = [\n+            \"last_edited_by\",\n+            \"name\",\n+            \"aliases\",\n+            \"bio\",\n+            \"wikipedia_link\",\n+            \"born\",\n+            \"died\",\n+            \"openlibrary_key\",\n+            \"inventaire_id\",\n+            \"librarything_key\",\n+            \"goodreads_key\",\n         ]\n", "issue": "Editing an author deletes author data not in form\n**Describe the bug**\r\nEditing an author saves the form data as an update. `isni` and `viaf_id` are not part of the form, so these values are deleted when the author details are edited.\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n1. Go to 'author page'\r\n2. Click on 'Edit'\r\n3. Make a change\r\n4. Save\r\n5. Most users will not see any change because these values are not displayed, however they have been removed.\r\n\r\n**Expected behavior**\r\nOnly values that have changed should change. ISNI and VIAF data should be retained on edits.\r\n\r\n**Instance**\r\nDevelopment site but it presumably would be replicated on any production server.\r\n\r\n**Additional context**\r\nI only noticed this because I am working on a PR that will display a link to the ISNI record, and it disappears whenever I edit an author.\r\n\r\n\r\n\nEditing an author deletes author data not in form\n**Describe the bug**\r\nEditing an author saves the form data as an update. `isni` and `viaf_id` are not part of the form, so these values are deleted when the author details are edited.\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n1. Go to 'author page'\r\n2. Click on 'Edit'\r\n3. Make a change\r\n4. Save\r\n5. Most users will not see any change because these values are not displayed, however they have been removed.\r\n\r\n**Expected behavior**\r\nOnly values that have changed should change. ISNI and VIAF data should be retained on edits.\r\n\r\n**Instance**\r\nDevelopment site but it presumably would be replicated on any production server.\r\n\r\n**Additional context**\r\nI only noticed this because I am working on a PR that will display a link to the ISNI record, and it disappears whenever I edit an author.\r\n\r\n\r\n\n", "before_files": [{"content": "\"\"\" using django model forms \"\"\"\nimport datetime\nfrom collections import defaultdict\n\nfrom django import forms\nfrom django.forms import ModelForm, PasswordInput, widgets, ChoiceField\nfrom django.forms.widgets import Textarea\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\n\nfrom bookwyrm import models\n\n\nclass CustomForm(ModelForm):\n    \"\"\"add css classes to the forms\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        css_classes = defaultdict(lambda: \"\")\n        css_classes[\"text\"] = \"input\"\n        css_classes[\"password\"] = \"input\"\n        css_classes[\"email\"] = \"input\"\n        css_classes[\"number\"] = \"input\"\n        css_classes[\"checkbox\"] = \"checkbox\"\n        css_classes[\"textarea\"] = \"textarea\"\n        # pylint: disable=super-with-arguments\n        super(CustomForm, self).__init__(*args, **kwargs)\n        for visible in self.visible_fields():\n            if hasattr(visible.field.widget, \"input_type\"):\n                input_type = visible.field.widget.input_type\n            if isinstance(visible.field.widget, Textarea):\n                input_type = \"textarea\"\n                visible.field.widget.attrs[\"rows\"] = 5\n            visible.field.widget.attrs[\"class\"] = css_classes[input_type]\n\n\n# pylint: disable=missing-class-docstring\nclass LoginForm(CustomForm):\n    class Meta:\n        model = models.User\n        fields = [\"localname\", \"password\"]\n        help_texts = {f: None for f in fields}\n        widgets = {\n            \"password\": PasswordInput(),\n        }\n\n\nclass RegisterForm(CustomForm):\n    class Meta:\n        model = models.User\n        fields = [\"localname\", \"email\", \"password\"]\n        help_texts = {f: None for f in fields}\n        widgets = {\"password\": PasswordInput()}\n\n\nclass RatingForm(CustomForm):\n    class Meta:\n        model = models.ReviewRating\n        fields = [\"user\", \"book\", \"rating\", \"privacy\"]\n\n\nclass ReviewForm(CustomForm):\n    class Meta:\n        model = models.Review\n        fields = [\n            \"user\",\n            \"book\",\n            \"name\",\n            \"content\",\n            \"rating\",\n            \"content_warning\",\n            \"sensitive\",\n            \"privacy\",\n        ]\n\n\nclass CommentForm(CustomForm):\n    class Meta:\n        model = models.Comment\n        fields = [\n            \"user\",\n            \"book\",\n            \"content\",\n            \"content_warning\",\n            \"sensitive\",\n            \"privacy\",\n            \"progress\",\n            \"progress_mode\",\n            \"reading_status\",\n        ]\n\n\nclass QuotationForm(CustomForm):\n    class Meta:\n        model = models.Quotation\n        fields = [\n            \"user\",\n            \"book\",\n            \"quote\",\n            \"content\",\n            \"content_warning\",\n            \"sensitive\",\n            \"privacy\",\n            \"position\",\n            \"position_mode\",\n        ]\n\n\nclass ReplyForm(CustomForm):\n    class Meta:\n        model = models.Status\n        fields = [\n            \"user\",\n            \"content\",\n            \"content_warning\",\n            \"sensitive\",\n            \"reply_parent\",\n            \"privacy\",\n        ]\n\n\nclass StatusForm(CustomForm):\n    class Meta:\n        model = models.Status\n        fields = [\"user\", \"content\", \"content_warning\", \"sensitive\", \"privacy\"]\n\n\nclass DirectForm(CustomForm):\n    class Meta:\n        model = models.Status\n        fields = [\"user\", \"content\", \"content_warning\", \"sensitive\", \"privacy\"]\n\n\nclass EditUserForm(CustomForm):\n    class Meta:\n        model = models.User\n        fields = [\n            \"avatar\",\n            \"name\",\n            \"email\",\n            \"summary\",\n            \"show_goal\",\n            \"show_suggested_users\",\n            \"manually_approves_followers\",\n            \"default_post_privacy\",\n            \"discoverable\",\n            \"preferred_timezone\",\n            \"preferred_language\",\n        ]\n        help_texts = {f: None for f in fields}\n\n\nclass LimitedEditUserForm(CustomForm):\n    class Meta:\n        model = models.User\n        fields = [\n            \"avatar\",\n            \"name\",\n            \"summary\",\n            \"manually_approves_followers\",\n            \"discoverable\",\n        ]\n        help_texts = {f: None for f in fields}\n\n\nclass DeleteUserForm(CustomForm):\n    class Meta:\n        model = models.User\n        fields = [\"password\"]\n\n\nclass UserGroupForm(CustomForm):\n    class Meta:\n        model = models.User\n        fields = [\"groups\"]\n\n\nclass CoverForm(CustomForm):\n    class Meta:\n        model = models.Book\n        fields = [\"cover\"]\n        help_texts = {f: None for f in fields}\n\n\nclass EditionForm(CustomForm):\n    class Meta:\n        model = models.Edition\n        exclude = [\n            \"remote_id\",\n            \"origin_id\",\n            \"created_date\",\n            \"updated_date\",\n            \"edition_rank\",\n            \"authors\",\n            \"parent_work\",\n            \"shelves\",\n            \"connector\",\n            \"search_vector\",\n        ]\n\n\nclass AuthorForm(CustomForm):\n    class Meta:\n        model = models.Author\n        exclude = [\n            \"remote_id\",\n            \"origin_id\",\n            \"created_date\",\n            \"updated_date\",\n            \"search_vector\",\n        ]\n\n\nclass ImportForm(forms.Form):\n    csv_file = forms.FileField()\n\n\nclass ExpiryWidget(widgets.Select):\n    def value_from_datadict(self, data, files, name):\n        \"\"\"human-readable exiration time buckets\"\"\"\n        selected_string = super().value_from_datadict(data, files, name)\n\n        if selected_string == \"day\":\n            interval = datetime.timedelta(days=1)\n        elif selected_string == \"week\":\n            interval = datetime.timedelta(days=7)\n        elif selected_string == \"month\":\n            interval = datetime.timedelta(days=31)  # Close enough?\n        elif selected_string == \"forever\":\n            return None\n        else:\n            return selected_string  # This will raise\n\n        return timezone.now() + interval\n\n\nclass InviteRequestForm(CustomForm):\n    def clean(self):\n        \"\"\"make sure the email isn't in use by a registered user\"\"\"\n        cleaned_data = super().clean()\n        email = cleaned_data.get(\"email\")\n        if email and models.User.objects.filter(email=email).exists():\n            self.add_error(\"email\", _(\"A user with this email already exists.\"))\n\n    class Meta:\n        model = models.InviteRequest\n        fields = [\"email\"]\n\n\nclass CreateInviteForm(CustomForm):\n    class Meta:\n        model = models.SiteInvite\n        exclude = [\"code\", \"user\", \"times_used\", \"invitees\"]\n        widgets = {\n            \"expiry\": ExpiryWidget(\n                choices=[\n                    (\"day\", _(\"One Day\")),\n                    (\"week\", _(\"One Week\")),\n                    (\"month\", _(\"One Month\")),\n                    (\"forever\", _(\"Does Not Expire\")),\n                ]\n            ),\n            \"use_limit\": widgets.Select(\n                choices=[(i, _(f\"{i} uses\")) for i in [1, 5, 10, 25, 50, 100]]\n                + [(None, _(\"Unlimited\"))]\n            ),\n        }\n\n\nclass ShelfForm(CustomForm):\n    class Meta:\n        model = models.Shelf\n        fields = [\"user\", \"name\", \"privacy\", \"description\"]\n\n\nclass GoalForm(CustomForm):\n    class Meta:\n        model = models.AnnualGoal\n        fields = [\"user\", \"year\", \"goal\", \"privacy\"]\n\n\nclass SiteForm(CustomForm):\n    class Meta:\n        model = models.SiteSettings\n        exclude = []\n\n\nclass AnnouncementForm(CustomForm):\n    class Meta:\n        model = models.Announcement\n        exclude = [\"remote_id\"]\n\n\nclass ListForm(CustomForm):\n    class Meta:\n        model = models.List\n        fields = [\"user\", \"name\", \"description\", \"curation\", \"privacy\", \"group\"]\n\n\nclass GroupForm(CustomForm):\n    class Meta:\n        model = models.Group\n        fields = [\"user\", \"privacy\", \"name\", \"description\"]\n\n\nclass ReportForm(CustomForm):\n    class Meta:\n        model = models.Report\n        fields = [\"user\", \"reporter\", \"statuses\", \"note\"]\n\n\nclass EmailBlocklistForm(CustomForm):\n    class Meta:\n        model = models.EmailBlocklist\n        fields = [\"domain\"]\n\n\nclass IPBlocklistForm(CustomForm):\n    class Meta:\n        model = models.IPBlocklist\n        fields = [\"address\"]\n\n\nclass ServerForm(CustomForm):\n    class Meta:\n        model = models.FederatedServer\n        exclude = [\"remote_id\"]\n\n\nclass SortListForm(forms.Form):\n    sort_by = ChoiceField(\n        choices=(\n            (\"order\", _(\"List Order\")),\n            (\"title\", _(\"Book Title\")),\n            (\"rating\", _(\"Rating\")),\n        ),\n        label=_(\"Sort By\"),\n    )\n    direction = ChoiceField(\n        choices=(\n            (\"ascending\", _(\"Ascending\")),\n            (\"descending\", _(\"Descending\")),\n        ),\n    )\n", "path": "bookwyrm/forms.py"}], "after_files": [{"content": "\"\"\" using django model forms \"\"\"\nimport datetime\nfrom collections import defaultdict\n\nfrom django import forms\nfrom django.forms import ModelForm, PasswordInput, widgets, ChoiceField\nfrom django.forms.widgets import Textarea\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\n\nfrom bookwyrm import models\n\n\nclass CustomForm(ModelForm):\n    \"\"\"add css classes to the forms\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        css_classes = defaultdict(lambda: \"\")\n        css_classes[\"text\"] = \"input\"\n        css_classes[\"password\"] = \"input\"\n        css_classes[\"email\"] = \"input\"\n        css_classes[\"number\"] = \"input\"\n        css_classes[\"checkbox\"] = \"checkbox\"\n        css_classes[\"textarea\"] = \"textarea\"\n        # pylint: disable=super-with-arguments\n        super(CustomForm, self).__init__(*args, **kwargs)\n        for visible in self.visible_fields():\n            if hasattr(visible.field.widget, \"input_type\"):\n                input_type = visible.field.widget.input_type\n            if isinstance(visible.field.widget, Textarea):\n                input_type = \"textarea\"\n                visible.field.widget.attrs[\"rows\"] = 5\n            visible.field.widget.attrs[\"class\"] = css_classes[input_type]\n\n\n# pylint: disable=missing-class-docstring\nclass LoginForm(CustomForm):\n    class Meta:\n        model = models.User\n        fields = [\"localname\", \"password\"]\n        help_texts = {f: None for f in fields}\n        widgets = {\n            \"password\": PasswordInput(),\n        }\n\n\nclass RegisterForm(CustomForm):\n    class Meta:\n        model = models.User\n        fields = [\"localname\", \"email\", \"password\"]\n        help_texts = {f: None for f in fields}\n        widgets = {\"password\": PasswordInput()}\n\n\nclass RatingForm(CustomForm):\n    class Meta:\n        model = models.ReviewRating\n        fields = [\"user\", \"book\", \"rating\", \"privacy\"]\n\n\nclass ReviewForm(CustomForm):\n    class Meta:\n        model = models.Review\n        fields = [\n            \"user\",\n            \"book\",\n            \"name\",\n            \"content\",\n            \"rating\",\n            \"content_warning\",\n            \"sensitive\",\n            \"privacy\",\n        ]\n\n\nclass CommentForm(CustomForm):\n    class Meta:\n        model = models.Comment\n        fields = [\n            \"user\",\n            \"book\",\n            \"content\",\n            \"content_warning\",\n            \"sensitive\",\n            \"privacy\",\n            \"progress\",\n            \"progress_mode\",\n            \"reading_status\",\n        ]\n\n\nclass QuotationForm(CustomForm):\n    class Meta:\n        model = models.Quotation\n        fields = [\n            \"user\",\n            \"book\",\n            \"quote\",\n            \"content\",\n            \"content_warning\",\n            \"sensitive\",\n            \"privacy\",\n            \"position\",\n            \"position_mode\",\n        ]\n\n\nclass ReplyForm(CustomForm):\n    class Meta:\n        model = models.Status\n        fields = [\n            \"user\",\n            \"content\",\n            \"content_warning\",\n            \"sensitive\",\n            \"reply_parent\",\n            \"privacy\",\n        ]\n\n\nclass StatusForm(CustomForm):\n    class Meta:\n        model = models.Status\n        fields = [\"user\", \"content\", \"content_warning\", \"sensitive\", \"privacy\"]\n\n\nclass DirectForm(CustomForm):\n    class Meta:\n        model = models.Status\n        fields = [\"user\", \"content\", \"content_warning\", \"sensitive\", \"privacy\"]\n\n\nclass EditUserForm(CustomForm):\n    class Meta:\n        model = models.User\n        fields = [\n            \"avatar\",\n            \"name\",\n            \"email\",\n            \"summary\",\n            \"show_goal\",\n            \"show_suggested_users\",\n            \"manually_approves_followers\",\n            \"default_post_privacy\",\n            \"discoverable\",\n            \"preferred_timezone\",\n            \"preferred_language\",\n        ]\n        help_texts = {f: None for f in fields}\n\n\nclass LimitedEditUserForm(CustomForm):\n    class Meta:\n        model = models.User\n        fields = [\n            \"avatar\",\n            \"name\",\n            \"summary\",\n            \"manually_approves_followers\",\n            \"discoverable\",\n        ]\n        help_texts = {f: None for f in fields}\n\n\nclass DeleteUserForm(CustomForm):\n    class Meta:\n        model = models.User\n        fields = [\"password\"]\n\n\nclass UserGroupForm(CustomForm):\n    class Meta:\n        model = models.User\n        fields = [\"groups\"]\n\n\nclass CoverForm(CustomForm):\n    class Meta:\n        model = models.Book\n        fields = [\"cover\"]\n        help_texts = {f: None for f in fields}\n\n\nclass EditionForm(CustomForm):\n    class Meta:\n        model = models.Edition\n        exclude = [\n            \"remote_id\",\n            \"origin_id\",\n            \"created_date\",\n            \"updated_date\",\n            \"edition_rank\",\n            \"authors\",\n            \"parent_work\",\n            \"shelves\",\n            \"connector\",\n            \"search_vector\",\n        ]\n\n\nclass AuthorForm(CustomForm):\n    class Meta:\n        model = models.Author\n        fields = [\n            \"last_edited_by\",\n            \"name\",\n            \"aliases\",\n            \"bio\",\n            \"wikipedia_link\",\n            \"born\",\n            \"died\",\n            \"openlibrary_key\",\n            \"inventaire_id\",\n            \"librarything_key\",\n            \"goodreads_key\",\n        ]\n\n\nclass ImportForm(forms.Form):\n    csv_file = forms.FileField()\n\n\nclass ExpiryWidget(widgets.Select):\n    def value_from_datadict(self, data, files, name):\n        \"\"\"human-readable exiration time buckets\"\"\"\n        selected_string = super().value_from_datadict(data, files, name)\n\n        if selected_string == \"day\":\n            interval = datetime.timedelta(days=1)\n        elif selected_string == \"week\":\n            interval = datetime.timedelta(days=7)\n        elif selected_string == \"month\":\n            interval = datetime.timedelta(days=31)  # Close enough?\n        elif selected_string == \"forever\":\n            return None\n        else:\n            return selected_string  # This will raise\n\n        return timezone.now() + interval\n\n\nclass InviteRequestForm(CustomForm):\n    def clean(self):\n        \"\"\"make sure the email isn't in use by a registered user\"\"\"\n        cleaned_data = super().clean()\n        email = cleaned_data.get(\"email\")\n        if email and models.User.objects.filter(email=email).exists():\n            self.add_error(\"email\", _(\"A user with this email already exists.\"))\n\n    class Meta:\n        model = models.InviteRequest\n        fields = [\"email\"]\n\n\nclass CreateInviteForm(CustomForm):\n    class Meta:\n        model = models.SiteInvite\n        exclude = [\"code\", \"user\", \"times_used\", \"invitees\"]\n        widgets = {\n            \"expiry\": ExpiryWidget(\n                choices=[\n                    (\"day\", _(\"One Day\")),\n                    (\"week\", _(\"One Week\")),\n                    (\"month\", _(\"One Month\")),\n                    (\"forever\", _(\"Does Not Expire\")),\n                ]\n            ),\n            \"use_limit\": widgets.Select(\n                choices=[(i, _(f\"{i} uses\")) for i in [1, 5, 10, 25, 50, 100]]\n                + [(None, _(\"Unlimited\"))]\n            ),\n        }\n\n\nclass ShelfForm(CustomForm):\n    class Meta:\n        model = models.Shelf\n        fields = [\"user\", \"name\", \"privacy\", \"description\"]\n\n\nclass GoalForm(CustomForm):\n    class Meta:\n        model = models.AnnualGoal\n        fields = [\"user\", \"year\", \"goal\", \"privacy\"]\n\n\nclass SiteForm(CustomForm):\n    class Meta:\n        model = models.SiteSettings\n        exclude = []\n\n\nclass AnnouncementForm(CustomForm):\n    class Meta:\n        model = models.Announcement\n        exclude = [\"remote_id\"]\n\n\nclass ListForm(CustomForm):\n    class Meta:\n        model = models.List\n        fields = [\"user\", \"name\", \"description\", \"curation\", \"privacy\", \"group\"]\n\n\nclass GroupForm(CustomForm):\n    class Meta:\n        model = models.Group\n        fields = [\"user\", \"privacy\", \"name\", \"description\"]\n\n\nclass ReportForm(CustomForm):\n    class Meta:\n        model = models.Report\n        fields = [\"user\", \"reporter\", \"statuses\", \"note\"]\n\n\nclass EmailBlocklistForm(CustomForm):\n    class Meta:\n        model = models.EmailBlocklist\n        fields = [\"domain\"]\n\n\nclass IPBlocklistForm(CustomForm):\n    class Meta:\n        model = models.IPBlocklist\n        fields = [\"address\"]\n\n\nclass ServerForm(CustomForm):\n    class Meta:\n        model = models.FederatedServer\n        exclude = [\"remote_id\"]\n\n\nclass SortListForm(forms.Form):\n    sort_by = ChoiceField(\n        choices=(\n            (\"order\", _(\"List Order\")),\n            (\"title\", _(\"Book Title\")),\n            (\"rating\", _(\"Rating\")),\n        ),\n        label=_(\"Sort By\"),\n    )\n    direction = ChoiceField(\n        choices=(\n            (\"ascending\", _(\"Ascending\")),\n            (\"descending\", _(\"Descending\")),\n        ),\n    )\n", "path": "bookwyrm/forms.py"}]}