diff --git a/.env.example b/.env.example index 3c935287e..2000a7165 100644 --- a/.env.example +++ b/.env.example @@ -28,10 +28,14 @@ MAX_STREAM_LENGTH=200 REDIS_ACTIVITY_HOST=redis_activity REDIS_ACTIVITY_PORT=6379 REDIS_ACTIVITY_PASSWORD=redispassword345 +# Optional, use a different redis database (defaults to 0) +# REDIS_ACTIVITY_DB_INDEX=0 # Redis as celery broker REDIS_BROKER_PORT=6379 REDIS_BROKER_PASSWORD=redispassword123 +# Optional, use a different redis database (defaults to 0) +# REDIS_BROKER_DB_INDEX=0 # Monitoring for celery FLOWER_PORT=8888 diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 24d383ac7..15ca5a938 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -20,22 +20,6 @@ class ActivityEncoder(JSONEncoder): return o.__dict__ -@dataclass -class Link: - """for tagging a book in a status""" - - href: str - name: str - type: str = "Link" - - -@dataclass -class Mention(Link): - """a subtype of Link for mentioning an actor""" - - type: str = "Mention" - - @dataclass # pylint: disable=invalid-name class Signature: @@ -198,8 +182,9 @@ class ActivityObject: ) return instance - def serialize(self): + def serialize(self, **kwargs): """convert to dictionary with context attr""" + omit = kwargs.get("omit", ()) data = self.__dict__.copy() # recursively serialize for (k, v) in data.items(): @@ -208,8 +193,9 @@ class ActivityObject: data[k] = v.serialize() except TypeError: pass - data = {k: v for (k, v) in data.items() if v is not None} - data["@context"] = "https://www.w3.org/ns/activitystreams" + data = {k: v for (k, v) in data.items() if v is not None and k not in omit} + if "@context" not in omit: + data["@context"] = "https://www.w3.org/ns/activitystreams" return data @@ -222,35 +208,32 @@ def set_related_field( model = apps.get_model(f"bookwyrm.{model_name}", require_ready=True) origin_model = apps.get_model(f"bookwyrm.{origin_model_name}", require_ready=True) - with transaction.atomic(): - if isinstance(data, str): - existing = model.find_existing_by_remote_id(data) - if existing: - data = existing.to_activity() - else: - data = get_data(data) - activity = model.activity_serializer(**data) + if isinstance(data, str): + existing = model.find_existing_by_remote_id(data) + if existing: + data = existing.to_activity() + else: + data = get_data(data) + activity = model.activity_serializer(**data) - # this must exist because it's the object that triggered this function - instance = origin_model.find_existing_by_remote_id(related_remote_id) - if not instance: - raise ValueError(f"Invalid related remote id: {related_remote_id}") + # this must exist because it's the object that triggered this function + instance = origin_model.find_existing_by_remote_id(related_remote_id) + if not instance: + raise ValueError(f"Invalid related remote id: {related_remote_id}") - # set the origin's remote id on the activity so it will be there when - # the model instance is created - # edition.parentWork = instance, for example - model_field = getattr(model, related_field_name) - if hasattr(model_field, "activitypub_field"): - setattr( - activity, getattr(model_field, "activitypub_field"), instance.remote_id - ) - item = activity.to_model() + # set the origin's remote id on the activity so it will be there when + # the model instance is created + # edition.parentWork = instance, for example + model_field = getattr(model, related_field_name) + if hasattr(model_field, "activitypub_field"): + setattr(activity, getattr(model_field, "activitypub_field"), instance.remote_id) + item = activity.to_model() - # if the related field isn't serialized (attachments on Status), then - # we have to set it post-creation - if not hasattr(model_field, "activitypub_field"): - setattr(item, related_field_name, instance) - item.save() + # if the related field isn't serialized (attachments on Status), then + # we have to set it post-creation + if not hasattr(model_field, "activitypub_field"): + setattr(item, related_field_name, instance) + item.save() def get_model_from_type(activity_type): @@ -304,3 +287,27 @@ def resolve_remote_id( # if we're refreshing, "result" will be set and we'll update it return item.to_model(model=model, instance=result, save=save) + + +@dataclass(init=False) +class Link(ActivityObject): + """for tagging a book in a status""" + + href: str + name: str = None + mediaType: str = None + id: str = None + attributedTo: str = None + type: str = "Link" + + def serialize(self, **kwargs): + """remove fields""" + omit = ("id", "type", "@context") + return super().serialize(omit=omit) + + +@dataclass(init=False) +class Mention(Link): + """a subtype of Link for mentioning an actor""" + + type: str = "Mention" diff --git a/bookwyrm/activitypub/book.py b/bookwyrm/activitypub/book.py index d8599c4b3..2238e3a87 100644 --- a/bookwyrm/activitypub/book.py +++ b/bookwyrm/activitypub/book.py @@ -17,6 +17,8 @@ class BookData(ActivityObject): goodreadsKey: str = None bnfId: str = None lastEditedBy: str = None + links: List[str] = field(default_factory=lambda: []) + fileLinks: List[str] = field(default_factory=lambda: []) # pylint: disable=invalid-name diff --git a/bookwyrm/activitypub/person.py b/bookwyrm/activitypub/person.py index 174ead616..576e7f9a6 100644 --- a/bookwyrm/activitypub/person.py +++ b/bookwyrm/activitypub/person.py @@ -15,6 +15,11 @@ class PublicKey(ActivityObject): publicKeyPem: str type: str = "PublicKey" + def serialize(self, **kwargs): + """remove fields""" + omit = ("type", "@context") + return super().serialize(omit=omit) + # pylint: disable=invalid-name @dataclass(init=False) diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index 7ba7bd97b..ef9bbc159 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -216,6 +216,18 @@ class CoverForm(CustomForm): help_texts = {f: None for f in fields} +class LinkDomainForm(CustomForm): + class Meta: + model = models.LinkDomain + fields = ["name"] + + +class FileLinkForm(CustomForm): + class Meta: + model = models.FileLink + fields = ["url", "filetype", "availability", "book", "added_by"] + + class EditionForm(CustomForm): class Meta: model = models.Edition @@ -230,6 +242,8 @@ class EditionForm(CustomForm): "shelves", "connector", "search_vector", + "links", + "file_links", ] widgets = { "title": forms.TextInput(attrs={"aria-describedby": "desc_title"}), @@ -439,7 +453,7 @@ class GroupForm(CustomForm): class ReportForm(CustomForm): class Meta: model = models.Report - fields = ["user", "reporter", "statuses", "note"] + fields = ["user", "reporter", "statuses", "links", "note"] class EmailBlocklistForm(CustomForm): @@ -478,3 +492,19 @@ class SortListForm(forms.Form): ("descending", _("Descending")), ), ) + + +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.") + ) + + class Meta: + model = models.ReadThrough + fields = ["user", "book", "start_date", "finish_date"] diff --git a/bookwyrm/management/commands/erase_streams.py b/bookwyrm/management/commands/erase_streams.py index 1d34b1bb6..7b8074029 100644 --- a/bookwyrm/management/commands/erase_streams.py +++ b/bookwyrm/management/commands/erase_streams.py @@ -5,7 +5,9 @@ import redis from bookwyrm import settings r = redis.Redis( - host=settings.REDIS_ACTIVITY_HOST, port=settings.REDIS_ACTIVITY_PORT, db=0 + host=settings.REDIS_ACTIVITY_HOST, + port=settings.REDIS_ACTIVITY_PORT, + db=settings.REDIS_ACTIVITY_DB_INDEX, ) diff --git a/bookwyrm/management/commands/initdb.py b/bookwyrm/management/commands/initdb.py index d0ab648e0..b54055744 100644 --- a/bookwyrm/management/commands/initdb.py +++ b/bookwyrm/management/commands/initdb.py @@ -3,7 +3,7 @@ from django.core.management.base import BaseCommand from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType -from bookwyrm.models import Connector, FederatedServer, SiteSettings, User +from bookwyrm import models def init_groups(): @@ -55,7 +55,7 @@ def init_permissions(): }, ] - content_type = ContentType.objects.get_for_model(User) + content_type = models.ContentType.objects.get_for_model(User) for permission in permissions: permission_obj = Permission.objects.create( codename=permission["codename"], @@ -72,7 +72,7 @@ def init_permissions(): def init_connectors(): """access book data sources""" - Connector.objects.create( + models.Connector.objects.create( identifier="bookwyrm.social", name="BookWyrm dot Social", connector_file="bookwyrm_connector", @@ -84,7 +84,7 @@ def init_connectors(): priority=2, ) - Connector.objects.create( + models.Connector.objects.create( identifier="inventaire.io", name="Inventaire", connector_file="inventaire", @@ -96,7 +96,7 @@ def init_connectors(): priority=3, ) - Connector.objects.create( + models.Connector.objects.create( identifier="openlibrary.org", name="OpenLibrary", connector_file="openlibrary", @@ -113,7 +113,7 @@ def init_federated_servers(): """big no to nazis""" built_in_blocks = ["gab.ai", "gab.com"] for server in built_in_blocks: - FederatedServer.objects.create( + models.FederatedServer.objects.create( server_name=server, status="blocked", ) @@ -121,18 +121,61 @@ def init_federated_servers(): def init_settings(): """info about the instance""" - SiteSettings.objects.create( + models.SiteSettings.objects.create( support_link="https://www.patreon.com/bookwyrm", support_title="Patreon", ) +def init_link_domains(*_): + """safe book links""" + domains = [ + ("standardebooks.org", "Standard EBooks"), + ("www.gutenberg.org", "Project Gutenberg"), + ("archive.org", "Internet Archive"), + ("openlibrary.org", "Open Library"), + ("theanarchistlibrary.org", "The Anarchist Library"), + ] + for domain, name in domains: + models.LinkDomain.objects.create( + domain=domain, + name=name, + status="approved", + ) + + class Command(BaseCommand): help = "Initializes the database with starter data" + def add_arguments(self, parser): + parser.add_argument( + "--limit", + default=None, + help="Limit init to specific table", + ) + def handle(self, *args, **options): - init_groups() - init_permissions() - init_connectors() - init_federated_servers() - init_settings() + limit = options.get("limit") + tables = [ + "group", + "permission", + "connector", + "federatedserver", + "settings", + "linkdomain", + ] + if limit and limit not in tables: + raise Exception("Invalid table limit:", limit) + + if not limit or limit == "group": + init_groups() + if not limit or limit == "permission": + init_permissions() + if not limit or limit == "connector": + init_connectors() + if not limit or limit == "federatedserver": + init_federated_servers() + if not limit or limit == "settings": + init_settings() + if not limit or limit == "linkdomain": + init_link_domains() diff --git a/bookwyrm/management/commands/populate_lists_streams.py b/bookwyrm/management/commands/populate_lists_streams.py index e3c30baba..0a057401c 100644 --- a/bookwyrm/management/commands/populate_lists_streams.py +++ b/bookwyrm/management/commands/populate_lists_streams.py @@ -22,13 +22,6 @@ class Command(BaseCommand): help = "Populate list streams for all users" - def add_arguments(self, parser): - parser.add_argument( - "--stream", - default=None, - help="Specifies which time of stream to populate", - ) - # pylint: disable=no-self-use,unused-argument def handle(self, *args, **options): """run feed builder""" diff --git a/bookwyrm/migrations/0126_auto_20220112_2315.py b/bookwyrm/migrations/0126_auto_20220112_2315.py new file mode 100644 index 000000000..23645d967 --- /dev/null +++ b/bookwyrm/migrations/0126_auto_20220112_2315.py @@ -0,0 +1,55 @@ +# Generated by Django 3.2.10 on 2022-01-12 23:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0125_alter_user_preferred_language"), + ] + + operations = [ + migrations.AlterField( + model_name="annualgoal", + name="privacy", + field=models.CharField( + choices=[ + ("public", "Public"), + ("unlisted", "Unlisted"), + ("followers", "Followers"), + ("direct", "Private"), + ], + default="public", + max_length=255, + ), + ), + migrations.AlterField( + model_name="importjob", + name="privacy", + field=models.CharField( + choices=[ + ("public", "Public"), + ("unlisted", "Unlisted"), + ("followers", "Followers"), + ("direct", "Private"), + ], + default="public", + max_length=255, + ), + ), + migrations.AlterField( + model_name="user", + name="default_post_privacy", + field=models.CharField( + choices=[ + ("public", "Public"), + ("unlisted", "Unlisted"), + ("followers", "Followers"), + ("direct", "Private"), + ], + default="public", + max_length=255, + ), + ), + ] diff --git a/bookwyrm/migrations/0126_filelink_link_linkdomain.py b/bookwyrm/migrations/0126_filelink_link_linkdomain.py new file mode 100644 index 000000000..7e5186b6d --- /dev/null +++ b/bookwyrm/migrations/0126_filelink_link_linkdomain.py @@ -0,0 +1,144 @@ +# Generated by Django 3.2.10 on 2022-01-10 21:20 + +import bookwyrm.models.activitypub_mixin +import bookwyrm.models.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0125_alter_user_preferred_language"), + ] + + operations = [ + migrations.CreateModel( + name="LinkDomain", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ( + "remote_id", + bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), + ), + ("domain", models.CharField(max_length=255, unique=True)), + ( + "status", + models.CharField( + choices=[ + ("approved", "Approved"), + ("blocked", "Blocked"), + ("pending", "Pending"), + ], + default="pending", + max_length=50, + ), + ), + ("name", models.CharField(max_length=100)), + ( + "reported_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="Link", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ( + "remote_id", + bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), + ), + ("url", bookwyrm.models.fields.URLField(max_length=255)), + ( + "added_by", + bookwyrm.models.fields.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "domain", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="links", + to="bookwyrm.linkdomain", + ), + ), + ], + options={ + "abstract": False, + }, + bases=(bookwyrm.models.activitypub_mixin.ActivitypubMixin, models.Model), + ), + migrations.CreateModel( + name="FileLink", + fields=[ + ( + "link_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="bookwyrm.link", + ), + ), + ("filetype", bookwyrm.models.fields.CharField(max_length=5)), + ( + "book", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="file_links", + to="bookwyrm.book", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("bookwyrm.link",), + ), + ] diff --git a/bookwyrm/migrations/0127_auto_20220110_2211.py b/bookwyrm/migrations/0127_auto_20220110_2211.py new file mode 100644 index 000000000..e18be29e6 --- /dev/null +++ b/bookwyrm/migrations/0127_auto_20220110_2211.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.10 on 2022-01-10 22:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0126_filelink_link_linkdomain"), + ] + + operations = [ + migrations.RemoveConstraint( + model_name="report", + name="self_report", + ), + migrations.AddField( + model_name="report", + name="links", + field=models.ManyToManyField(blank=True, to="bookwyrm.Link"), + ), + ] diff --git a/bookwyrm/migrations/0128_merge_0126_auto_20220112_2315_0127_auto_20220110_2211.py b/bookwyrm/migrations/0128_merge_0126_auto_20220112_2315_0127_auto_20220110_2211.py new file mode 100644 index 000000000..dc700e99c --- /dev/null +++ b/bookwyrm/migrations/0128_merge_0126_auto_20220112_2315_0127_auto_20220110_2211.py @@ -0,0 +1,13 @@ +# Generated by Django 3.2.10 on 2022-01-13 01:14 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0126_auto_20220112_2315"), + ("bookwyrm", "0127_auto_20220110_2211"), + ] + + operations = [] diff --git a/bookwyrm/migrations/0129_auto_20220117_1716.py b/bookwyrm/migrations/0129_auto_20220117_1716.py new file mode 100644 index 000000000..6b05fd272 --- /dev/null +++ b/bookwyrm/migrations/0129_auto_20220117_1716.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.10 on 2022-01-17 17:16 + +import bookwyrm.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0128_merge_0126_auto_20220112_2315_0127_auto_20220110_2211"), + ] + + operations = [ + migrations.AddField( + model_name="filelink", + name="availability", + field=bookwyrm.models.fields.CharField( + choices=[ + ("free", "Free"), + ("purchase", "Purchasable"), + ("loan", "Available for loan"), + ], + default="free", + max_length=100, + ), + ), + migrations.AlterField( + model_name="filelink", + name="filetype", + field=bookwyrm.models.fields.CharField(max_length=50), + ), + ] diff --git a/bookwyrm/models/__init__.py b/bookwyrm/models/__init__.py index c5ea44e0a..4c6305f99 100644 --- a/bookwyrm/models/__init__.py +++ b/bookwyrm/models/__init__.py @@ -4,6 +4,7 @@ import sys from .book import Book, Work, Edition, BookDataModel from .author import Author +from .link import Link, FileLink, LinkDomain from .connector import Connector from .shelf import Shelf, ShelfBook diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index a9dd9508d..ffc03d3e6 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -241,8 +241,11 @@ class Work(OrderedCollectionPageMixin, Book): ) activity_serializer = activitypub.Work - serialize_reverse_fields = [("editions", "editions", "-edition_rank")] - deserialize_reverse_fields = [("editions", "editions")] + serialize_reverse_fields = [ + ("editions", "editions", "-edition_rank"), + ("file_links", "fileLinks", "-created_date"), + ] + deserialize_reverse_fields = [("editions", "editions"), ("file_links", "fileLinks")] # https://schema.org/BookFormatType @@ -296,6 +299,8 @@ class Edition(Book): activity_serializer = activitypub.Edition name_field = "title" + serialize_reverse_fields = [("file_links", "fileLinks", "-created_date")] + deserialize_reverse_fields = [("file_links", "fileLinks")] def get_rank(self): """calculate how complete the data is on this edition""" @@ -337,6 +342,11 @@ class Edition(Book): # set rank self.edition_rank = self.get_rank() + # clear author cache + if self.id: + for author_id in self.authors.values_list("id", flat=True): + cache.delete(f"author-books-{author_id}") + return super().save(*args, **kwargs) @classmethod diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 7d14f88f9..e61f912e5 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -203,9 +203,12 @@ class UsernameField(ActivitypubFieldMixin, models.CharField): return value.split("@")[0] -PrivacyLevels = models.TextChoices( - "Privacy", ["public", "unlisted", "followers", "direct"] -) +PrivacyLevels = [ + ("public", _("Public")), + ("unlisted", _("Unlisted")), + ("followers", _("Followers")), + ("direct", _("Private")), +] class PrivacyField(ActivitypubFieldMixin, models.CharField): @@ -214,9 +217,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField): public = "https://www.w3.org/ns/activitystreams#Public" def __init__(self, *args, **kwargs): - super().__init__( - *args, max_length=255, choices=PrivacyLevels.choices, default="public" - ) + super().__init__(*args, max_length=255, choices=PrivacyLevels, default="public") # pylint: disable=invalid-name def set_field_from_activity(self, instance, data, overwrite=True): @@ -516,6 +517,10 @@ class CharField(ActivitypubFieldMixin, models.CharField): """activitypub-aware char field""" +class URLField(ActivitypubFieldMixin, models.URLField): + """activitypub-aware url field""" + + class TextField(ActivitypubFieldMixin, models.TextField): """activitypub-aware text field""" diff --git a/bookwyrm/models/import_job.py b/bookwyrm/models/import_job.py index 919bbf0db..bcba391b6 100644 --- a/bookwyrm/models/import_job.py +++ b/bookwyrm/models/import_job.py @@ -40,9 +40,7 @@ class ImportJob(models.Model): mappings = models.JSONField() complete = models.BooleanField(default=False) source = models.CharField(max_length=100) - privacy = models.CharField( - max_length=255, default="public", choices=PrivacyLevels.choices - ) + privacy = models.CharField(max_length=255, default="public", choices=PrivacyLevels) retry = models.BooleanField(default=False) @property diff --git a/bookwyrm/models/link.py b/bookwyrm/models/link.py new file mode 100644 index 000000000..0e4148ddd --- /dev/null +++ b/bookwyrm/models/link.py @@ -0,0 +1,95 @@ +""" outlink data """ +from urllib.parse import urlparse + +from django.core.exceptions import PermissionDenied +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from bookwyrm import activitypub +from .activitypub_mixin import ActivitypubMixin +from .base_model import BookWyrmModel +from . import fields + + +class Link(ActivitypubMixin, BookWyrmModel): + """a link to a website""" + + url = fields.URLField(max_length=255, activitypub_field="href") + added_by = fields.ForeignKey( + "User", on_delete=models.SET_NULL, null=True, activitypub_field="attributedTo" + ) + domain = models.ForeignKey( + "LinkDomain", + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="links", + ) + + activity_serializer = activitypub.Link + reverse_unfurl = True + + @property + def name(self): + """link name via the assocaited domain""" + return self.domain.name + + def save(self, *args, **kwargs): + """create a link""" + # get or create the associated domain + if not self.domain: + domain = urlparse(self.url).netloc + self.domain, _ = LinkDomain.objects.get_or_create(domain=domain) + + # this is never broadcast, the owning model broadcasts an update + if "broadcast" in kwargs: + del kwargs["broadcast"] + return super().save(*args, **kwargs) + + +AvailabilityChoices = [ + ("free", _("Free")), + ("purchase", _("Purchasable")), + ("loan", _("Available for loan")), +] + + +class FileLink(Link): + """a link to a file""" + + book = models.ForeignKey( + "Book", on_delete=models.CASCADE, related_name="file_links", null=True + ) + filetype = fields.CharField(max_length=50, activitypub_field="mediaType") + availability = fields.CharField( + max_length=100, choices=AvailabilityChoices, default="free" + ) + + +StatusChoices = [ + ("approved", _("Approved")), + ("blocked", _("Blocked")), + ("pending", _("Pending")), +] + + +class LinkDomain(BookWyrmModel): + """List of domains used in links""" + + domain = models.CharField(max_length=255, unique=True) + status = models.CharField(max_length=50, choices=StatusChoices, default="pending") + name = models.CharField(max_length=100) + reported_by = models.ForeignKey( + "User", blank=True, null=True, on_delete=models.SET_NULL + ) + + def raise_not_editable(self, viewer): + if viewer.has_perm("moderate_post"): + return + raise PermissionDenied() + + def save(self, *args, **kwargs): + """set a default name""" + if not self.name: + self.name = self.domain + super().save(*args, **kwargs) diff --git a/bookwyrm/models/readthrough.py b/bookwyrm/models/readthrough.py index f75918ac1..ceb8e0b6e 100644 --- a/bookwyrm/models/readthrough.py +++ b/bookwyrm/models/readthrough.py @@ -1,5 +1,6 @@ """ progress in a book """ from django.core import validators +from django.core.cache import cache from django.db import models from django.db.models import F, Q @@ -30,6 +31,7 @@ class ReadThrough(BookWyrmModel): def save(self, *args, **kwargs): """update user active time""" + cache.delete(f"latest_read_through-{self.user.id}-{self.book.id}") self.user.update_active_date() # an active readthrough must have an unset finish date if self.finish_date: diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 034174546..e95c38fa5 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -1,7 +1,6 @@ """ defines relationships between users """ from django.apps import apps from django.core.cache import cache -from django.core.cache.utils import make_template_fragment_key from django.db import models, transaction, IntegrityError from django.db.models import Q @@ -41,15 +40,12 @@ class UserRelationship(BookWyrmModel): def save(self, *args, **kwargs): """clear the template cache""" # invalidate the template cache - cache_keys = [ - make_template_fragment_key( - "follow_button", [self.user_subject.id, self.user_object.id] - ), - make_template_fragment_key( - "follow_button", [self.user_object.id, self.user_subject.id] - ), - ] - cache.delete_many(cache_keys) + cache.delete_many( + [ + f"relationship-{self.user_subject.id}-{self.user_object.id}", + f"relationship-{self.user_object.id}-{self.user_subject.id}", + ] + ) super().save(*args, **kwargs) class Meta: diff --git a/bookwyrm/models/report.py b/bookwyrm/models/report.py index 636817cb2..a9f5b3b1e 100644 --- a/bookwyrm/models/report.py +++ b/bookwyrm/models/report.py @@ -1,6 +1,5 @@ """ flagged for moderation """ from django.db import models -from django.db.models import F, Q from .base_model import BookWyrmModel @@ -13,14 +12,12 @@ class Report(BookWyrmModel): note = models.TextField(null=True, blank=True) user = models.ForeignKey("User", on_delete=models.PROTECT) statuses = models.ManyToManyField("Status", blank=True) + links = models.ManyToManyField("Link", blank=True) resolved = models.BooleanField(default=False) class Meta: - """don't let users report themselves""" + """set order by default""" - constraints = [ - models.CheckConstraint(check=~Q(reporter=F("user")), name="self_report") - ] ordering = ("-created_date",) diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index c578f0827..320d495d2 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -1,5 +1,6 @@ """ puttin' books on shelves """ import re +from django.core.cache import cache from django.core.exceptions import PermissionDenied from django.db import models from django.utils import timezone @@ -94,8 +95,15 @@ class ShelfBook(CollectionItemMixin, BookWyrmModel): def save(self, *args, **kwargs): if not self.user: self.user = self.shelf.user + if self.id and self.user.local: + cache.delete(f"book-on-shelf-{self.book.id}-{self.shelf.id}") super().save(*args, **kwargs) + def delete(self, *args, **kwargs): + if self.id and self.user.local: + cache.delete(f"book-on-shelf-{self.book.id}-{self.shelf.id}") + super().delete(*args, **kwargs) + class Meta: """an opinionated constraint! you can't put a book on shelf twice""" diff --git a/bookwyrm/models/site.py b/bookwyrm/models/site.py index 5d91553e3..b2119e238 100644 --- a/bookwyrm/models/site.py +++ b/bookwyrm/models/site.py @@ -90,6 +90,14 @@ class SiteSettings(models.Model): return get_absolute_url(uploaded) return urljoin(STATIC_FULL_URL, default_path) + def save(self, *args, **kwargs): + """if require_confirm_email is disabled, make sure no users are pending""" + if not self.require_confirm_email: + User.objects.filter(is_active=False, deactivation_reason="pending").update( + is_active=True, deactivation_reason=None + ) + super().save(*args, **kwargs) + class SiteInvite(models.Model): """gives someone access to create an account on the instance""" diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index ee138d979..29b3ba9cc 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -3,6 +3,7 @@ from dataclasses import MISSING import re from django.apps import apps +from django.core.cache import cache from django.core.exceptions import PermissionDenied from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models @@ -373,6 +374,12 @@ class Review(BookStatus): activity_serializer = activitypub.Review pure_type = "Article" + def save(self, *args, **kwargs): + """clear rating caches""" + if self.book.parent_work: + cache.delete(f"book-rating-{self.book.parent_work.id}-*") + super().save(*args, **kwargs) + class ReviewRating(Review): """a subtype of review that only contains a rating""" diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index bd340b01d..6367dcaef 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -129,7 +129,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): related_name="favorite_statuses", ) default_post_privacy = models.CharField( - max_length=255, default="public", choices=fields.PrivacyLevels.choices + max_length=255, default="public", choices=fields.PrivacyLevels ) remote_id = fields.RemoteIdField(null=True, unique=True, activitypub_field="id") created_date = models.DateTimeField(auto_now_add=True) @@ -346,6 +346,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): def delete(self, *args, **kwargs): """deactivate rather than delete a user""" + # pylint: disable=attribute-defined-outside-init self.is_active = False # skip the logic in this class's save() super().save(*args, **kwargs) @@ -406,14 +407,6 @@ class KeyPair(ActivitypubMixin, BookWyrmModel): self.private_key, self.public_key = create_key_pair() return super().save(*args, **kwargs) - def to_activity(self, **kwargs): - """override default AP serializer to add context object - idk if this is the best way to go about this""" - activity_object = super().to_activity(**kwargs) - del activity_object["@context"] - del activity_object["type"] - return activity_object - def get_current_year(): """sets default year for annual goal to this year""" @@ -427,7 +420,7 @@ class AnnualGoal(BookWyrmModel): goal = models.IntegerField(validators=[MinValueValidator(1)]) year = models.IntegerField(default=get_current_year) privacy = models.CharField( - max_length=255, default="public", choices=fields.PrivacyLevels.choices + max_length=255, default="public", choices=fields.PrivacyLevels ) class Meta: diff --git a/bookwyrm/redis_store.py b/bookwyrm/redis_store.py index 964409e89..ae50db2ee 100644 --- a/bookwyrm/redis_store.py +++ b/bookwyrm/redis_store.py @@ -8,7 +8,7 @@ r = redis.Redis( host=settings.REDIS_ACTIVITY_HOST, port=settings.REDIS_ACTIVITY_PORT, password=settings.REDIS_ACTIVITY_PASSWORD, - db=0, + db=settings.REDIS_ACTIVITY_DB_INDEX, ) diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index fe2f7467a..197e672c1 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -9,12 +9,12 @@ from django.utils.translation import gettext_lazy as _ env = Env() env.read_env() DOMAIN = env("DOMAIN") -VERSION = "0.1.1" +VERSION = "0.2.0" PAGE_LENGTH = env("PAGE_LENGTH", 15) DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English") -JS_CACHE = "2d3181e1" +JS_CACHE = "76c5ff1f" # email EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend") @@ -25,7 +25,7 @@ EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", True) EMAIL_USE_SSL = env.bool("EMAIL_USE_SSL", False) EMAIL_SENDER_NAME = env("EMAIL_SENDER_NAME", "admin") -EMAIL_SENDER_DOMAIN = env("EMAIL_SENDER_NAME", DOMAIN) +EMAIL_SENDER_DOMAIN = env("EMAIL_SENDER_DOMAIN", DOMAIN) EMAIL_SENDER = f"{EMAIL_SENDER_NAME}@{EMAIL_SENDER_DOMAIN}" # Build paths inside the project like this: os.path.join(BASE_DIR, ...) @@ -106,6 +106,58 @@ TEMPLATES = [ }, ] +LOG_LEVEL = env("LOG_LEVEL", "INFO").upper() +# Override aspects of the default handler to our taste +# See https://docs.djangoproject.com/en/3.2/topics/logging/#default-logging-configuration +# for a reference to the defaults we're overriding +# +# It seems that in order to override anything you have to include its +# entire dependency tree (handlers and filters) which makes this a +# bit verbose +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "filters": { + # These are copied from the default configuration, required for + # implementing mail_admins below + "require_debug_false": { + "()": "django.utils.log.RequireDebugFalse", + }, + "require_debug_true": { + "()": "django.utils.log.RequireDebugTrue", + }, + }, + "handlers": { + # Overrides the default handler to make it log to console + # regardless of the DEBUG setting (default is to not log to + # console if DEBUG=False) + "console": { + "level": LOG_LEVEL, + "class": "logging.StreamHandler", + }, + # This is copied as-is from the default logger, and is + # required for the django section below + "mail_admins": { + "level": "ERROR", + "filters": ["require_debug_false"], + "class": "django.utils.log.AdminEmailHandler", + }, + }, + "loggers": { + # Install our new console handler for Django's logger, and + # override the log level while we're at it + "django": { + "handlers": ["console", "mail_admins"], + "level": LOG_LEVEL, + }, + # Add a bookwyrm-specific logger + "bookwyrm": { + "handlers": ["console"], + "level": LOG_LEVEL, + }, + }, +} + WSGI_APPLICATION = "bookwyrm.wsgi.application" @@ -113,6 +165,7 @@ WSGI_APPLICATION = "bookwyrm.wsgi.application" REDIS_ACTIVITY_HOST = env("REDIS_ACTIVITY_HOST", "localhost") REDIS_ACTIVITY_PORT = env("REDIS_ACTIVITY_PORT", 6379) REDIS_ACTIVITY_PASSWORD = env("REDIS_ACTIVITY_PASSWORD", None) +REDIS_ACTIVITY_DB_INDEX = env("REDIS_ACTIVITY_DB_INDEX", 0) MAX_STREAM_LENGTH = int(env("MAX_STREAM_LENGTH", 200)) @@ -139,7 +192,7 @@ else: CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": f"redis://:{REDIS_ACTIVITY_PASSWORD}@{REDIS_ACTIVITY_HOST}:{REDIS_ACTIVITY_PORT}/0", + "LOCATION": f"redis://:{REDIS_ACTIVITY_PASSWORD}@{REDIS_ACTIVITY_HOST}:{REDIS_ACTIVITY_PORT}/{REDIS_ACTIVITY_DB_INDEX}", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", }, diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css index 4d960734e..f05ea3c91 100644 --- a/bookwyrm/static/css/bookwyrm.css +++ b/bookwyrm/static/css/bookwyrm.css @@ -720,6 +720,11 @@ ol.ordered-list li::before { } } +.overflow-wrap-anywhere { + overflow-wrap: anywhere; + min-width: 10em; +} + /* Threads ******************************************************************************/ @@ -751,6 +756,13 @@ ol.ordered-list li::before { padding: 0 0.75em; } +/* Notifications page + ******************************************************************************/ + +.notification a.icon { + text-decoration: none !important; +} + /* Breadcrumbs ******************************************************************************/ diff --git a/bookwyrm/static/js/autocomplete.js b/bookwyrm/static/js/autocomplete.js new file mode 100644 index 000000000..84474e43c --- /dev/null +++ b/bookwyrm/static/js/autocomplete.js @@ -0,0 +1,184 @@ +(function () { + "use strict"; + + /** + * Suggest a completion as a user types + * + * Use `data-autocomplete=""`on the input field. + * specifying the trie to be used for autocomplete + * + * @example + * + * @param {Event} event + * @return {undefined} + */ + function autocomplete(event) { + const input = event.target; + + // Get suggestions + let trie = tries[input.getAttribute("data-autocomplete")]; + + let suggestions = getSuggestions(input.value, trie); + + const boxId = input.getAttribute("list"); + + // Create suggestion box, if needed + let suggestionsBox = document.getElementById(boxId); + + // Clear existing suggestions + suggestionsBox.innerHTML = ""; + + // Populate suggestions box + suggestions.forEach((suggestion) => { + const suggestionItem = document.createElement("option"); + + suggestionItem.textContent = suggestion; + suggestionsBox.appendChild(suggestionItem); + }); + } + + function getSuggestions(input, trie) { + // Follow the trie through the provided input + input = input.toLowerCase(); + + input.split("").forEach((letter) => { + if (!trie) { + return; + } + + trie = trie[letter]; + }); + + if (!trie) { + return []; + } + + return searchTrie(trie); + } + + function searchTrie(trie) { + const options = Object.values(trie); + + if (typeof trie == "string") { + return [trie]; + } + + return options + .map((option) => { + const newTrie = option; + + if (typeof newTrie == "string") { + return [newTrie]; + } + + return searchTrie(newTrie); + }) + .reduce((prev, next) => prev.concat(next)); + } + + document.querySelectorAll("[data-autocomplete]").forEach((input) => { + input.addEventListener("input", autocomplete); + }); +})(); + +const tries = { + mimetype: { + a: { + a: { + c: "AAC", + }, + z: { + w: "AZW", + }, + }, + d: { + a: { + i: { + s: { + y: "Daisy", + }, + }, + }, + }, + e: { + p: { + u: { + b: "ePub", + }, + }, + }, + f: { + l: { + a: { + c: "FLAC", + }, + }, + }, + h: { + t: { + m: { + l: "HTML", + }, + }, + }, + m: { + 4: { + a: "M4A", + b: "M4B", + }, + o: { + b: { + i: "MOBI", + }, + }, + p: { + 3: "MP3", + }, + }, + o: { + g: { + g: "OGG", + }, + }, + p: { + d: { + f: "PDF", + }, + l: { + a: { + i: { + n: { + t: { + e: { + x: { + t: "Plaintext", + }, + }, + }, + }, + }, + }, + }, + r: { + i: { + n: { + t: { + " ": { + b: { + o: { + o: { + k: "Print book", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}; diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index cf3944b35..94163787d 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -35,7 +35,7 @@ let BookWyrm = new (class { .forEach((node) => node.addEventListener("change", this.disableIfTooLarge.bind(this))); document - .querySelectorAll("button[data-modal-open]") + .querySelectorAll("[data-modal-open]") .forEach((node) => node.addEventListener("click", this.handleModalButton.bind(this))); document diff --git a/bookwyrm/templates/about/about.html b/bookwyrm/templates/about/about.html index d39d70486..4e533b11a 100644 --- a/bookwyrm/templates/about/about.html +++ b/bookwyrm/templates/about/about.html @@ -2,7 +2,7 @@ {% load humanize %} {% load i18n %} {% load utilities %} -{% load bookwyrm_tags %} +{% load landing_page_tags %} {% load cache %} {% block title %} @@ -12,6 +12,7 @@ {% block about_content %} {# seven day cache #} {% cache 604800 about_page %} + {% get_book_superlatives as superlatives %}

@@ -26,7 +27,7 @@

- {% if top_rated %} + {% if superlatives.top_rated %} {% with book=superlatives.top_rated.default_edition rating=top_rated.rating %}
@@ -45,7 +46,7 @@ {% endwith %} {% endif %} - {% if wanted %} + {% if superlatives.wanted %} {% with book=superlatives.wanted.default_edition %}
@@ -64,7 +65,7 @@ {% endwith %} {% endif %} - {% if controversial %} + {% if superlatives.controversial %} {% with book=superlatives.controversial.default_edition %}
@@ -95,7 +96,7 @@

{% trans "Meet your admins" %}

{% url "conduct" as coc_path %} - {% blocktrans with site_name=site.name %} + {% blocktrans trimmed with site_name=site.name %} {{ site_name }}'s moderators and administrators keep the site up and running, enforce the code of conduct, and respond when users report spam and bad behavior. {% endblocktrans %}

diff --git a/bookwyrm/templates/author/author.html b/bookwyrm/templates/author/author.html index 2f3ed6f03..afbf31784 100644 --- a/bookwyrm/templates/author/author.html +++ b/bookwyrm/templates/author/author.html @@ -23,18 +23,13 @@
-
+
- {% if author.bio %} -
- {% include "snippets/trimmed_text.html" with full=author.bio trim_length=200 %} -
- {% endif %} {% firstof author.aliases author.born author.died as details %} {% firstof author.wikipedia_link author.openlibrary_key author.inventaire_id author.isni as links %} {% if details or links %} -
+
{% if details %}

{% trans "Author details" %}

@@ -71,7 +66,7 @@
{% if author.wikipedia_link %} @@ -79,7 +74,7 @@ {% if author.isni %} @@ -88,7 +83,7 @@ {% trans "Load data" as button_text %} {% if author.openlibrary_key %}
- + {% trans "View on OpenLibrary" %} {% if request.user.is_authenticated and perms.bookwyrm.edit_book %} @@ -103,7 +98,7 @@ {% if author.inventaire_id %}
- + {% trans "View on Inventaire" %} @@ -119,7 +114,7 @@ {% if author.librarything_key %} @@ -127,7 +122,7 @@ {% if author.goodreads_key %} @@ -137,26 +132,30 @@ {% endif %}
{% endif %} -
- +
+ {% if author.bio %} + {% include "snippets/trimmed_text.html" with full=author.bio trim_length=200 %} + {% endif %} -
-

{% blocktrans with name=author.name %}Books by {{ name }}{% endblocktrans %}

-
- {% for book in books %} -
-
- {% include 'landing/small-book.html' with book=book %} +

{% blocktrans with name=author.name %}Books by {{ name }}{% endblocktrans %}

+
+ {% for book in books %} + {% with book=book.default_edition %} +
+
+ {% include 'landing/small-book.html' with book=book %} +
+ {% include 'snippets/shelve_button/shelve_button.html' with book=book %}
- {% include 'snippets/shelve_button/shelve_button.html' with book=book %} + {% endwith %} + {% endfor %} +
+ +
+ {% include 'snippets/pagination.html' with page=books %}
- {% endfor %}
-
- {% include 'snippets/pagination.html' with page=books %} -
- {% endblock %} diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index 19dbccbd6..43f2171c3 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -1,6 +1,6 @@ {% extends 'layout.html' %} {% load i18n %} -{% load bookwyrm_tags %} +{% load book_display_tags %} {% load humanize %} {% load utilities %} {% load static %} @@ -122,7 +122,7 @@ {% trans "Load data" as button_text %} {% if book.openlibrary_key %}

- + {% trans "View on OpenLibrary" %} {% if request.user.is_authenticated and perms.bookwyrm.edit_book %} @@ -136,7 +136,7 @@ {% endif %} {% if book.inventaire_id %}

- + {% trans "View on Inventaire" %} @@ -183,7 +183,7 @@ {% include 'snippets/toggle/open_button.html' with text=button_text controls_text="add_description" controls_uid=book.id focus="id_description" hide_active=True id="hide_description" %}

- {% trans "Add read dates" as button_text %} - {% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="plus" class="is-small" controls_text="add_readthrough" focus="add_readthrough_focus_" %} +
- + {% include "readthrough/readthrough_modal.html" with id="add-readthrough" %} + {% if not readthroughs.exists %}

{% trans "You don't have any reading activity for this book." %}

{% endif %} {% for readthrough in readthroughs %} - {% include 'book/readthrough.html' with readthrough=readthrough %} + {% include 'readthrough/readthrough_list.html' with readthrough=readthrough %} {% endfor %}
@@ -327,7 +319,7 @@
-
+
{% if book.subjects %}

{% trans "Subjects" %}

@@ -352,11 +344,11 @@ {% endif %} {% if lists.exists or request.user.list_set.exists %} -
+

{% trans "Lists" %}

@@ -366,7 +358,7 @@
-
+
+ + +

+ {% trans "Links from unknown domains will need to be approved by a moderator before they are added." %} +

+ +
+
+ + + {% include 'snippets/form_errors.html' with errors_list=file_link_form.url.errors id="desc_url" %} +
+
+ + + + {% include 'snippets/form_errors.html' with errors_list=file_link_form.filetype.errors id="desc_filetype" %} +
+
+
+ +
+ {{ file_link_form.availability }} +
+
+ +{% endblock %} + +{% block modal-footer %} + +{% if not static %} + +{% endif %} + +{% endblock %} +{% block modal-form-close %}{% endblock %} diff --git a/bookwyrm/templates/book/file_links/edit_links.html b/bookwyrm/templates/book/file_links/edit_links.html new file mode 100644 index 000000000..39d3b998b --- /dev/null +++ b/bookwyrm/templates/book/file_links/edit_links.html @@ -0,0 +1,114 @@ +{% extends 'layout.html' %} +{% load i18n %} +{% load utilities %} + +{% block title %}{% trans "Edit links" %}{% endblock %} + +{% block content %} + +
+

+ {% blocktrans with title=book|book_title %} + Links for "{{ title }}" + {% endblocktrans %} +

+
+ + + +
+
+ + + + + + + + + + {% for link in links %} + + + + + + + + + + {% endfor %} + {% if not book.file_links.exists %} + + + + {% endif %} +
{% trans "URL" %}{% trans "Added by" %}{% trans "Filetype" %}{% trans "Domain" %}{% trans "Status" %}{% trans "Actions" %}
+ {{ link.url }} + + {{ link.added_by.display_name }} + + {{ link.filelink.filetype }} + + {{ link.domain.name }} +

+ {% trans "Report spam" %} +

+
+ {% with status=link.domain.status %} + + + + {{ link.domain.get_status_display }} + + + {% endwith %} + +
+ {% csrf_token %} + + + + +
+
+
+ {{ link.form.availability }} +
+
+
+ +
+
+
+
+
+ {% csrf_token %} + +
+
{% trans "No links available for this book." %}
+
+ + {% url 'file-link-add' book.id as fallback_url %} +
+ +
+
+ +{% endblock %} diff --git a/bookwyrm/templates/book/file_links/file_link_page.html b/bookwyrm/templates/book/file_links/file_link_page.html new file mode 100644 index 000000000..00efe5089 --- /dev/null +++ b/bookwyrm/templates/book/file_links/file_link_page.html @@ -0,0 +1,15 @@ +{% extends 'layout.html' %} +{% load i18n %} +{% load static %} + +{% block title %} +{% trans "File Links" %} +{% endblock %} + +{% block content %} +{% include "book/file_links/add_link_modal.html" with book=book active=True static=True id="file-link" %} +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/bookwyrm/templates/book/file_links/links.html b/bookwyrm/templates/book/file_links/links.html new file mode 100644 index 000000000..2147bf6e0 --- /dev/null +++ b/bookwyrm/templates/book/file_links/links.html @@ -0,0 +1,58 @@ +{% load i18n %} +{% load book_display_tags %} +{% load utilities %} + +{% get_book_file_links book as links %} +{% if links.exists or request.user.is_authenticated %} +
+
+

{% trans "Get a copy" %}

+
+ {% if can_edit_book %} +
+ {% url 'file-link-add' book.id as fallback_url %} +
+ +
+
+ {% endif %} +
+{% if links %} +
    + {% for link in links.all %} + {% join "verify" link.id as verify_modal %} +
  • + {{ link.name }} + ({{ link.filetype }}) + + {% if link.availability != "free" %} +

    + {{ link.get_availability_display }} +

    + {% endif %} +
  • + {% endfor %} +
+{% for link in links.all %} +{% join "verify" link.id as verify_modal %} +{% include "book/file_links/verification_modal.html" with id=verify_modal %} +{% endfor %} +{% else %} +{% trans "No links available" %} +{% endif %} + +{% if can_edit_book and links.exists %} + + + {% trans "Edit links" %} + +{% endif %} +{% include 'book/file_links/add_link_modal.html' with book=book id="add-links" %} + +{% endif %} diff --git a/bookwyrm/templates/book/file_links/verification_modal.html b/bookwyrm/templates/book/file_links/verification_modal.html new file mode 100644 index 000000000..81685da0f --- /dev/null +++ b/bookwyrm/templates/book/file_links/verification_modal.html @@ -0,0 +1,29 @@ +{% extends 'components/modal.html' %} +{% load i18n %} + +{% block modal-title %} +{% trans "Leaving BookWyrm" %} +{% endblock %} + + +{% block modal-body %} + +{% blocktrans trimmed with link_url=link.url %} +This link is taking you to: {{ link_url }}.
+Is that where you'd like to go? +{% endblocktrans %} + +{% endblock %} + + +{% block modal-footer %} +{% trans "Continue" %} + + +{% if request.user.is_authenticated %} + +{% endif %} + +{% endblock %} diff --git a/bookwyrm/templates/discover/large-book.html b/bookwyrm/templates/discover/large-book.html index 1fa0afb92..a6ff0aca0 100644 --- a/bookwyrm/templates/discover/large-book.html +++ b/bookwyrm/templates/discover/large-book.html @@ -1,4 +1,4 @@ -{% load bookwyrm_tags %} +{% load rating_tags %} {% load i18n %} {% load utilities %} {% load status_display %} diff --git a/bookwyrm/templates/discover/small-book.html b/bookwyrm/templates/discover/small-book.html index 76732ca14..2da93d522 100644 --- a/bookwyrm/templates/discover/small-book.html +++ b/bookwyrm/templates/discover/small-book.html @@ -1,4 +1,4 @@ -{% load bookwyrm_tags %} +{% load landing_page_tags %} {% load utilities %} {% load i18n %} {% load status_display %} diff --git a/bookwyrm/templates/feed/status.html b/bookwyrm/templates/feed/status.html index e7b9280d7..ed828ae01 100644 --- a/bookwyrm/templates/feed/status.html +++ b/bookwyrm/templates/feed/status.html @@ -1,6 +1,6 @@ {% extends 'feed/layout.html' %} +{% load feed_page_tags %} {% load i18n %} -{% load bookwyrm_tags %} {% block opengraph_images %} diff --git a/bookwyrm/templates/feed/suggested_books.html b/bookwyrm/templates/feed/suggested_books.html index 899767d13..435d4f513 100644 --- a/bookwyrm/templates/feed/suggested_books.html +++ b/bookwyrm/templates/feed/suggested_books.html @@ -1,5 +1,5 @@ {% load i18n %} -{% load bookwyrm_tags %} +{% load feed_page_tags %} {% suggested_books as suggested_books %}
@@ -16,10 +16,7 @@ {% with shelf_counter=forloop.counter %}
  • - {% if shelf.identifier == 'to-read' %}{% trans "To Read" %} - {% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %} - {% elif shelf.identifier == 'read' %}{% trans "Read" %} - {% else %}{{ shelf.name }}{% endif %} + {% include "snippets/translated_shelf_name.html" with shelf=shelf %}

      diff --git a/bookwyrm/templates/groups/group.html b/bookwyrm/templates/groups/group.html index 1a71bda89..5f5b58601 100644 --- a/bookwyrm/templates/groups/group.html +++ b/bookwyrm/templates/groups/group.html @@ -1,7 +1,6 @@ {% extends 'groups/layout.html' %} {% load i18n %} -{% load bookwyrm_tags %} -{% load bookwyrm_group_tags %} +{% load group_tags %} {% load markdown %} {% block panel %} diff --git a/bookwyrm/templates/groups/layout.html b/bookwyrm/templates/groups/layout.html index a25c10850..e688bc771 100644 --- a/bookwyrm/templates/groups/layout.html +++ b/bookwyrm/templates/groups/layout.html @@ -1,6 +1,6 @@ {% extends 'layout.html' %} {% load i18n %} -{% load bookwyrm_group_tags %} +{% load group_tags %} {% block title %}{{ group.name }}{% endblock %} diff --git a/bookwyrm/templates/groups/members.html b/bookwyrm/templates/groups/members.html index 8b06d178a..90236843f 100644 --- a/bookwyrm/templates/groups/members.html +++ b/bookwyrm/templates/groups/members.html @@ -1,8 +1,7 @@ {% load i18n %} {% load utilities %} {% load humanize %} -{% load bookwyrm_tags %} -{% load bookwyrm_group_tags %} +{% load group_tags %}

      Group Members

      {% if group.user == request.user %} diff --git a/bookwyrm/templates/import/tooltip.html b/bookwyrm/templates/import/tooltip.html index 311cce82c..f2712b7e9 100644 --- a/bookwyrm/templates/import/tooltip.html +++ b/bookwyrm/templates/import/tooltip.html @@ -3,6 +3,6 @@ {% block tooltip_content %} -{% trans 'You can download your Goodreads data from the Import/Export page of your Goodreads account.' %} +{% trans 'You can download your Goodreads data from the Import/Export page of your Goodreads account.' %} {% endblock %} diff --git a/bookwyrm/templates/landing/landing.html b/bookwyrm/templates/landing/landing.html index 759e8c619..ec8bcee06 100644 --- a/bookwyrm/templates/landing/landing.html +++ b/bookwyrm/templates/landing/landing.html @@ -1,13 +1,17 @@ {% extends 'landing/layout.html' %} {% load i18n %} {% load cache %} +{% load landing_page_tags %} + {% block panel %}

      {% trans "Recent Books" %}

      -{% cache 60 * 60 %} +{% get_current_language as LANGUAGE_CODE %} +{% cache 60 * 60 LANGUAGE_CODE %} +{% get_landing_books as books %}
      diff --git a/bookwyrm/templates/landing/large-book.html b/bookwyrm/templates/landing/large-book.html index 03ec718ba..9b4fd1f93 100644 --- a/bookwyrm/templates/landing/large-book.html +++ b/bookwyrm/templates/landing/large-book.html @@ -1,4 +1,5 @@ -{% load bookwyrm_tags %} +{% load book_display_tags %} +{% load rating_tags %} {% load markdown %} {% load i18n %} diff --git a/bookwyrm/templates/landing/small-book.html b/bookwyrm/templates/landing/small-book.html index 813fb797d..31b095803 100644 --- a/bookwyrm/templates/landing/small-book.html +++ b/bookwyrm/templates/landing/small-book.html @@ -1,4 +1,4 @@ -{% load bookwyrm_tags %} +{% load rating_tags %} {% load i18n %} {% if book %} diff --git a/bookwyrm/templates/lists/embed-list.html b/bookwyrm/templates/lists/embed-list.html index 54dc80ab2..186681670 100644 --- a/bookwyrm/templates/lists/embed-list.html +++ b/bookwyrm/templates/lists/embed-list.html @@ -1,7 +1,8 @@ {% extends 'embed-layout.html' %} {% load i18n %} -{% load bookwyrm_tags %} -{% load bookwyrm_group_tags %} +{% load book_display_tags %} +{% load rating_tags %} +{% load group_tags %} {% load markdown %} {% block title %}{% blocktrans with list_name=list.name owner=list.user.display_name %}{{ list_name }}, a list by {{owner}}{% endblocktrans %}{% endblock title %} diff --git a/bookwyrm/templates/lists/list.html b/bookwyrm/templates/lists/list.html index 880413cdb..c44d3fe36 100644 --- a/bookwyrm/templates/lists/list.html +++ b/bookwyrm/templates/lists/list.html @@ -1,7 +1,8 @@ {% extends 'lists/layout.html' %} {% load i18n %} -{% load bookwyrm_tags %} -{% load bookwyrm_group_tags %} +{% load rating_tags %} +{% load book_display_tags %} +{% load group_tags %} {% load markdown %} {% block breadcrumbs %} diff --git a/bookwyrm/templates/notifications/items/accept.html b/bookwyrm/templates/notifications/items/accept.html index 045e23266..5f26008f4 100644 --- a/bookwyrm/templates/notifications/items/accept.html +++ b/bookwyrm/templates/notifications/items/accept.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% load utilities %} diff --git a/bookwyrm/templates/notifications/items/add.html b/bookwyrm/templates/notifications/items/add.html index 0e653aeb8..6a0183ebe 100644 --- a/bookwyrm/templates/notifications/items/add.html +++ b/bookwyrm/templates/notifications/items/add.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% load utilities %} diff --git a/bookwyrm/templates/notifications/items/boost.html b/bookwyrm/templates/notifications/items/boost.html index 5f8962b38..6bb373ef6 100644 --- a/bookwyrm/templates/notifications/items/boost.html +++ b/bookwyrm/templates/notifications/items/boost.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% load utilities %} diff --git a/bookwyrm/templates/notifications/items/fav.html b/bookwyrm/templates/notifications/items/fav.html index fbb865e4f..58964d033 100644 --- a/bookwyrm/templates/notifications/items/fav.html +++ b/bookwyrm/templates/notifications/items/fav.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% load utilities %} diff --git a/bookwyrm/templates/notifications/items/follow.html b/bookwyrm/templates/notifications/items/follow.html index 7220d5d17..3518e7b1b 100644 --- a/bookwyrm/templates/notifications/items/follow.html +++ b/bookwyrm/templates/notifications/items/follow.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% load utilities %} diff --git a/bookwyrm/templates/notifications/items/follow_request.html b/bookwyrm/templates/notifications/items/follow_request.html index febb0a50e..9cec8116a 100644 --- a/bookwyrm/templates/notifications/items/follow_request.html +++ b/bookwyrm/templates/notifications/items/follow_request.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% load utilities %} diff --git a/bookwyrm/templates/notifications/items/import.html b/bookwyrm/templates/notifications/items/import.html index f3c8b5c09..7f5994811 100644 --- a/bookwyrm/templates/notifications/items/import.html +++ b/bookwyrm/templates/notifications/items/import.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% block primary_link %}{% spaceless %} diff --git a/bookwyrm/templates/notifications/items/invite.html b/bookwyrm/templates/notifications/items/invite.html index abb8cd02f..aff416b07 100644 --- a/bookwyrm/templates/notifications/items/invite.html +++ b/bookwyrm/templates/notifications/items/invite.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% load utilities %} diff --git a/bookwyrm/templates/notifications/items/join.html b/bookwyrm/templates/notifications/items/join.html index c10def456..82f8a8c50 100644 --- a/bookwyrm/templates/notifications/items/join.html +++ b/bookwyrm/templates/notifications/items/join.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% load utilities %} diff --git a/bookwyrm/templates/notifications/items/item_layout.html b/bookwyrm/templates/notifications/items/layout.html similarity index 68% rename from bookwyrm/templates/notifications/items/item_layout.html rename to bookwyrm/templates/notifications/items/layout.html index 506bda8dd..d595bf20e 100644 --- a/bookwyrm/templates/notifications/items/item_layout.html +++ b/bookwyrm/templates/notifications/items/layout.html @@ -1,9 +1,9 @@ -{% load bookwyrm_tags %} +{% load notification_page_tags %} {% related_status notification as related_status %} -
      -
      -
      - +
      +
      + diff --git a/bookwyrm/templates/notifications/items/leave.html b/bookwyrm/templates/notifications/items/leave.html index 422a31dea..c17a1986e 100644 --- a/bookwyrm/templates/notifications/items/leave.html +++ b/bookwyrm/templates/notifications/items/leave.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% load utilities %} diff --git a/bookwyrm/templates/notifications/items/mention.html b/bookwyrm/templates/notifications/items/mention.html index cda77163e..ead3c8a6c 100644 --- a/bookwyrm/templates/notifications/items/mention.html +++ b/bookwyrm/templates/notifications/items/mention.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% load utilities %} diff --git a/bookwyrm/templates/notifications/items/remove.html b/bookwyrm/templates/notifications/items/remove.html index eba18fd89..84160c7bd 100644 --- a/bookwyrm/templates/notifications/items/remove.html +++ b/bookwyrm/templates/notifications/items/remove.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% load utilities %} diff --git a/bookwyrm/templates/notifications/items/reply.html b/bookwyrm/templates/notifications/items/reply.html index 883bbbb5b..0aa664ce4 100644 --- a/bookwyrm/templates/notifications/items/reply.html +++ b/bookwyrm/templates/notifications/items/reply.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% load utilities %} diff --git a/bookwyrm/templates/notifications/items/report.html b/bookwyrm/templates/notifications/items/report.html index f537b5255..fdd5f0094 100644 --- a/bookwyrm/templates/notifications/items/report.html +++ b/bookwyrm/templates/notifications/items/report.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} diff --git a/bookwyrm/templates/notifications/items/update.html b/bookwyrm/templates/notifications/items/update.html index be796b785..7fc52cef1 100644 --- a/bookwyrm/templates/notifications/items/update.html +++ b/bookwyrm/templates/notifications/items/update.html @@ -1,4 +1,4 @@ -{% extends 'notifications/items/item_layout.html' %} +{% extends 'notifications/items/layout.html' %} {% load i18n %} {% load utilities %} diff --git a/bookwyrm/templates/opensearch.xml b/bookwyrm/templates/opensearch.xml index 651958ab4..3d5f124b3 100644 --- a/bookwyrm/templates/opensearch.xml +++ b/bookwyrm/templates/opensearch.xml @@ -3,7 +3,7 @@ xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/" > - BW + {{ site_name }} {% blocktrans trimmed with site_name=site.name %} {{ site_name }} search {% endblocktrans %} diff --git a/bookwyrm/templates/preferences/edit_user.html b/bookwyrm/templates/preferences/edit_user.html index b18eb4e98..642d177cb 100644 --- a/bookwyrm/templates/preferences/edit_user.html +++ b/bookwyrm/templates/preferences/edit_user.html @@ -34,26 +34,26 @@
      {{ form.avatar }} - {% include 'snippets/form_errors.html' with errors_list=form.avatar.errors id="desc_avatar" %} + {% include 'snippets/form_errors.html' with errors_list=form.avatar.errors id="desc_avatar" %}
      {{ form.name }} - {% include 'snippets/form_errors.html' with errors_list=form.name.errors id="desc_name" %} + {% include 'snippets/form_errors.html' with errors_list=form.name.errors id="desc_name" %}
      {{ form.summary }} - {% include 'snippets/form_errors.html' with errors_list=form.summary.errors id="desc_summary" %} + {% include 'snippets/form_errors.html' with errors_list=form.summary.errors id="desc_summary" %}
      {{ form.email }} - {% include 'snippets/form_errors.html' with errors_list=form.email.errors id="desc_email" %} + {% include 'snippets/form_errors.html' with errors_list=form.email.errors id="desc_email" %}
      diff --git a/bookwyrm/templates/book/delete_readthrough_modal.html b/bookwyrm/templates/readthrough/delete_readthrough_modal.html similarity index 100% rename from bookwyrm/templates/book/delete_readthrough_modal.html rename to bookwyrm/templates/readthrough/delete_readthrough_modal.html diff --git a/bookwyrm/templates/readthrough/readthrough.html b/bookwyrm/templates/readthrough/readthrough.html new file mode 100644 index 000000000..0b42017e6 --- /dev/null +++ b/bookwyrm/templates/readthrough/readthrough.html @@ -0,0 +1,15 @@ +{% extends 'layout.html' %} +{% load i18n %} +{% load utilities %} + +{% block title %} +{% blocktrans trimmed with title=book|book_title %} +Update read dates for "{{ title }}" +{% endblocktrans %} +{% endblock %} + +{% block content %} + +{% include "readthrough/readthrough_modal.html" with book=book active=True static=True %} + +{% endblock %} diff --git a/bookwyrm/templates/snippets/readthrough_form.html b/bookwyrm/templates/readthrough/readthrough_form.html similarity index 94% rename from bookwyrm/templates/snippets/readthrough_form.html rename to bookwyrm/templates/readthrough/readthrough_form.html index 295ad7c6e..1558dada4 100644 --- a/bookwyrm/templates/snippets/readthrough_form.html +++ b/bookwyrm/templates/readthrough/readthrough_form.html @@ -4,6 +4,7 @@ {% csrf_token %} +
    {% endif %} {% if perms.bookwyrm.edit_instance_settings %} diff --git a/bookwyrm/templates/settings/link_domains/edit_domain_modal.html b/bookwyrm/templates/settings/link_domains/edit_domain_modal.html new file mode 100644 index 000000000..135783e78 --- /dev/null +++ b/bookwyrm/templates/settings/link_domains/edit_domain_modal.html @@ -0,0 +1,25 @@ +{% extends 'components/modal.html' %} +{% load i18n %} + +{% block modal-title %} +{% blocktrans with url=domain.domain %}Set display name for {{ url }}{% endblocktrans %} +{% endblock %} + +{% block modal-form-open %} +
    +{% endblock %} + +{% block modal-body %} +{% csrf_token %} + +
    + +
    +{% endblock %} + +{% block modal-footer %} + + +{% endblock %} + +{% block modal-form-close %}
    {% endblock %} diff --git a/bookwyrm/templates/settings/link_domains/link_domains.html b/bookwyrm/templates/settings/link_domains/link_domains.html new file mode 100644 index 000000000..ceb0d8cb0 --- /dev/null +++ b/bookwyrm/templates/settings/link_domains/link_domains.html @@ -0,0 +1,106 @@ +{% extends 'settings/layout.html' %} +{% load i18n %} +{% load utilities %} + +{% block title %}{% trans "Link Domains" %}{% endblock %} + +{% block header %}{% trans "Link Domains" %}{% endblock %} + +{% block panel %} +

    + {% trans "Link domains must be approved before they are shown on book pages. Please make sure that the domains are not hosting spam, malicious code, or deceptive links before approving." %} +

    + +
    +
    + +
    + + {% for domain in domains %} + {% join "domain" domain.id as domain_modal %} +
    +
    +
    +

    + {{ domain.name }} + ({{ domain.domain }}) +

    +
    +
    + +
    +
    +
    +
    + + + {% trans "View links" %} + ({{ domain.links.count }}) + + + + +
    + {% include "settings/link_domains/link_table.html" with links=domain.links.all|slice:10 %} +
    +
    +
    + + {% include "settings/link_domains/edit_domain_modal.html" with domain=domain id=domain_modal %} + +
    + {% if status != "approved" %} +
    + {% csrf_token %} + +
    + {% endif %} + {% if status != "blocked" %} +
    + {% csrf_token %} + +
    + {% endif %} +
    +
    + {% endfor %} + + {% if not domains.exists %} + {% if status == "approved" %} + {% trans "No domains currently approved" %} + {% elif status == "pending" %} + {% trans "No domains currently pending" %} + {% else %} + {% trans "No domains currently blocked" %} + {% endif %} + {% endif %} +
    + +{% endblock %} + diff --git a/bookwyrm/templates/settings/link_domains/link_table.html b/bookwyrm/templates/settings/link_domains/link_table.html new file mode 100644 index 000000000..5518477c8 --- /dev/null +++ b/bookwyrm/templates/settings/link_domains/link_table.html @@ -0,0 +1,42 @@ +{% load i18n %} +{% load utilities %} + + + + + + + + {% block additional_headers %}{% endblock %} + + {% for link in links %} + + + + + + + + {% block additional_data %}{% endblock %} + + {% endfor %} + {% if not links %} + + + + {% endif %} +
    {% trans "URL" %}{% trans "Added by" %}{% trans "Filetype" %}{% trans "Book" %}
    + {{ link.url }} + + @{{ link.added_by|username }} + + {% if link.filelink.filetype %} + {{ link.filelink.filetype }} + {% endif %} + + {% if link.filelink.book %} + {% with book=link.filelink.book %} + {% include "snippets/book_titleby.html" with book=book %} + {% endwith %} + {% endif %} +
    {% trans "No links available for this domain." %}
    diff --git a/bookwyrm/templates/settings/reports/report.html b/bookwyrm/templates/settings/reports/report.html index 37593f3cc..84fafb143 100644 --- a/bookwyrm/templates/settings/reports/report.html +++ b/bookwyrm/templates/settings/reports/report.html @@ -2,10 +2,12 @@ {% load i18n %} {% load humanize %} -{% block title %}{% blocktrans with report_id=report.id username=report.user.username %}Report #{{ report_id }}: {{ username }}{% endblocktrans %}{% endblock %} +{% block title %} +{% include "settings/reports/report_header.html" with report=report %} +{% endblock %} {% block header %} -{% blocktrans with report_id=report.id username=report.user.username %}Report #{{ report_id }}: {{ username }}{% endblocktrans %} +{% include "settings/reports/report_header.html" with report=report %} {% trans "Back to reports" %} {% endblock %} @@ -15,6 +17,36 @@ {% include 'settings/reports/report_preview.html' with report=report %}
    +{% if report.statuses.exists %} +
    +

    {% trans "Reported statuses" %}

    +
      + {% for status in report.statuses.select_subclasses.all %} +
    • + {% if status.deleted %} + {% trans "Status has been deleted" %} + {% else %} + {% include 'snippets/status/status.html' with status=status moderation_mode=True %} + {% endif %} +
    • + {% endfor %} +
    +
    +{% endif %} + +{% if report.links.exists %} +
    +

    {% trans "Reported links" %}

    +
    +
    +
    + {% include "settings/reports/report_links_table.html" with links=report.links.all %} +
    +
    +
    +
    +{% endif %} + {% include 'settings/users/user_info.html' with user=report.user %} {% include 'settings/users/user_moderation_actions.html' with user=report.user %} @@ -41,23 +73,4 @@
  • - -
    -

    {% trans "Reported statuses" %}

    - {% if not report.statuses.exists %} - {% trans "No statuses reported" %} - {% else %} -
      - {% for status in report.statuses.select_subclasses.all %} -
    • - {% if status.deleted %} - {% trans "Status has been deleted" %} - {% else %} - {% include 'snippets/status/status.html' with status=status moderation_mode=True %} - {% endif %} -
    • - {% endfor %} -
    - {% endif %} -
    {% endblock %} diff --git a/bookwyrm/templates/settings/reports/report_header.html b/bookwyrm/templates/settings/reports/report_header.html new file mode 100644 index 000000000..d76db1048 --- /dev/null +++ b/bookwyrm/templates/settings/reports/report_header.html @@ -0,0 +1,22 @@ +{% load i18n %} +{% load utilities %} + +{% if report.statuses.exists %} + +{% blocktrans trimmed with report_id=report.id username=report.user|username %} +Report #{{ report_id }}: Status posted by @{{ username }} +{% endblocktrans %} + +{% elif report.links.exists %} + +{% blocktrans trimmed with report_id=report.id username=report.user|username %} +Report #{{ report_id }}: Link added by @{{ username }} +{% endblocktrans %} + +{% else %} + +{% blocktrans trimmed with report_id=report.id username=report.user|username %} +Report #{{ report_id }}: User @{{ username }} +{% endblocktrans %} + +{% endif %} diff --git a/bookwyrm/templates/settings/reports/report_links_table.html b/bookwyrm/templates/settings/reports/report_links_table.html new file mode 100644 index 000000000..b0ebf73a5 --- /dev/null +++ b/bookwyrm/templates/settings/reports/report_links_table.html @@ -0,0 +1,20 @@ +{% extends "settings/link_domains/link_table.html" %} +{% load i18n %} + +{% block additional_headers %} +{% trans "Domain" %} +{% trans "Actions" %} +{% endblock %} + +{% block additional_data %} + + + {{ link.domain.domain }} + + + +
    + +
    + +{% endblock %} diff --git a/bookwyrm/templates/settings/reports/report_preview.html b/bookwyrm/templates/settings/reports/report_preview.html index 363783d50..31bde0a1e 100644 --- a/bookwyrm/templates/settings/reports/report_preview.html +++ b/bookwyrm/templates/settings/reports/report_preview.html @@ -1,9 +1,13 @@ {% extends 'components/card.html' %} {% load i18n %} {% load humanize %} +{% load utilities %} + {% block card-header %}

    - {% blocktrans with report_id=report.id username=report.user.username %}Report #{{ report_id }}: {{ username }}{% endblocktrans %} + + {% include "settings/reports/report_header.html" with report=report %} +

    {% endblock %} @@ -17,7 +21,7 @@ {% block card-footer %} {% else %} -

    {% trans "Actions" %}

    +

    {% trans "User Actions" %}

    -
    - {% if user.is_active %} -

    - {% trans "Send direct message" %} -

    - {% endif %} +
    +
    + {% if user.is_active %} +

    + {% trans "Send direct message" %} +

    + {% endif %} - {% if user.is_active or user.deactivation_reason == "pending" %} -
    - {% csrf_token %} - -
    - {% else %} -
    - {% csrf_token %} - -
    + {% if user.is_active or user.deactivation_reason == "pending" %} +
    + {% csrf_token %} + +
    + {% else %} +
    + {% csrf_token %} + +
    + {% endif %} + + {% if user.local %} +
    + {% trans "Permanently delete user" as button_text %} + {% include "snippets/toggle/open_button.html" with controls_text="delete_user" text=button_text class="is-danger is-light" %} +
    + {% endif %} +
    + + {% if user.local %} +
    + {% include "settings/users/delete_user_form.html" with controls_text="delete_user" class="mt-2 mb-2" %} +
    {% endif %} {% if user.local %}
    - {% trans "Permanently delete user" as button_text %} - {% include "snippets/toggle/open_button.html" with controls_text="delete_user" text=button_text class="is-danger is-light" %} +
    + {% csrf_token %} + + {% if group_form.non_field_errors %} + {{ group_form.non_field_errors }} + {% endif %} + {% with group=user.groups.first %} +
    + +
    + + {% include 'snippets/form_errors.html' with errors_list=group_form.groups.errors id="desc_user_group" %} + {% endwith %} + +
    {% endif %}
    - {% if user.local %} -
    - {% include "settings/users/delete_user_form.html" with controls_text="delete_user" class="mt-2 mb-2" %} -
    - {% endif %} - - {% if user.local %} -
    -
    - {% csrf_token %} - - {% if group_form.non_field_errors %} - {{ group_form.non_field_errors }} - {% endif %} - {% with group=user.groups.first %} -
    - -
    - - {% include 'snippets/form_errors.html' with errors_list=group_form.groups.errors id="desc_user_group" %} - {% endwith %} - -
    -
    - {% endif %} - {% endif %}
    diff --git a/bookwyrm/templates/shelf/shelf.html b/bookwyrm/templates/shelf/shelf.html index 0184ab1d8..3a5652219 100644 --- a/bookwyrm/templates/shelf/shelf.html +++ b/bookwyrm/templates/shelf/shelf.html @@ -1,5 +1,5 @@ {% extends 'layout.html' %} -{% load bookwyrm_tags %} +{% load shelf_tags %} {% load utilities %} {% load humanize %} {% load i18n %} @@ -19,6 +19,17 @@

    + +