moviewyrm/bookwyrm/forms.py

575 lines
18 KiB
Python
Raw Normal View History

2021-03-08 16:49:10 +00:00
""" using django model forms """
2020-06-03 16:38:30 +00:00
import datetime
2020-09-29 17:21:10 +00:00
from collections import defaultdict
from urllib.parse import urlparse
2020-06-03 16:38:30 +00:00
2020-03-23 16:40:09 +00:00
from django import forms
2021-04-08 16:05:21 +00:00
from django.forms import ModelForm, PasswordInput, widgets, ChoiceField
2020-09-29 17:21:10 +00:00
from django.forms.widgets import Textarea
from django.utils import timezone
2021-04-04 20:22:36 +00:00
from django.utils.translation import gettext_lazy as _
2020-01-29 09:05:27 +00:00
from bookwyrm import models
from bookwyrm.models.fields import ClearableFileInputWithWarning
2021-11-24 10:59:45 +00:00
from bookwyrm.models.user import FeedFilterChoices
2020-01-29 09:05:27 +00:00
2020-09-29 17:21:10 +00:00
class CustomForm(ModelForm):
2021-04-26 16:15:42 +00:00
"""add css classes to the forms"""
2021-03-08 16:49:10 +00:00
2020-09-29 17:21:10 +00:00
def __init__(self, *args, **kwargs):
2021-03-08 16:49:10 +00:00
css_classes = defaultdict(lambda: "")
css_classes["text"] = "input"
css_classes["password"] = "input"
css_classes["email"] = "input"
css_classes["number"] = "input"
css_classes["checkbox"] = "checkbox"
css_classes["textarea"] = "textarea"
2021-06-18 21:12:56 +00:00
# pylint: disable=super-with-arguments
2020-09-29 17:21:10 +00:00
super(CustomForm, self).__init__(*args, **kwargs)
for visible in self.visible_fields():
2021-03-08 16:49:10 +00:00
if hasattr(visible.field.widget, "input_type"):
2020-09-29 17:21:10 +00:00
input_type = visible.field.widget.input_type
if isinstance(visible.field.widget, Textarea):
2021-03-08 16:49:10 +00:00
input_type = "textarea"
2021-09-28 17:21:12 +00:00
visible.field.widget.attrs["rows"] = 5
2021-03-08 16:49:10 +00:00
visible.field.widget.attrs["class"] = css_classes[input_type]
2020-09-29 17:21:10 +00:00
2020-12-13 02:13:00 +00:00
# pylint: disable=missing-class-docstring
2020-09-29 17:21:10 +00:00
class LoginForm(CustomForm):
2020-01-29 09:05:27 +00:00
class Meta:
model = models.User
2021-03-08 16:49:10 +00:00
fields = ["localname", "password"]
2020-01-29 09:05:27 +00:00
help_texts = {f: None for f in fields}
widgets = {
2021-03-08 16:49:10 +00:00
"password": PasswordInput(),
2020-01-29 09:05:27 +00:00
}
2020-09-29 17:21:10 +00:00
class RegisterForm(CustomForm):
2020-01-29 09:05:27 +00:00
class Meta:
model = models.User
2021-03-08 16:49:10 +00:00
fields = ["localname", "email", "password"]
2020-01-29 09:05:27 +00:00
help_texts = {f: None for f in fields}
2021-03-08 16:49:10 +00:00
widgets = {"password": PasswordInput()}
2020-01-29 09:05:27 +00:00
2022-02-17 19:25:11 +00:00
def clean(self):
"""Check if the username is taken"""
cleaned_data = super().clean()
localname = cleaned_data.get("localname").strip()
if models.User.objects.filter(localname=localname).first():
2022-02-17 19:31:52 +00:00
self.add_error("localname", _("User with this username already exists"))
2022-02-17 19:25:11 +00:00
2020-01-29 09:05:27 +00:00
2020-09-29 17:21:10 +00:00
class RatingForm(CustomForm):
2020-04-03 19:43:49 +00:00
class Meta:
model = models.ReviewRating
2021-03-08 17:48:25 +00:00
fields = ["user", "book", "rating", "privacy"]
2020-04-03 19:43:49 +00:00
2020-09-29 17:21:10 +00:00
class ReviewForm(CustomForm):
2020-01-29 09:05:27 +00:00
class Meta:
model = models.Review
fields = [
2021-03-08 16:49:10 +00:00
"user",
"book",
"name",
"content",
"rating",
"content_warning",
"sensitive",
"privacy",
]
2020-01-29 09:05:27 +00:00
2020-09-29 17:21:10 +00:00
class CommentForm(CustomForm):
2020-03-21 23:50:49 +00:00
class Meta:
model = models.Comment
2021-03-21 00:39:05 +00:00
fields = [
"user",
"book",
"content",
"content_warning",
"sensitive",
"privacy",
"progress",
2021-03-21 01:03:20 +00:00
"progress_mode",
2021-08-16 20:32:20 +00:00
"reading_status",
2021-03-21 00:39:05 +00:00
]
2020-03-21 23:50:49 +00:00
2020-09-29 17:21:10 +00:00
class QuotationForm(CustomForm):
2020-04-08 16:40:47 +00:00
class Meta:
model = models.Quotation
fields = [
2021-03-08 16:49:10 +00:00
"user",
"book",
"quote",
"content",
"content_warning",
"sensitive",
"privacy",
2021-09-05 23:00:40 +00:00
"position",
"position_mode",
2021-03-08 16:49:10 +00:00
]
2020-04-08 16:40:47 +00:00
2020-09-29 17:21:10 +00:00
class ReplyForm(CustomForm):
2020-02-18 05:39:08 +00:00
class Meta:
model = models.Status
fields = [
2021-03-08 16:49:10 +00:00
"user",
"content",
"content_warning",
"sensitive",
"reply_parent",
"privacy",
]
2020-02-18 05:39:08 +00:00
2021-01-29 19:14:18 +00:00
class StatusForm(CustomForm):
class Meta:
model = models.Status
2021-09-11 19:07:09 +00:00
fields = ["user", "content", "content_warning", "sensitive", "privacy"]
class DirectForm(CustomForm):
class Meta:
model = models.Status
2021-03-08 16:49:10 +00:00
fields = ["user", "content", "content_warning", "sensitive", "privacy"]
2021-01-29 19:14:18 +00:00
2020-02-18 05:39:08 +00:00
2020-09-29 17:21:10 +00:00
class EditUserForm(CustomForm):
2020-01-29 09:05:27 +00:00
class Meta:
model = models.User
2021-03-18 16:06:00 +00:00
fields = [
"avatar",
"name",
"email",
"summary",
"show_goal",
"show_suggested_users",
2021-03-21 23:37:52 +00:00
"manually_approves_followers",
"default_post_privacy",
2021-03-21 23:37:52 +00:00
"discoverable",
"preferred_timezone",
"preferred_language",
2021-03-18 16:06:00 +00:00
]
2020-01-29 09:05:27 +00:00
help_texts = {f: None for f in fields}
widgets = {
"avatar": ClearableFileInputWithWarning(
2021-11-28 21:01:49 +00:00
attrs={"aria-describedby": "desc_avatar"}
),
2021-11-28 21:01:49 +00:00
"name": forms.TextInput(attrs={"aria-describedby": "desc_name"}),
"summary": forms.Textarea(attrs={"aria-describedby": "desc_summary"}),
"email": forms.EmailInput(attrs={"aria-describedby": "desc_email"}),
"discoverable": forms.CheckboxInput(
2021-11-28 21:01:49 +00:00
attrs={"aria-describedby": "desc_discoverable"}
),
}
2020-02-18 05:39:08 +00:00
2020-02-21 06:19:19 +00:00
2021-04-01 15:32:06 +00:00
class LimitedEditUserForm(CustomForm):
class Meta:
model = models.User
fields = [
"avatar",
"name",
"summary",
"manually_approves_followers",
"discoverable",
]
help_texts = {f: None for f in fields}
widgets = {
"avatar": ClearableFileInputWithWarning(
2021-11-28 21:01:49 +00:00
attrs={"aria-describedby": "desc_avatar"}
),
2021-11-28 21:01:49 +00:00
"name": forms.TextInput(attrs={"aria-describedby": "desc_name"}),
"summary": forms.Textarea(attrs={"aria-describedby": "desc_summary"}),
"discoverable": forms.CheckboxInput(
2021-11-28 21:01:49 +00:00
attrs={"aria-describedby": "desc_discoverable"}
),
}
2021-04-01 15:32:06 +00:00
2021-04-20 01:12:55 +00:00
2021-06-14 17:44:25 +00:00
class DeleteUserForm(CustomForm):
class Meta:
model = models.User
fields = ["password"]
class UserGroupForm(CustomForm):
class Meta:
model = models.User
fields = ["groups"]
2021-04-01 15:32:06 +00:00
2021-11-22 17:52:57 +00:00
class FeedStatusTypesForm(CustomForm):
2021-11-21 23:25:47 +00:00
class Meta:
model = models.User
fields = ["feed_status_types"]
help_texts = {f: None for f in fields}
widgets = {
2021-11-22 17:52:57 +00:00
"feed_status_types": widgets.CheckboxSelectMultiple(
2021-11-24 10:59:45 +00:00
choices=FeedFilterChoices,
2021-11-21 23:25:47 +00:00
),
}
2020-09-29 17:21:10 +00:00
class CoverForm(CustomForm):
2020-03-28 22:06:16 +00:00
class Meta:
model = models.Book
2021-03-08 16:49:10 +00:00
fields = ["cover"]
2020-03-28 22:06:16 +00:00
help_texts = {f: None for f in fields}
2022-01-10 18:38:05 +00:00
class LinkDomainForm(CustomForm):
class Meta:
model = models.LinkDomain
2022-01-10 18:48:27 +00:00
fields = ["name"]
2022-01-10 18:38:05 +00:00
2021-12-15 20:40:31 +00:00
class FileLinkForm(CustomForm):
class Meta:
model = models.FileLink
2022-01-17 17:21:58 +00:00
fields = ["url", "filetype", "availability", "book", "added_by"]
def clean(self):
"""make sure the domain isn't blocked or pending"""
cleaned_data = super().clean()
url = cleaned_data.get("url")
filetype = cleaned_data.get("filetype")
book = cleaned_data.get("book")
domain = urlparse(url).netloc
if models.LinkDomain.objects.filter(domain=domain).exists():
status = models.LinkDomain.objects.get(domain=domain).status
if status == "blocked":
2022-02-04 19:47:18 +00:00
# pylint: disable=line-too-long
self.add_error(
"url",
_(
"This domain is blocked. Please contact your administrator if you think this is an error."
),
)
elif models.FileLink.objects.filter(
url=url, book=book, filetype=filetype
).exists():
2022-02-04 19:47:18 +00:00
# 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."
),
)
2021-12-15 20:40:31 +00:00
2020-09-29 17:21:10 +00:00
class EditionForm(CustomForm):
2020-03-28 22:06:16 +00:00
class Meta:
2020-04-02 15:44:53 +00:00
model = models.Edition
2020-03-28 22:06:16 +00:00
exclude = [
2021-03-08 16:49:10 +00:00
"remote_id",
"origin_id",
"created_date",
"updated_date",
"edition_rank",
2021-03-08 18:07:02 +00:00
"authors",
2021-03-08 16:49:10 +00:00
"parent_work",
"shelves",
"connector",
2021-06-26 15:54:52 +00:00
"search_vector",
2021-10-07 23:53:39 +00:00
"links",
"file_links",
2020-03-28 22:06:16 +00:00
]
2021-11-29 22:29:22 +00:00
widgets = {
2021-11-29 22:31:05 +00:00
"title": forms.TextInput(attrs={"aria-describedby": "desc_title"}),
"subtitle": forms.TextInput(attrs={"aria-describedby": "desc_subtitle"}),
2021-11-29 22:29:22 +00:00
"description": forms.Textarea(
attrs={"aria-describedby": "desc_description"}
),
2021-11-29 22:31:05 +00:00
"series": forms.TextInput(attrs={"aria-describedby": "desc_series"}),
2021-11-29 22:29:22 +00:00
"series_number": forms.TextInput(
attrs={"aria-describedby": "desc_series_number"}
),
"languages": forms.TextInput(
attrs={"aria-describedby": "desc_languages_help desc_languages"}
),
"publishers": forms.TextInput(
attrs={"aria-describedby": "desc_publishers_help desc_publishers"}
),
"first_published_date": forms.SelectDateWidget(
attrs={"aria-describedby": "desc_first_published_date"}
),
"published_date": forms.SelectDateWidget(
attrs={"aria-describedby": "desc_published_date"}
),
"cover": ClearableFileInputWithWarning(
attrs={"aria-describedby": "desc_cover"}
),
"physical_format": forms.Select(
attrs={"aria-describedby": "desc_physical_format"}
),
"physical_format_detail": forms.TextInput(
attrs={"aria-describedby": "desc_physical_format_detail"}
),
2021-11-29 22:31:05 +00:00
"pages": forms.NumberInput(attrs={"aria-describedby": "desc_pages"}),
"isbn_13": forms.TextInput(attrs={"aria-describedby": "desc_isbn_13"}),
"isbn_10": forms.TextInput(attrs={"aria-describedby": "desc_isbn_10"}),
2021-11-29 22:29:22 +00:00
"openlibrary_key": forms.TextInput(
attrs={"aria-describedby": "desc_openlibrary_key"}
),
"inventaire_id": forms.TextInput(
attrs={"aria-describedby": "desc_inventaire_id"}
),
"oclc_number": forms.TextInput(
attrs={"aria-describedby": "desc_oclc_number"}
),
2021-11-29 22:31:05 +00:00
"ASIN": forms.TextInput(attrs={"aria-describedby": "desc_ASIN"}),
2021-11-29 22:29:22 +00:00
}
2020-03-28 22:06:16 +00:00
2021-03-08 16:49:10 +00:00
class AuthorForm(CustomForm):
class Meta:
model = models.Author
fields = [
"last_edited_by",
"name",
"aliases",
"bio",
"wikipedia_link",
"born",
"died",
"openlibrary_key",
2021-11-14 10:26:23 +00:00
"inventaire_id",
"librarything_key",
"goodreads_key",
2021-12-05 19:02:36 +00:00
"isni",
]
2021-11-29 21:39:01 +00:00
widgets = {
2021-11-29 22:31:05 +00:00
"name": forms.TextInput(attrs={"aria-describedby": "desc_name"}),
"aliases": forms.TextInput(attrs={"aria-describedby": "desc_aliases"}),
"bio": forms.Textarea(attrs={"aria-describedby": "desc_bio"}),
2021-11-29 21:39:01 +00:00
"wikipedia_link": forms.TextInput(
attrs={"aria-describedby": "desc_wikipedia_link"}
),
2021-11-29 22:31:05 +00:00
"born": forms.SelectDateWidget(attrs={"aria-describedby": "desc_born"}),
"died": forms.SelectDateWidget(attrs={"aria-describedby": "desc_died"}),
2021-11-29 21:39:01 +00:00
"oepnlibrary_key": forms.TextInput(
attrs={"aria-describedby": "desc_oepnlibrary_key"}
),
"inventaire_id": forms.TextInput(
attrs={"aria-describedby": "desc_inventaire_id"}
),
"librarything_key": forms.TextInput(
attrs={"aria-describedby": "desc_librarything_key"}
),
"goodreads_key": forms.TextInput(
attrs={"aria-describedby": "desc_goodreads_key"}
),
}
2020-03-28 22:06:16 +00:00
2020-03-23 16:40:09 +00:00
class ImportForm(forms.Form):
csv_file = forms.FileField()
2020-06-03 16:38:30 +00:00
2021-03-08 16:49:10 +00:00
2020-06-03 16:38:30 +00:00
class ExpiryWidget(widgets.Select):
def value_from_datadict(self, data, files, name):
2021-04-26 16:15:42 +00:00
"""human-readable exiration time buckets"""
2020-06-03 16:38:30 +00:00
selected_string = super().value_from_datadict(data, files, name)
2021-03-08 16:49:10 +00:00
if selected_string == "day":
2020-06-03 16:38:30 +00:00
interval = datetime.timedelta(days=1)
2021-03-08 16:49:10 +00:00
elif selected_string == "week":
2020-06-03 16:38:30 +00:00
interval = datetime.timedelta(days=7)
2021-03-08 16:49:10 +00:00
elif selected_string == "month":
interval = datetime.timedelta(days=31) # Close enough?
elif selected_string == "forever":
2020-06-03 16:38:30 +00:00
return None
else:
2021-09-27 22:57:22 +00:00
return selected_string # This will raise
2020-06-03 16:38:30 +00:00
return timezone.now() + interval
2020-06-03 16:38:30 +00:00
2021-03-08 16:49:10 +00:00
2021-03-21 01:23:59 +00:00
class InviteRequestForm(CustomForm):
2021-03-21 02:14:41 +00:00
def clean(self):
2021-04-26 16:15:42 +00:00
"""make sure the email isn't in use by a registered user"""
2021-03-21 02:14:41 +00:00
cleaned_data = super().clean()
email = cleaned_data.get("email")
if email and models.User.objects.filter(email=email).exists():
self.add_error("email", _("A user with this email already exists."))
2021-03-21 01:23:59 +00:00
class Meta:
model = models.InviteRequest
fields = ["email"]
2020-09-29 17:21:10 +00:00
class CreateInviteForm(CustomForm):
2020-06-03 16:38:30 +00:00
class Meta:
model = models.SiteInvite
2021-04-05 17:17:01 +00:00
exclude = ["code", "user", "times_used", "invitees"]
2020-06-03 16:38:30 +00:00
widgets = {
2021-03-08 16:49:10 +00:00
"expiry": ExpiryWidget(
choices=[
("day", _("One Day")),
("week", _("One Week")),
("month", _("One Month")),
("forever", _("Does Not Expire")),
]
),
"use_limit": widgets.Select(
2021-09-18 18:33:43 +00:00
choices=[(i, _(f"{i} uses")) for i in [1, 5, 10, 25, 50, 100]]
2021-03-08 16:49:10 +00:00
+ [(None, _("Unlimited"))]
),
2020-06-03 16:38:30 +00:00
}
2020-11-10 22:52:04 +00:00
2021-03-08 16:49:10 +00:00
2020-11-10 22:52:04 +00:00
class ShelfForm(CustomForm):
class Meta:
model = models.Shelf
2021-09-28 23:36:47 +00:00
fields = ["user", "name", "privacy", "description"]
2021-01-16 16:18:54 +00:00
2021-01-29 23:38:42 +00:00
2021-01-16 16:18:54 +00:00
class GoalForm(CustomForm):
class Meta:
model = models.AnnualGoal
2021-03-08 16:49:10 +00:00
fields = ["user", "year", "goal", "privacy"]
2021-01-29 23:38:42 +00:00
class SiteForm(CustomForm):
class Meta:
model = models.SiteSettings
2022-02-18 01:50:57 +00:00
exclude = ["admin_code", "install_mode"]
widgets = {
"instance_short_description": forms.TextInput(
2021-11-28 21:01:49 +00:00
attrs={"aria-describedby": "desc_instance_short_description"}
),
"require_confirm_email": forms.CheckboxInput(
2021-11-28 21:01:49 +00:00
attrs={"aria-describedby": "desc_require_confirm_email"}
),
"invite_request_text": forms.Textarea(
2021-11-28 21:01:49 +00:00
attrs={"aria-describedby": "desc_invite_request_text"}
),
}
2021-01-31 16:08:52 +00:00
2022-02-27 18:00:50 +00:00
class SiteThemeForm(CustomForm):
class Meta:
model = models.SiteSettings
fields = ["default_theme"]
class ThemeForm(CustomForm):
class Meta:
model = models.Theme
fields = ["name", "path"]
widgets = {
"name": forms.TextInput(attrs={"aria-describedby": "desc_name"}),
2022-02-27 19:19:09 +00:00
"path": forms.Select(attrs={"aria-describedby": "desc_path"}),
2022-02-27 18:00:50 +00:00
}
2021-05-19 21:55:01 +00:00
class AnnouncementForm(CustomForm):
class Meta:
model = models.Announcement
exclude = ["remote_id"]
2021-11-29 22:28:51 +00:00
widgets = {
2021-11-29 22:31:05 +00:00
"preview": forms.TextInput(attrs={"aria-describedby": "desc_preview"}),
"content": forms.Textarea(attrs={"aria-describedby": "desc_content"}),
2021-11-29 22:28:51 +00:00
"event_date": forms.SelectDateWidget(
attrs={"aria-describedby": "desc_event_date"}
),
"start_date": forms.SelectDateWidget(
attrs={"aria-describedby": "desc_start_date"}
),
"end_date": forms.SelectDateWidget(
attrs={"aria-describedby": "desc_end_date"}
),
2021-11-29 22:31:05 +00:00
"active": forms.CheckboxInput(attrs={"aria-describedby": "desc_active"}),
2021-11-29 22:28:51 +00:00
}
2021-05-19 21:55:01 +00:00
2021-01-31 16:08:52 +00:00
class ListForm(CustomForm):
class Meta:
model = models.List
2021-09-26 05:55:16 +00:00
fields = ["user", "name", "description", "curation", "privacy", "group"]
2021-03-09 02:36:34 +00:00
2022-01-25 16:44:04 +00:00
class ListItemForm(CustomForm):
class Meta:
model = models.ListItem
2022-01-25 20:10:58 +00:00
fields = ["user", "book", "book_list", "notes"]
2022-01-25 16:44:04 +00:00
2021-10-04 10:31:28 +00:00
class GroupForm(CustomForm):
class Meta:
model = models.Group
fields = ["user", "privacy", "name", "description"]
2021-03-09 02:36:34 +00:00
class ReportForm(CustomForm):
class Meta:
model = models.Report
2022-02-24 20:48:52 +00:00
fields = ["user", "reporter", "status", "links", "note"]
2021-04-07 18:52:13 +00:00
2021-09-08 22:08:22 +00:00
class EmailBlocklistForm(CustomForm):
class Meta:
model = models.EmailBlocklist
fields = ["domain"]
2021-11-29 22:28:29 +00:00
widgets = {
2021-11-29 22:31:05 +00:00
"avatar": forms.TextInput(attrs={"aria-describedby": "desc_domain"}),
2021-11-29 22:28:29 +00:00
}
2021-09-08 22:08:22 +00:00
2021-09-17 19:59:16 +00:00
class IPBlocklistForm(CustomForm):
class Meta:
model = models.IPBlocklist
fields = ["address"]
2021-04-07 18:52:13 +00:00
class ServerForm(CustomForm):
class Meta:
model = models.FederatedServer
2021-04-07 19:11:01 +00:00
exclude = ["remote_id"]
2021-04-08 16:05:21 +00:00
class SortListForm(forms.Form):
sort_by = ChoiceField(
choices=(
("order", _("List Order")),
("title", _("Book Title")),
("rating", _("Rating")),
),
label=_("Sort By"),
)
direction = ChoiceField(
choices=(
("ascending", _("Ascending")),
("descending", _("Descending")),
),
)
2022-01-11 17:50:04 +00:00
2022-01-11 18:40:32 +00:00
2022-01-11 17:50:04 +00:00
class ReadThroughForm(CustomForm):
def clean(self):
"""make sure the email isn't in use by a registered user"""
cleaned_data = super().clean()
start_date = cleaned_data.get("start_date")
finish_date = cleaned_data.get("finish_date")
if start_date and finish_date and start_date > finish_date:
self.add_error(
"finish_date", _("Reading finish date cannot be before start date.")
)
2022-01-11 17:50:04 +00:00
class Meta:
model = models.ReadThrough
fields = ["user", "book", "start_date", "finish_date"]
2022-02-24 19:18:43 +00:00
class AutoModRuleForm(CustomForm):
class Meta:
model = models.AutoMod
fields = ["string_match", "flag_users", "flag_statuses", "created_by"]