diff --git a/.env.dev.example b/.env.dev.example index 2f24378c6..f407c52b1 100644 --- a/.env.dev.example +++ b/.env.dev.example @@ -13,16 +13,10 @@ DEFAULT_LANGUAGE="English" ## Leave unset to allow all hosts # ALLOWED_HOSTS="localhost,127.0.0.1,[::1]" -OL_URL=https://openlibrary.org - -## Database backend to use. -## Default is postgres, sqlite is for dev quickstart only (NOT production!!!) -BOOKWYRM_DATABASE_BACKEND=postgres - MEDIA_ROOT=images/ POSTGRES_PORT=5432 -POSTGRES_PASSWORD=fedireads +POSTGRES_PASSWORD=securedbypassword123 POSTGRES_USER=fedireads POSTGRES_DB=fedireads POSTGRES_HOST=db @@ -34,10 +28,8 @@ REDIS_ACTIVITY_PORT=6379 #REDIS_ACTIVITY_PASSWORD=redispassword345 # Redis as celery broker -#REDIS_BROKER_PORT=6379 +REDIS_BROKER_PORT=6379 #REDIS_BROKER_PASSWORD=redispassword123 -CELERY_BROKER=redis://redis_broker:6379/0 -CELERY_RESULT_BACKEND=redis://redis_broker:6379/0 FLOWER_PORT=8888 #FLOWER_USER=mouse @@ -50,5 +42,14 @@ EMAIL_HOST_PASSWORD=emailpassword123 EMAIL_USE_TLS=true EMAIL_USE_SSL=false -# Set this to true when initializing certbot for domain, false when not -CERTBOT_INIT=false +# Preview image generation can be computing and storage intensive +# ENABLE_PREVIEW_IMAGES=True + +# Specify RGB tuple or RGB hex strings, +# or use_dominant_color_light / use_dominant_color_dark +PREVIEW_BG_COLOR=use_dominant_color_light +# Change to #FFF if you use use_dominant_color_dark +PREVIEW_TEXT_COLOR="#363636" +PREVIEW_IMG_WIDTH=1200 +PREVIEW_IMG_HEIGHT=630 +PREVIEW_DEFAULT_COVER_COLOR="#002549" diff --git a/.env.prod.example b/.env.prod.example index a82499a32..d61c46af5 100644 --- a/.env.prod.example +++ b/.env.prod.example @@ -13,16 +13,10 @@ DEFAULT_LANGUAGE="English" ## Leave unset to allow all hosts # ALLOWED_HOSTS="localhost,127.0.0.1,[::1]" -OL_URL=https://openlibrary.org - -## Database backend to use. -## Default is postgres, sqlite is for dev quickstart only (NOT production!!!) -BOOKWYRM_DATABASE_BACKEND=postgres - MEDIA_ROOT=images/ POSTGRES_PORT=5432 -POSTGRES_PASSWORD=securedbpassword123 +POSTGRES_PASSWORD=securedbypassword123 POSTGRES_USER=fedireads POSTGRES_DB=fedireads POSTGRES_HOST=db @@ -36,8 +30,6 @@ REDIS_ACTIVITY_PASSWORD=redispassword345 # Redis as celery broker REDIS_BROKER_PORT=6379 REDIS_BROKER_PASSWORD=redispassword123 -CELERY_BROKER=redis://:${REDIS_BROKER_PASSWORD}@redis_broker:${REDIS_BROKER_PORT}/0 -CELERY_RESULT_BACKEND=redis://:${REDIS_BROKER_PASSWORD}@redis_broker:${REDIS_BROKER_PORT}/0 FLOWER_PORT=8888 FLOWER_USER=mouse @@ -50,5 +42,14 @@ EMAIL_HOST_PASSWORD=emailpassword123 EMAIL_USE_TLS=true EMAIL_USE_SSL=false -# Set this to true when initializing certbot for domain, false when not -CERTBOT_INIT=false +# Preview image generation can be computing and storage intensive +# ENABLE_PREVIEW_IMAGES=True + +# Specify RGB tuple or RGB hex strings, +# or use_dominant_color_light / use_dominant_color_dark +PREVIEW_BG_COLOR=use_dominant_color_light +# Change to #FFF if you use use_dominant_color_dark +PREVIEW_TEXT_COLOR="#363636" +PREVIEW_IMG_WIDTH=1200 +PREVIEW_IMG_HEIGHT=630 +PREVIEW_DEFAULT_COVER_COLOR="#002549" diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml index 25af1e08d..fb681dcd5 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/black.yml @@ -1,4 +1,4 @@ -name: Lint Python +name: Python Formatting (run ./bw-dev black to fix) on: [push, pull_request] diff --git a/.github/workflows/django-tests.yml b/.github/workflows/django-tests.yml index 3ce368ecd..b5b319f53 100644 --- a/.github/workflows/django-tests.yml +++ b/.github/workflows/django-tests.yml @@ -50,7 +50,6 @@ jobs: SECRET_KEY: beepbeep DEBUG: true DOMAIN: your.domain.here - OL_URL: https://openlibrary.org BOOKWYRM_DATABASE_BACKEND: postgres MEDIA_ROOT: images/ POSTGRES_PASSWORD: hunter2 @@ -58,11 +57,13 @@ jobs: POSTGRES_DB: github_actions POSTGRES_HOST: 127.0.0.1 CELERY_BROKER: "" - CELERY_RESULT_BACKEND: "" + REDIS_BROKER_PORT: 6379 + FLOWER_PORT: 8888 EMAIL_HOST: "smtp.mailgun.org" EMAIL_PORT: 587 EMAIL_HOST_USER: "" EMAIL_HOST_PASSWORD: "" EMAIL_USE_TLS: true + ENABLE_PREVIEW_IMAGES: true run: | python manage.py test diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 000000000..1a32940f9 --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,24 @@ +name: Pylint + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pylint + - name: Analysing the code with pylint + run: | + pylint bookwyrm/ --ignore=migrations,tests --disable=E1101,E1135,E1136,R0903,R0901,R0902,W0707,W0511,W0406,R0401,R0801 + diff --git a/.gitignore b/.gitignore index cf88e9878..624ce100c 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ #nginx nginx/default.conf + +#macOS +**/.DS_Store diff --git a/Dockerfile b/Dockerfile index 0f10015c6..1892ae234 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,5 +9,3 @@ WORKDIR /app COPY requirements.txt /app/ RUN pip install -r requirements.txt --no-cache-dir RUN apt-get update && apt-get install -y gettext libgettextpo-dev && apt-get clean - -COPY ./bookwyrm ./celerywyrm /app/ diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 5349e1dd0..81762388f 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -37,6 +37,7 @@ class Mention(Link): @dataclass +# pylint: disable=invalid-name class Signature: """public key block""" @@ -56,11 +57,11 @@ def naive_parse(activity_objects, activity_json, serializer=None): activity_type = activity_json.get("type") try: serializer = activity_objects[activity_type] - except KeyError as e: + except KeyError as err: # we know this exists and that we can't handle it if activity_type in ["Question"]: return None - raise ActivitySerializerError(e) + raise ActivitySerializerError(err) return serializer(activity_objects=activity_objects, **activity_json) diff --git a/bookwyrm/activitypub/book.py b/bookwyrm/activitypub/book.py index 1599b408a..bd27c4e60 100644 --- a/bookwyrm/activitypub/book.py +++ b/bookwyrm/activitypub/book.py @@ -6,6 +6,7 @@ from .base_activity import ActivityObject from .image import Document +# pylint: disable=invalid-name @dataclass(init=False) class BookData(ActivityObject): """shared fields for all book data and authors""" @@ -18,6 +19,7 @@ class BookData(ActivityObject): lastEditedBy: str = None +# pylint: disable=invalid-name @dataclass(init=False) class Book(BookData): """serializes an edition or work, abstract""" @@ -40,6 +42,7 @@ class Book(BookData): type: str = "Book" +# pylint: disable=invalid-name @dataclass(init=False) class Edition(Book): """Edition instance of a book object""" @@ -57,6 +60,7 @@ class Edition(Book): type: str = "Edition" +# pylint: disable=invalid-name @dataclass(init=False) class Work(Book): """work instance of a book object""" @@ -66,6 +70,7 @@ class Work(Book): type: str = "Work" +# pylint: disable=invalid-name @dataclass(init=False) class Author(BookData): """author of a book""" diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py index ea2e92b6e..916da2d0e 100644 --- a/bookwyrm/activitypub/note.py +++ b/bookwyrm/activitypub/note.py @@ -19,6 +19,7 @@ class Tombstone(ActivityObject): return model.find_existing_by_remote_id(self.id) +# pylint: disable=invalid-name @dataclass(init=False) class Note(ActivityObject): """Note activity""" @@ -52,6 +53,7 @@ class GeneratedNote(Note): type: str = "GeneratedNote" +# pylint: disable=invalid-name @dataclass(init=False) class Comment(Note): """like a note but with a book""" diff --git a/bookwyrm/activitypub/ordered_collection.py b/bookwyrm/activitypub/ordered_collection.py index e3a83be8e..32e37c996 100644 --- a/bookwyrm/activitypub/ordered_collection.py +++ b/bookwyrm/activitypub/ordered_collection.py @@ -5,6 +5,7 @@ from typing import List from .base_activity import ActivityObject +# pylint: disable=invalid-name @dataclass(init=False) class OrderedCollection(ActivityObject): """structure of an ordered collection activity""" @@ -17,6 +18,7 @@ class OrderedCollection(ActivityObject): type: str = "OrderedCollection" +# pylint: disable=invalid-name @dataclass(init=False) class OrderedCollectionPrivate(OrderedCollection): """an ordered collection with privacy settings""" @@ -41,6 +43,7 @@ class BookList(OrderedCollectionPrivate): type: str = "BookList" +# pylint: disable=invalid-name @dataclass(init=False) class OrderedCollectionPage(ActivityObject): """structure of an ordered collection activity""" diff --git a/bookwyrm/activitypub/person.py b/bookwyrm/activitypub/person.py index d5f379461..174ead616 100644 --- a/bookwyrm/activitypub/person.py +++ b/bookwyrm/activitypub/person.py @@ -6,6 +6,7 @@ from .base_activity import ActivityObject from .image import Image +# pylint: disable=invalid-name @dataclass(init=False) class PublicKey(ActivityObject): """public key block""" @@ -15,6 +16,7 @@ class PublicKey(ActivityObject): type: str = "PublicKey" +# pylint: disable=invalid-name @dataclass(init=False) class Person(ActivityObject): """actor activitypub json""" diff --git a/bookwyrm/activitypub/response.py b/bookwyrm/activitypub/response.py index 07f39c7e1..e480b85df 100644 --- a/bookwyrm/activitypub/response.py +++ b/bookwyrm/activitypub/response.py @@ -1,3 +1,4 @@ +""" ActivityPub-specific json response wrapper """ from django.http import JsonResponse from .base_activity import ActivityEncoder diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index f26936d7b..50a479b71 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -22,6 +22,7 @@ class Verb(ActivityObject): self.object.to_model() +# pylint: disable=invalid-name @dataclass(init=False) class Create(Verb): """Create activity""" @@ -32,6 +33,7 @@ class Create(Verb): type: str = "Create" +# pylint: disable=invalid-name @dataclass(init=False) class Delete(Verb): """Create activity""" @@ -57,6 +59,7 @@ class Delete(Verb): # if we can't find it, we don't need to delete it because we don't have it +# pylint: disable=invalid-name @dataclass(init=False) class Update(Verb): """Update activity""" @@ -192,6 +195,7 @@ class Like(Verb): self.to_model() +# pylint: disable=invalid-name @dataclass(init=False) class Announce(Verb): """boosting a status""" diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index 86321cd83..a49a7ce4d 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -55,6 +55,8 @@ class ActivityStream(RedisStore): return ( models.Status.objects.select_subclasses() .filter(id__in=statuses) + .select_related("user", "reply_parent") + .prefetch_related("mention_books", "mention_users") .order_by("-published_date") ) @@ -199,6 +201,19 @@ def add_status_on_create(sender, instance, created, *args, **kwargs): for stream in streams.values(): stream.add_status(instance) + if sender != models.Boost: + return + # remove the original post and other, earlier boosts + boosted = instance.boost.boosted_status + old_versions = models.Boost.objects.filter( + boosted_status__id=boosted.id, + created_date__lt=instance.created_date, + ) + for stream in streams.values(): + stream.remove_object_from_related_stores(boosted) + for status in old_versions: + stream.remove_object_from_related_stores(status) + @receiver(signals.post_delete, sender=models.Boost) # pylint: disable=unused-argument @@ -206,7 +221,10 @@ def remove_boost_on_delete(sender, instance, *args, **kwargs): """boosts are deleted""" # we're only interested in new statuses for stream in streams.values(): + # remove the boost stream.remove_object_from_related_stores(instance) + # re-add the original status + stream.add_status(instance.boosted_status) @receiver(signals.post_save, sender=models.UserFollows) diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py index 606678150..6c032b830 100644 --- a/bookwyrm/connectors/abstract_connector.py +++ b/bookwyrm/connectors/abstract_connector.py @@ -37,7 +37,7 @@ class AbstractMinimalConnector(ABC): for field in self_fields: setattr(self, field, getattr(info, field)) - def search(self, query, min_confidence=None): + def search(self, query, min_confidence=None, timeout=5): """free text search""" params = {} if min_confidence: @@ -46,6 +46,7 @@ class AbstractMinimalConnector(ABC): data = self.get_search_data( "%s%s" % (self.search_url, query), params=params, + timeout=timeout, ) results = [] @@ -126,8 +127,8 @@ class AbstractConnector(AbstractMinimalConnector): edition_data = data try: work_data = self.get_work_from_edition_data(data) - except (KeyError, ConnectorException) as e: - logger.exception(e) + except (KeyError, ConnectorException) as err: + logger.exception(err) work_data = data if not work_data or not edition_data: @@ -218,7 +219,7 @@ def dict_from_mappings(data, mappings): return result -def get_data(url, params=None): +def get_data(url, params=None, timeout=10): """wrapper for request.get""" # check if the url is blocked if models.FederatedServer.is_blocked(url): @@ -234,23 +235,24 @@ def get_data(url, params=None): "Accept": "application/json; charset=utf-8", "User-Agent": settings.USER_AGENT, }, + timeout=timeout, ) - except (RequestError, SSLError, ConnectionError) as e: - logger.exception(e) + except (RequestError, SSLError, ConnectionError) as err: + logger.exception(err) raise ConnectorException() if not resp.ok: raise ConnectorException() try: data = resp.json() - except ValueError as e: - logger.exception(e) + except ValueError as err: + logger.exception(err) raise ConnectorException() return data -def get_image(url): +def get_image(url, timeout=10): """wrapper for requesting an image""" try: resp = requests.get( @@ -258,9 +260,10 @@ def get_image(url): headers={ "User-Agent": settings.USER_AGENT, }, + timeout=timeout, ) - except (RequestError, SSLError) as e: - logger.exception(e) + except (RequestError, SSLError) as err: + logger.exception(err) return None if not resp.ok: return None diff --git a/bookwyrm/connectors/connector_manager.py b/bookwyrm/connectors/connector_manager.py index 95c5959df..1a615c9b2 100644 --- a/bookwyrm/connectors/connector_manager.py +++ b/bookwyrm/connectors/connector_manager.py @@ -1,4 +1,5 @@ """ interface with whatever connectors the app has """ +from datetime import datetime import importlib import logging import re @@ -29,23 +30,25 @@ def search(query, min_confidence=0.1, return_first=False): isbn = re.sub(r"[\W_]", "", query) maybe_isbn = len(isbn) in [10, 13] # ISBN10 or ISBN13 + timeout = 15 + start_time = datetime.now() for connector in get_connectors(): result_set = None - if maybe_isbn and connector.isbn_search_url and connector.isbn_search_url == "": + if maybe_isbn and connector.isbn_search_url and connector.isbn_search_url != "": # Search on ISBN try: result_set = connector.isbn_search(isbn) - except Exception as e: # pylint: disable=broad-except - logger.exception(e) + except Exception as err: # pylint: disable=broad-except + logger.exception(err) # if this fails, we can still try regular search # if no isbn search results, we fallback to generic search if not result_set: try: result_set = connector.search(query, min_confidence=min_confidence) - except Exception as e: # pylint: disable=broad-except + except Exception as err: # pylint: disable=broad-except # we don't want *any* error to crash the whole search page - logger.exception(e) + logger.exception(err) continue if return_first and result_set: @@ -59,6 +62,8 @@ def search(query, min_confidence=0.1, return_first=False): "results": result_set, } ) + if (datetime.now() - start_time).seconds >= timeout: + break if return_first: return None diff --git a/bookwyrm/connectors/inventaire.py b/bookwyrm/connectors/inventaire.py index 102c9d727..116aa5c11 100644 --- a/bookwyrm/connectors/inventaire.py +++ b/bookwyrm/connectors/inventaire.py @@ -74,7 +74,7 @@ class Connector(AbstractConnector): **{k: data.get(k) for k in ["uri", "image", "labels", "sitelinks"]}, } - def search(self, query, min_confidence=None): + def search(self, query, min_confidence=None): # pylint: disable=arguments-differ """overrides default search function with confidence ranking""" results = super().search(query) if min_confidence: diff --git a/bookwyrm/connectors/self_connector.py b/bookwyrm/connectors/self_connector.py index a8f858345..930b7cb3d 100644 --- a/bookwyrm/connectors/self_connector.py +++ b/bookwyrm/connectors/self_connector.py @@ -3,7 +3,7 @@ from functools import reduce import operator from django.contrib.postgres.search import SearchRank, SearchVector -from django.db.models import Count, OuterRef, Subquery, F, Q +from django.db.models import OuterRef, Subquery, F, Q from bookwyrm import models from .abstract_connector import AbstractConnector, SearchResult @@ -114,6 +114,7 @@ class Connector(AbstractConnector): def search_identifiers(query, *filters): """tries remote_id, isbn; defined as dedupe fields on the model""" + # pylint: disable=W0212 or_filters = [ {f.name: query} for f in models.Edition._meta.get_fields() @@ -122,6 +123,8 @@ def search_identifiers(query, *filters): results = models.Edition.objects.filter( *filters, reduce(operator.or_, (Q(**f) for f in or_filters)) ).distinct() + if results.count() <= 1: + return results # when there are multiple editions of the same work, pick the default. # it would be odd for this to happen. @@ -146,19 +149,15 @@ def search_title_author(query, min_confidence, *filters): ) results = ( - models.Edition.objects.annotate(search=vector) - .annotate(rank=SearchRank(vector, query)) + models.Edition.objects.annotate(rank=SearchRank(vector, query)) .filter(*filters, rank__gt=min_confidence) .order_by("-rank") ) # when there are multiple editions of the same work, pick the closest - editions_of_work = ( - results.values("parent_work") - .annotate(Count("parent_work")) - .values_list("parent_work") - ) + editions_of_work = results.values("parent_work__id").values_list("parent_work__id") + # filter out multiple editions of the same work for work_id in set(editions_of_work): editions = results.filter(parent_work=work_id) default = editions.order_by("-edition_rank").first() diff --git a/bookwyrm/context_processors.py b/bookwyrm/context_processors.py index b77c62b02..f2661a193 100644 --- a/bookwyrm/context_processors.py +++ b/bookwyrm/context_processors.py @@ -1,10 +1,20 @@ """ customize the info available in context for rendering templates """ -from bookwyrm import models +from bookwyrm import models, settings def site_settings(request): # pylint: disable=unused-argument """include the custom info about the site""" + request_protocol = "https://" + if not request.is_secure(): + request_protocol = "http://" + return { "site": models.SiteSettings.objects.get(), "active_announcements": models.Announcement.active_announcements(), + "static_url": settings.STATIC_URL, + "media_url": settings.MEDIA_URL, + "static_path": settings.STATIC_PATH, + "media_path": settings.MEDIA_PATH, + "preview_images_enabled": settings.ENABLE_PREVIEW_IMAGES, + "request_protocol": request_protocol, } diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index 25b72a119..cb55d229e 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -22,6 +22,7 @@ class CustomForm(ModelForm): css_classes["number"] = "input" css_classes["checkbox"] = "checkbox" css_classes["textarea"] = "textarea" + # pylint: disable=super-with-arguments super(CustomForm, self).__init__(*args, **kwargs) for visible in self.visible_fields(): if hasattr(visible.field.widget, "input_type"): @@ -150,6 +151,12 @@ class LimitedEditUserForm(CustomForm): help_texts = {f: None for f in fields} +class DeleteUserForm(CustomForm): + class Meta: + model = models.User + fields = ["password"] + + class UserGroupForm(CustomForm): class Meta: model = models.User @@ -175,8 +182,6 @@ class EditionForm(CustomForm): "authors", "parent_work", "shelves", - "subjects", # TODO - "subject_places", # TODO "connector", ] diff --git a/bookwyrm/importers/importer.py b/bookwyrm/importers/importer.py index 89c62e735..203db0343 100644 --- a/bookwyrm/importers/importer.py +++ b/bookwyrm/importers/importer.py @@ -67,8 +67,8 @@ def import_data(source, job_id): for item in job.items.all(): try: item.resolve() - except Exception as e: # pylint: disable=broad-except - logger.exception(e) + except Exception as err: # pylint: disable=broad-except + logger.exception(err) item.fail_reason = "Error loading book" item.save() continue diff --git a/bookwyrm/management/commands/generate_preview_images.py b/bookwyrm/management/commands/generate_preview_images.py new file mode 100644 index 000000000..df2186238 --- /dev/null +++ b/bookwyrm/management/commands/generate_preview_images.py @@ -0,0 +1,65 @@ +""" Generate preview images """ +from django.core.management.base import BaseCommand + +from bookwyrm import models, preview_images + + +# pylint: disable=line-too-long +class Command(BaseCommand): + """Creates previews for existing objects""" + + help = "Generate preview images" + + def add_arguments(self, parser): + parser.add_argument( + "--all", + "-a", + action="store_true", + help="Generates images for ALL types: site, users and books. Can use a lot of computing power.", + ) + + # pylint: disable=no-self-use,unused-argument + def handle(self, *args, **options): + """generate preview images""" + self.stdout.write( + " | Hello! I will be generating preview images for your instance." + ) + if options["all"]: + self.stdout.write( + "🧑‍🎨 ⎨ This might take quite long if your instance has a lot of books and users." + ) + self.stdout.write(" | ✧ Thank you for your patience ✧") + else: + self.stdout.write("🧑‍🎨 ⎨ I will only generate the instance preview image.") + self.stdout.write(" | ✧ Be right back! ✧") + + # Site + self.stdout.write(" → Site preview image: ", ending="") + preview_images.generate_site_preview_image_task.delay() + self.stdout.write(" OK 🖼") + + if options["all"]: + # Users + users = models.User.objects.filter( + local=True, + is_active=True, + ) + self.stdout.write( + " → User preview images ({}): ".format(len(users)), ending="" + ) + for user in users: + preview_images.generate_user_preview_image_task.delay(user.id) + self.stdout.write(".", ending="") + self.stdout.write(" OK 🖼") + + # Books + books = models.Book.objects.select_subclasses().filter() + self.stdout.write( + " → Book preview images ({}): ".format(len(books)), ending="" + ) + for book in books: + preview_images.generate_edition_preview_image_task.delay(book.id) + self.stdout.write(".", ending="") + self.stdout.write(" OK 🖼") + + self.stdout.write("🧑‍🎨 ⎨ I’m all done! ✧ Enjoy ✧") diff --git a/bookwyrm/migrations/0076_preview_images.py b/bookwyrm/migrations/0076_preview_images.py new file mode 100644 index 000000000..8812e9b22 --- /dev/null +++ b/bookwyrm/migrations/0076_preview_images.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2 on 2021-05-26 12:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0075_announcement"), + ] + + operations = [ + migrations.AddField( + model_name="book", + name="preview_image", + field=models.ImageField( + blank=True, null=True, upload_to="previews/covers/" + ), + ), + migrations.AddField( + model_name="sitesettings", + name="preview_image", + field=models.ImageField(blank=True, null=True, upload_to="previews/logos/"), + ), + migrations.AddField( + model_name="user", + name="preview_image", + field=models.ImageField( + blank=True, null=True, upload_to="previews/avatars/" + ), + ), + ] diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 869ff04d2..d79ce206d 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -2,10 +2,13 @@ import re from django.db import models +from django.dispatch import receiver +from model_utils import FieldTracker from model_utils.managers import InheritanceManager from bookwyrm import activitypub -from bookwyrm.settings import DOMAIN, DEFAULT_LANGUAGE +from bookwyrm.preview_images import generate_edition_preview_image_task +from bookwyrm.settings import DOMAIN, DEFAULT_LANGUAGE, ENABLE_PREVIEW_IMAGES from .activitypub_mixin import OrderedCollectionPageMixin, ObjectMixin from .base_model import BookWyrmModel @@ -82,10 +85,14 @@ class Book(BookDataModel): cover = fields.ImageField( upload_to="covers/", blank=True, null=True, alt_field="alt_text" ) + preview_image = models.ImageField( + upload_to="previews/covers/", blank=True, null=True + ) first_published_date = fields.DateTimeField(blank=True, null=True) published_date = fields.DateTimeField(blank=True, null=True) objects = InheritanceManager() + field_tracker = FieldTracker(fields=["authors", "title", "subtitle", "cover"]) @property def author_text(self): @@ -293,3 +300,17 @@ def isbn_13_to_10(isbn_13): if checkdigit == 10: checkdigit = "X" return converted + str(checkdigit) + + +# pylint: disable=unused-argument +@receiver(models.signals.post_save, sender=Edition) +def preview_image(instance, *args, **kwargs): + """create preview image on book create""" + if not ENABLE_PREVIEW_IMAGES: + return + changed_fields = {} + if instance.field_tracker: + changed_fields = instance.field_tracker.changed() + + if len(changed_fields) > 0: + generate_edition_preview_image_task.delay(instance.id) diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 123b3efa4..4ec46504c 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -1,5 +1,6 @@ """ activitypub-aware django model fields """ from dataclasses import MISSING +import imghdr import re from uuid import uuid4 @@ -9,6 +10,7 @@ from django.contrib.postgres.fields import ArrayField as DjangoArrayField from django.core.exceptions import ValidationError from django.core.files.base import ContentFile from django.db import models +from django.forms import ClearableFileInput, ImageField as DjangoImageField from django.utils import timezone from django.utils.translation import gettext_lazy as _ from bookwyrm import activitypub @@ -200,6 +202,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField): *args, max_length=255, choices=PrivacyLevels.choices, default="public" ) + # pylint: disable=invalid-name def set_field_from_activity(self, instance, data): to = data.to cc = data.cc @@ -218,6 +221,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField): if hasattr(instance, "mention_users"): mentions = [u.remote_id for u in instance.mention_users.all()] # this is a link to the followers list + # pylint: disable=protected-access followers = instance.user.__class__._meta.get_field( "followers" ).field_to_activity(instance.user.followers) @@ -332,6 +336,18 @@ class TagField(ManyToManyField): return items +class ClearableFileInputWithWarning(ClearableFileInput): + """max file size warning""" + + template_name = "widgets/clearable_file_input_with_warning.html" + + +class CustomImageField(DjangoImageField): + """overwrites image field for form""" + + widget = ClearableFileInputWithWarning + + def image_serializer(value, alt): """helper for serializing images""" if value and hasattr(value, "url"): @@ -391,10 +407,19 @@ class ImageField(ActivitypubFieldMixin, models.ImageField): if not response: return None - image_name = str(uuid4()) + "." + url.split(".")[-1] image_content = ContentFile(response.content) + image_name = str(uuid4()) + "." + imghdr.what(None, image_content.read()) return [image_name, image_content] + def formfield(self, **kwargs): + """special case for forms""" + return super().formfield( + **{ + "form_class": CustomImageField, + **kwargs, + } + ) + class DateTimeField(ActivitypubFieldMixin, models.DateTimeField): """activitypub-aware datetime field""" diff --git a/bookwyrm/models/import_job.py b/bookwyrm/models/import_job.py index c8130af26..f29938469 100644 --- a/bookwyrm/models/import_job.py +++ b/bookwyrm/models/import_job.py @@ -75,7 +75,12 @@ class ImportItem(models.Model): def resolve(self): """try various ways to lookup a book""" - self.book = self.get_book_from_isbn() or self.get_book_from_title_author() + if self.isbn: + self.book = self.get_book_from_isbn() + else: + # don't fall back on title/author search is isbn is present. + # you're too likely to mismatch + self.get_book_from_title_author() def get_book_from_isbn(self): """search by isbn""" diff --git a/bookwyrm/models/list.py b/bookwyrm/models/list.py index 2a5c3382a..bbad5ba9b 100644 --- a/bookwyrm/models/list.py +++ b/bookwyrm/models/list.py @@ -93,7 +93,8 @@ class ListItem(CollectionItemMixin, BookWyrmModel): ) class Meta: - # A book may only be placed into a list once, and each order in the list may be used only - # once + """A book may only be placed into a list once, + and each order in the list may be used only once""" + unique_together = (("book", "book_list"), ("order", "book_list")) ordering = ("-created_date",) diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 12f4c51af..edb89d13c 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -99,7 +99,7 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship): status = "follow_request" activity_serializer = activitypub.Follow - def save(self, *args, broadcast=True, **kwargs): + def save(self, *args, broadcast=True, **kwargs): # pylint: disable=arguments-differ """make sure the follow or block relationship doesn't already exist""" # if there's a request for a follow that already exists, accept it # without changing the local database state diff --git a/bookwyrm/models/site.py b/bookwyrm/models/site.py index 2c5a21642..872f6b454 100644 --- a/bookwyrm/models/site.py +++ b/bookwyrm/models/site.py @@ -4,9 +4,12 @@ import datetime from Crypto import Random from django.db import models, IntegrityError +from django.dispatch import receiver from django.utils import timezone +from model_utils import FieldTracker -from bookwyrm.settings import DOMAIN +from bookwyrm.preview_images import generate_site_preview_image_task +from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES from .base_model import BookWyrmModel from .user import User @@ -35,6 +38,9 @@ class SiteSettings(models.Model): logo = models.ImageField(upload_to="logos/", null=True, blank=True) logo_small = models.ImageField(upload_to="logos/", null=True, blank=True) favicon = models.ImageField(upload_to="logos/", null=True, blank=True) + preview_image = models.ImageField( + upload_to="previews/logos/", null=True, blank=True + ) # footer support_link = models.CharField(max_length=255, null=True, blank=True) @@ -42,6 +48,8 @@ class SiteSettings(models.Model): admin_email = models.EmailField(max_length=255, null=True, blank=True) footer_item = models.TextField(null=True, blank=True) + field_tracker = FieldTracker(fields=["name", "instance_tagline", "logo"]) + @classmethod def get(cls): """gets the site settings db entry or defaults""" @@ -119,3 +127,15 @@ class PasswordReset(models.Model): def link(self): """formats the invite link""" return "https://{}/password-reset/{}".format(DOMAIN, self.code) + + +# pylint: disable=unused-argument +@receiver(models.signals.post_save, sender=SiteSettings) +def preview_image(instance, *args, **kwargs): + """Update image preview for the default site image""" + if not ENABLE_PREVIEW_IMAGES: + return + changed_fields = instance.field_tracker.changed() + + if len(changed_fields) > 0: + generate_site_preview_image_task.delay() diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index bd21ec563..3c25f1af8 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -5,11 +5,15 @@ import re from django.apps import apps from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models +from django.dispatch import receiver from django.template.loader import get_template from django.utils import timezone +from model_utils import FieldTracker from model_utils.managers import InheritanceManager from bookwyrm import activitypub +from bookwyrm.preview_images import generate_edition_preview_image_task +from bookwyrm.settings import ENABLE_PREVIEW_IMAGES from .activitypub_mixin import ActivitypubMixin, ActivityMixin from .activitypub_mixin import OrderedCollectionPageMixin from .base_model import BookWyrmModel @@ -304,6 +308,8 @@ class Review(Status): max_digits=3, ) + field_tracker = FieldTracker(fields=["rating"]) + @property def pure_name(self): """clarify review names for mastodon serialization""" @@ -398,3 +404,17 @@ class Boost(ActivityMixin, Status): # This constraint can't work as it would cross tables. # class Meta: # unique_together = ('user', 'boosted_status') + + +# pylint: disable=unused-argument +@receiver(models.signals.post_save) +def preview_image(instance, sender, *args, **kwargs): + """Updates book previews if the rating has changed""" + if not ENABLE_PREVIEW_IMAGES or sender not in (Review, ReviewRating): + return + + changed_fields = instance.field_tracker.changed() + + if len(changed_fields) > 0: + edition = instance.book + generate_edition_preview_image_task.delay(edition.id) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 7d821c5ba..49458a2e0 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -6,15 +6,18 @@ from django.apps import apps from django.contrib.auth.models import AbstractUser, Group from django.contrib.postgres.fields import CICharField from django.core.validators import MinValueValidator +from django.dispatch import receiver from django.db import models from django.utils import timezone +from model_utils import FieldTracker import pytz from bookwyrm import activitypub from bookwyrm.connectors import get_data, ConnectorException from bookwyrm.models.shelf import Shelf from bookwyrm.models.status import Status, Review -from bookwyrm.settings import DOMAIN +from bookwyrm.preview_images import generate_user_preview_image_task +from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES from bookwyrm.signatures import create_key_pair from bookwyrm.tasks import app from bookwyrm.utils import regex @@ -70,6 +73,9 @@ class User(OrderedCollectionPageMixin, AbstractUser): activitypub_field="icon", alt_field="alt_text", ) + preview_image = models.ImageField( + upload_to="previews/avatars/", blank=True, null=True + ) followers = fields.ManyToManyField( "self", link_only=True, @@ -117,6 +123,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): name_field = "username" property_fields = [("following_link", "following")] + field_tracker = FieldTracker(fields=["name", "avatar"]) @property def following_link(self): @@ -232,7 +239,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): def save(self, *args, **kwargs): """populate fields for new local users""" created = not bool(self.id) - if not self.local and not re.match(regex.full_username, self.username): + if not self.local and not re.match(regex.FULL_USERNAME, self.username): # generate a username that uses the domain (webfinger format) actor_parts = urlparse(self.remote_id) self.username = "%s@%s" % (self.username, actor_parts.netloc) @@ -356,7 +363,7 @@ class AnnualGoal(BookWyrmModel): def get_remote_id(self): """put the year in the path""" - return "%s/goal/%d" % (self.user.remote_id, self.year) + return "{:s}/goal/{:d}".format(self.user.remote_id, self.year) @property def books(self): @@ -381,17 +388,16 @@ class AnnualGoal(BookWyrmModel): return {r.book.id: r.rating for r in reviews} @property - def progress_percent(self): - """how close to your goal, in percent form""" - return int(float(self.book_count / self.goal) * 100) - - @property - def book_count(self): + def progress(self): """how many books you've read this year""" - return self.user.readthrough_set.filter( + count = self.user.readthrough_set.filter( finish_date__year__gte=self.year, finish_date__year__lt=self.year + 1, ).count() + return { + "count": count, + "percent": int(float(count / self.goal) * 100), + } @app.task @@ -444,3 +450,15 @@ def get_remote_reviews(outbox): if not activity["type"] == "Review": continue activitypub.Review(**activity).to_model() + + +# pylint: disable=unused-argument +@receiver(models.signals.post_save, sender=User) +def preview_image(instance, *args, **kwargs): + """create preview images when user is updated""" + if not ENABLE_PREVIEW_IMAGES: + return + changed_fields = instance.field_tracker.changed() + + if len(changed_fields) > 0: + generate_user_preview_image_task.delay(instance.id) diff --git a/bookwyrm/preview_images.py b/bookwyrm/preview_images.py new file mode 100644 index 000000000..510625e39 --- /dev/null +++ b/bookwyrm/preview_images.py @@ -0,0 +1,424 @@ +""" Generate social media preview images for twitter/mastodon/etc """ +import math +import os +import textwrap +from io import BytesIO +from uuid import uuid4 + +import colorsys +from colorthief import ColorThief +from PIL import Image, ImageDraw, ImageFont, ImageOps, ImageColor + +from django.core.files.base import ContentFile +from django.core.files.uploadedfile import InMemoryUploadedFile +from django.db.models import Avg + +from bookwyrm import models, settings +from bookwyrm.tasks import app + + +IMG_WIDTH = settings.PREVIEW_IMG_WIDTH +IMG_HEIGHT = settings.PREVIEW_IMG_HEIGHT +BG_COLOR = settings.PREVIEW_BG_COLOR +TEXT_COLOR = settings.PREVIEW_TEXT_COLOR +DEFAULT_COVER_COLOR = settings.PREVIEW_DEFAULT_COVER_COLOR +TRANSPARENT_COLOR = (0, 0, 0, 0) + +margin = math.floor(IMG_HEIGHT / 10) +gutter = math.floor(margin / 2) +inner_img_height = math.floor(IMG_HEIGHT * 0.8) +inner_img_width = math.floor(inner_img_height * 0.7) +font_dir = os.path.join(settings.STATIC_ROOT, "fonts/public_sans") + + +def get_font(font_name, size=28): + """Loads custom font""" + if font_name == "light": + font_path = os.path.join(font_dir, "PublicSans-Light.ttf") + if font_name == "regular": + font_path = os.path.join(font_dir, "PublicSans-Regular.ttf") + elif font_name == "bold": + font_path = os.path.join(font_dir, "PublicSans-Bold.ttf") + + try: + font = ImageFont.truetype(font_path, size) + except OSError: + font = ImageFont.load_default() + + return font + + +def generate_texts_layer(texts, content_width): + """Adds text for images""" + font_text_zero = get_font("bold", size=20) + font_text_one = get_font("bold", size=48) + font_text_two = get_font("bold", size=40) + font_text_three = get_font("regular", size=40) + + text_layer = Image.new("RGBA", (content_width, IMG_HEIGHT), color=TRANSPARENT_COLOR) + text_layer_draw = ImageDraw.Draw(text_layer) + + text_y = 0 + + if "text_zero" in texts and texts["text_zero"]: + # Text one (Book title) + text_zero = textwrap.fill(texts["text_zero"], width=72) + text_layer_draw.multiline_text( + (0, text_y), text_zero, font=font_text_zero, fill=TEXT_COLOR + ) + + try: + text_y = text_y + font_text_zero.getsize_multiline(text_zero)[1] + 16 + except (AttributeError, IndexError): + text_y = text_y + 26 + + if "text_one" in texts and texts["text_one"]: + # Text one (Book title) + text_one = textwrap.fill(texts["text_one"], width=28) + text_layer_draw.multiline_text( + (0, text_y), text_one, font=font_text_one, fill=TEXT_COLOR + ) + + try: + text_y = text_y + font_text_one.getsize_multiline(text_one)[1] + 16 + except (AttributeError, IndexError): + text_y = text_y + 26 + + if "text_two" in texts and texts["text_two"]: + # Text one (Book subtitle) + text_two = textwrap.fill(texts["text_two"], width=36) + text_layer_draw.multiline_text( + (0, text_y), text_two, font=font_text_two, fill=TEXT_COLOR + ) + + try: + text_y = text_y + font_text_one.getsize_multiline(text_two)[1] + 16 + except (AttributeError, IndexError): + text_y = text_y + 26 + + if "text_three" in texts and texts["text_three"]: + # Text three (Book authors) + text_three = textwrap.fill(texts["text_three"], width=36) + text_layer_draw.multiline_text( + (0, text_y), text_three, font=font_text_three, fill=TEXT_COLOR + ) + + text_layer_box = text_layer.getbbox() + return text_layer.crop(text_layer_box) + + +def generate_instance_layer(content_width): + """Places components for instance preview""" + font_instance = get_font("light", size=28) + + site = models.SiteSettings.objects.get() + + if site.logo_small: + logo_img = Image.open(site.logo_small) + else: + try: + static_path = os.path.join(settings.STATIC_ROOT, "images/logo-small.png") + logo_img = Image.open(static_path) + except FileNotFoundError: + logo_img = None + + instance_layer = Image.new("RGBA", (content_width, 62), color=TRANSPARENT_COLOR) + + instance_text_x = 0 + + if logo_img: + logo_img.thumbnail((50, 50), Image.ANTIALIAS) + + instance_layer.paste(logo_img, (0, 0)) + + instance_text_x = instance_text_x + 60 + + instance_layer_draw = ImageDraw.Draw(instance_layer) + instance_layer_draw.text( + (instance_text_x, 10), site.name, font=font_instance, fill=TEXT_COLOR + ) + + line_width = 50 + 10 + font_instance.getsize(site.name)[0] + + line_layer = Image.new( + "RGBA", (line_width, 2), color=(*(ImageColor.getrgb(TEXT_COLOR)), 50) + ) + instance_layer.alpha_composite(line_layer, (0, 60)) + + return instance_layer + + +def generate_rating_layer(rating, content_width): + """Places components for rating preview""" + try: + icon_star_full = Image.open( + os.path.join(settings.STATIC_ROOT, "images/icons/star-full.png") + ) + icon_star_empty = Image.open( + os.path.join(settings.STATIC_ROOT, "images/icons/star-empty.png") + ) + icon_star_half = Image.open( + os.path.join(settings.STATIC_ROOT, "images/icons/star-half.png") + ) + except FileNotFoundError: + return None + + icon_size = 64 + icon_margin = 10 + + rating_layer_base = Image.new( + "RGBA", (content_width, icon_size), color=TRANSPARENT_COLOR + ) + rating_layer_color = Image.new("RGBA", (content_width, icon_size), color=TEXT_COLOR) + rating_layer_mask = Image.new( + "RGBA", (content_width, icon_size), color=TRANSPARENT_COLOR + ) + + position_x = 0 + + for _ in range(math.floor(rating)): + rating_layer_mask.alpha_composite(icon_star_full, (position_x, 0)) + position_x = position_x + icon_size + icon_margin + + if math.floor(rating) != math.ceil(rating): + rating_layer_mask.alpha_composite(icon_star_half, (position_x, 0)) + position_x = position_x + icon_size + icon_margin + + for _ in range(5 - math.ceil(rating)): + rating_layer_mask.alpha_composite(icon_star_empty, (position_x, 0)) + position_x = position_x + icon_size + icon_margin + + rating_layer_mask = rating_layer_mask.getchannel("A") + rating_layer_mask = ImageOps.invert(rating_layer_mask) + + rating_layer_composite = Image.composite( + rating_layer_base, rating_layer_color, rating_layer_mask + ) + + return rating_layer_composite + + +def generate_default_inner_img(): + """Adds cover image""" + font_cover = get_font("light", size=28) + + default_cover = Image.new( + "RGB", (inner_img_width, inner_img_height), color=DEFAULT_COVER_COLOR + ) + default_cover_draw = ImageDraw.Draw(default_cover) + + text = "no image :(" + text_dimensions = font_cover.getsize(text) + text_coords = ( + math.floor((inner_img_width - text_dimensions[0]) / 2), + math.floor((inner_img_height - text_dimensions[1]) / 2), + ) + default_cover_draw.text(text_coords, text, font=font_cover, fill="white") + + return default_cover + + +# pylint: disable=too-many-locals +def generate_preview_image( + texts=None, picture=None, rating=None, show_instance_layer=True +): + """Puts everything together""" + texts = texts or {} + # Cover + try: + inner_img_layer = Image.open(picture) + inner_img_layer.thumbnail((inner_img_width, inner_img_height), Image.ANTIALIAS) + color_thief = ColorThief(picture) + dominant_color = color_thief.get_color(quality=1) + except: # pylint: disable=bare-except + inner_img_layer = generate_default_inner_img() + dominant_color = ImageColor.getrgb(DEFAULT_COVER_COLOR) + + # Color + if BG_COLOR in ["use_dominant_color_light", "use_dominant_color_dark"]: + image_bg_color = "rgb(%s, %s, %s)" % dominant_color + + # Adjust color + image_bg_color_rgb = [x / 255.0 for x in ImageColor.getrgb(image_bg_color)] + image_bg_color_hls = colorsys.rgb_to_hls(*image_bg_color_rgb) + + if BG_COLOR == "use_dominant_color_light": + lightness = max(0.9, image_bg_color_hls[1]) + else: + lightness = min(0.15, image_bg_color_hls[1]) + + image_bg_color_hls = ( + image_bg_color_hls[0], + lightness, + image_bg_color_hls[2], + ) + image_bg_color = tuple( + math.ceil(x * 255) for x in colorsys.hls_to_rgb(*image_bg_color_hls) + ) + else: + image_bg_color = BG_COLOR + + # Background (using the color) + img = Image.new("RGBA", (IMG_WIDTH, IMG_HEIGHT), color=image_bg_color) + + # Contents + inner_img_x = margin + inner_img_width - inner_img_layer.width + inner_img_y = math.floor((IMG_HEIGHT - inner_img_layer.height) / 2) + content_x = margin + inner_img_width + gutter + content_width = IMG_WIDTH - content_x - margin + + contents_layer = Image.new( + "RGBA", (content_width, IMG_HEIGHT), color=TRANSPARENT_COLOR + ) + contents_composite_y = 0 + + if show_instance_layer: + instance_layer = generate_instance_layer(content_width) + contents_layer.alpha_composite(instance_layer, (0, contents_composite_y)) + contents_composite_y = contents_composite_y + instance_layer.height + gutter + + texts_layer = generate_texts_layer(texts, content_width) + contents_layer.alpha_composite(texts_layer, (0, contents_composite_y)) + contents_composite_y = contents_composite_y + texts_layer.height + gutter + + if rating: + # Add some more margin + contents_composite_y = contents_composite_y + gutter + rating_layer = generate_rating_layer(rating, content_width) + + if rating_layer: + contents_layer.alpha_composite(rating_layer, (0, contents_composite_y)) + contents_composite_y = contents_composite_y + rating_layer.height + gutter + + contents_layer_box = contents_layer.getbbox() + contents_layer_height = contents_layer_box[3] - contents_layer_box[1] + + contents_y = math.floor((IMG_HEIGHT - contents_layer_height) / 2) + + if show_instance_layer: + # Remove Instance Layer from centering calculations + contents_y = contents_y - math.floor((instance_layer.height + gutter) / 2) + + contents_y = max(contents_y, margin) + + # Composite layers + img.paste( + inner_img_layer, (inner_img_x, inner_img_y), inner_img_layer.convert("RGBA") + ) + img.alpha_composite(contents_layer, (content_x, contents_y)) + + return img.convert("RGB") + + +def save_and_cleanup(image, instance=None): + """Save and close the file""" + if not isinstance(instance, (models.Book, models.User, models.SiteSettings)): + return False + file_name = "%s-%s.jpg" % (str(instance.id), str(uuid4())) + image_buffer = BytesIO() + + try: + try: + old_path = instance.preview_image.path + except ValueError: + old_path = "" + + # Save + image.save(image_buffer, format="jpeg", quality=75) + + instance.preview_image = InMemoryUploadedFile( + ContentFile(image_buffer.getvalue()), + "preview_image", + file_name, + "image/jpg", + image_buffer.tell(), + None, + ) + + save_without_broadcast = isinstance(instance, (models.Book, models.User)) + if save_without_broadcast: + instance.save(broadcast=False) + else: + instance.save() + + # Clean up old file after saving + if os.path.exists(old_path): + os.remove(old_path) + + finally: + image_buffer.close() + return True + + +# pylint: disable=invalid-name +@app.task +def generate_site_preview_image_task(): + """generate preview_image for the website""" + if not settings.ENABLE_PREVIEW_IMAGES: + return + + site = models.SiteSettings.objects.get() + + if site.logo: + logo = site.logo + else: + logo = os.path.join(settings.STATIC_ROOT, "images/logo.png") + + texts = { + "text_zero": settings.DOMAIN, + "text_one": site.name, + "text_three": site.instance_tagline, + } + + image = generate_preview_image(texts=texts, picture=logo, show_instance_layer=False) + + save_and_cleanup(image, instance=site) + + +# pylint: disable=invalid-name +@app.task +def generate_edition_preview_image_task(book_id): + """generate preview_image for a book""" + if not settings.ENABLE_PREVIEW_IMAGES: + return + + book = models.Book.objects.select_subclasses().get(id=book_id) + + rating = models.Review.objects.filter( + privacy="public", + deleted=False, + book__in=[book_id], + ).aggregate(Avg("rating"))["rating__avg"] + + texts = { + "text_one": book.title, + "text_two": book.subtitle, + "text_three": book.author_text, + } + + image = generate_preview_image(texts=texts, picture=book.cover, rating=rating) + + save_and_cleanup(image, instance=book) + + +@app.task +def generate_user_preview_image_task(user_id): + """generate preview_image for a book""" + if not settings.ENABLE_PREVIEW_IMAGES: + return + + user = models.User.objects.get(id=user_id) + + texts = { + "text_one": user.display_name, + "text_three": "@{}@{}".format(user.localname, settings.DOMAIN), + } + + if user.avatar: + avatar = user.avatar + else: + avatar = os.path.join(settings.STATIC_ROOT, "images/default_avi.jpg") + + image = generate_preview_image(texts=texts, picture=avatar) + + save_and_cleanup(image, instance=user) diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index d694e33fd..b928f97ef 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -14,13 +14,18 @@ PAGE_LENGTH = env("PAGE_LENGTH", 15) DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English") # celery -CELERY_BROKER = env("CELERY_BROKER") -CELERY_RESULT_BACKEND = env("CELERY_RESULT_BACKEND") +CELERY_BROKER = "redis://:{}@redis_broker:{}/0".format( + requests.utils.quote(env("REDIS_BROKER_PASSWORD", "")), env("REDIS_BROKER_PORT") +) +CELERY_RESULT_BACKEND = "redis://:{}@redis_broker:{}/0".format( + requests.utils.quote(env("REDIS_BROKER_PASSWORD", "")), env("REDIS_BROKER_PORT") +) CELERY_ACCEPT_CONTENT = ["application/json"] CELERY_TASK_SERIALIZER = "json" CELERY_RESULT_SERIALIZER = "json" # email +EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend") EMAIL_HOST = env("EMAIL_HOST") EMAIL_PORT = env("EMAIL_PORT", 587) EMAIL_HOST_USER = env("EMAIL_HOST_USER") @@ -37,6 +42,14 @@ LOCALE_PATHS = [ DEFAULT_AUTO_FIELD = "django.db.models.AutoField" +# Preview image +ENABLE_PREVIEW_IMAGES = env.bool("ENABLE_PREVIEW_IMAGES", False) +PREVIEW_BG_COLOR = env.str("PREVIEW_BG_COLOR", "use_dominant_color_light") +PREVIEW_TEXT_COLOR = env.str("PREVIEW_TEXT_COLOR", "#363636") +PREVIEW_IMG_WIDTH = env.int("PREVIEW_IMG_WIDTH", 1200) +PREVIEW_IMG_HEIGHT = env.int("PREVIEW_IMG_HEIGHT", 630) +PREVIEW_DEFAULT_COVER_COLOR = env.str("PREVIEW_DEFAULT_COVER_COLOR", "#002549") + # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ @@ -47,7 +60,6 @@ SECRET_KEY = env("SECRET_KEY") DEBUG = env.bool("DEBUG", True) ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", ["*"]) -OL_URL = env("OL_URL") # Application definition @@ -109,10 +121,8 @@ STREAMS = ["home", "local", "federated"] # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases -BOOKWYRM_DATABASE_BACKEND = env("BOOKWYRM_DATABASE_BACKEND", "postgres") - -BOOKWYRM_DBS = { - "postgres": { +DATABASES = { + "default": { "ENGINE": "django.db.backends.postgresql_psycopg2", "NAME": env("POSTGRES_DB", "fedireads"), "USER": env("POSTGRES_USER", "fedireads"), @@ -122,8 +132,6 @@ BOOKWYRM_DBS = { }, } -DATABASES = {"default": BOOKWYRM_DBS[BOOKWYRM_DATABASE_BACKEND]} - LOGIN_URL = "/login/" AUTH_USER_MODEL = "bookwyrm.User" @@ -131,6 +139,7 @@ AUTH_USER_MODEL = "bookwyrm.User" # Password validation # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators +# pylint: disable=line-too-long AUTH_PASSWORD_VALIDATORS = [ { "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", @@ -174,8 +183,10 @@ USE_TZ = True PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) STATIC_URL = "/static/" +STATIC_PATH = "%s/%s" % (DOMAIN, env("STATIC_ROOT", "static")) STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static")) MEDIA_URL = "/images/" +MEDIA_PATH = "%s/%s" % (DOMAIN, env("MEDIA_ROOT", "images")) MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images")) USER_AGENT = "%s (BookWyrm/%s; +https://%s/)" % ( diff --git a/bookwyrm/signatures.py b/bookwyrm/signatures.py index 5488cf9be..c8c900283 100644 --- a/bookwyrm/signatures.py +++ b/bookwyrm/signatures.py @@ -73,6 +73,7 @@ class Signature: self.headers = headers self.signature = signature + # pylint: disable=invalid-name @classmethod def parse(cls, request): """extract and parse a signature from an http request""" diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css index fcb32e212..3db25d1fe 100644 --- a/bookwyrm/static/css/bookwyrm.css +++ b/bookwyrm/static/css/bookwyrm.css @@ -43,6 +43,19 @@ body { white-space: nowrap !important; width: 0.01em !important; } + + .m-0-mobile { + margin: 0 !important; + } + + .card-footer.is-stacked-mobile { + flex-direction: column; + } + + .card-footer.is-stacked-mobile .card-footer-item:not(:last-child) { + border-bottom: 1px solid #ededed; + border-right: 0; + } } .button.is-transparent { @@ -331,6 +344,49 @@ body { } } +/* Book list + ******************************************************************************/ + +ol.ordered-list { + list-style: none; + counter-reset: list-counter; +} + +ol.ordered-list li { + counter-increment: list-counter; +} + +ol.ordered-list li::before { + content: counter(list-counter); + position: absolute; + left: -20px; + width: 20px; + height: 24px; + background-color: #fff; + border: 1px solid #dbdbdb; + border-right: 0; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + display: flex; + justify-content: center; + align-items: center; + color: #888; + font-size: 0.8em; + font-weight: bold; +} + +@media only screen and (max-width: 768px) { + ol.ordered-list li::before { + left: 0; + z-index: 1; + border: 0; + border-right: 1px solid #dbdbdb; + border-bottom: 1px solid #dbdbdb; + border-radius: 0; + border-bottom-right-radius: 2px; + } +} + /* Dimensions * @todo These could be in rem. ******************************************************************************/ diff --git a/bookwyrm/static/fonts/public_sans/OFL.txt b/bookwyrm/static/fonts/public_sans/OFL.txt new file mode 100644 index 000000000..ba4ea0b96 --- /dev/null +++ b/bookwyrm/static/fonts/public_sans/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2015, Pablo Impallari, Rodrigo Fuenzalida +(Modified by Dan O. Williams and USWDS) (https://github.com/uswds/public-sans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/bookwyrm/static/fonts/public_sans/PublicSans-Bold.ttf b/bookwyrm/static/fonts/public_sans/PublicSans-Bold.ttf new file mode 100644 index 000000000..3eb5ac24e Binary files /dev/null and b/bookwyrm/static/fonts/public_sans/PublicSans-Bold.ttf differ diff --git a/bookwyrm/static/fonts/public_sans/PublicSans-Light.ttf b/bookwyrm/static/fonts/public_sans/PublicSans-Light.ttf new file mode 100644 index 000000000..13fd7edbc Binary files /dev/null and b/bookwyrm/static/fonts/public_sans/PublicSans-Light.ttf differ diff --git a/bookwyrm/static/fonts/public_sans/PublicSans-Regular.ttf b/bookwyrm/static/fonts/public_sans/PublicSans-Regular.ttf new file mode 100644 index 000000000..25c1646a4 Binary files /dev/null and b/bookwyrm/static/fonts/public_sans/PublicSans-Regular.ttf differ diff --git a/bookwyrm/static/images/icons/star-empty.png b/bookwyrm/static/images/icons/star-empty.png new file mode 100755 index 000000000..896417ef6 Binary files /dev/null and b/bookwyrm/static/images/icons/star-empty.png differ diff --git a/bookwyrm/static/images/icons/star-full.png b/bookwyrm/static/images/icons/star-full.png new file mode 100755 index 000000000..6d78caf0c Binary files /dev/null and b/bookwyrm/static/images/icons/star-full.png differ diff --git a/bookwyrm/static/images/icons/star-half.png b/bookwyrm/static/images/icons/star-half.png new file mode 100755 index 000000000..75e4eadc6 Binary files /dev/null and b/bookwyrm/static/images/icons/star-half.png differ diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 3659a20e4..e43ed134b 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -3,6 +3,7 @@ let BookWyrm = new class { constructor() { + this.MAX_FILE_SIZE_BYTES = 10 * 1000000; this.initOnDOMLoaded(); this.initReccuringTasks(); this.initEventListeners(); @@ -32,15 +33,26 @@ let BookWyrm = new class { 'click', this.back) ); + + document.querySelectorAll('input[type="file"]') + .forEach(node => node.addEventListener( + 'change', + this.disableIfTooLarge.bind(this) + )); } /** * Execute code once the DOM is loaded. */ initOnDOMLoaded() { + const bookwyrm = this; + window.addEventListener('DOMContentLoaded', function() { document.querySelectorAll('.tab-group') .forEach(tabs => new TabGroup(tabs)); + document.querySelectorAll('input[type="file"]').forEach( + bookwyrm.disableIfTooLarge.bind(bookwyrm) + ); }); } @@ -126,6 +138,7 @@ let BookWyrm = new class { * @return {undefined} */ toggleAction(event) { + event.preventDefault(); let trigger = event.currentTarget; let pressed = trigger.getAttribute('aria-pressed') === 'false'; let targetId = trigger.dataset.controls; @@ -170,6 +183,8 @@ let BookWyrm = new class { if (focus) { this.toggleFocus(focus); } + + return false; } /** @@ -284,4 +299,27 @@ let BookWyrm = new class { node.classList.remove(classname); } } -} + + disableIfTooLarge(eventOrElement) { + const { addRemoveClass, MAX_FILE_SIZE_BYTES } = this; + const element = eventOrElement.currentTarget || eventOrElement; + + const submits = element.form.querySelectorAll('[type="submit"]'); + const warns = element.parentElement.querySelectorAll('.file-too-big'); + const isTooBig = element.files && + element.files[0] && + element.files[0].size > MAX_FILE_SIZE_BYTES; + + if (isTooBig) { + submits.forEach(submitter => submitter.disabled = true); + warns.forEach( + sib => addRemoveClass(sib, 'is-hidden', false) + ); + } else { + submits.forEach(submitter => submitter.disabled = false); + warns.forEach( + sib => addRemoveClass(sib, 'is-hidden', true) + ); + } + } +}(); diff --git a/bookwyrm/static/js/localstorage.js b/bookwyrm/static/js/localstorage.js index 059557799..b485ed7ea 100644 --- a/bookwyrm/static/js/localstorage.js +++ b/bookwyrm/static/js/localstorage.js @@ -17,7 +17,7 @@ let LocalStorageTools = new class { * @return {undefined} */ updateDisplay(event) { - // used in set reading goal + // Used in set reading goal let key = event.target.dataset.id; let value = event.target.dataset.value; @@ -34,10 +34,10 @@ let LocalStorageTools = new class { * @return {undefined} */ setDisplay(node) { - // used in set reading goal + // Used in set reading goal let key = node.dataset.hide; let value = window.localStorage.getItem(key); BookWyrm.addRemoveClass(node, 'is-hidden', value); } -} +}(); diff --git a/bookwyrm/templates/author/author.html b/bookwyrm/templates/author/author.html index 67f8792c9..0bc427758 100644 --- a/bookwyrm/templates/author/author.html +++ b/bookwyrm/templates/author/author.html @@ -15,49 +15,83 @@
- {% trans "Edit Author" %} + {% trans "Edit Author" %}
{% endif %} -
- {% if author.aliases or author.born or author.died or author.wikipedia_link %} -
-
+
+ + + {% if author.aliases or author.born or author.died or author.wikipedia_link or author.openlibrary_key or author.inventaire_id %} +
+
{% if author.aliases %} -
-
{% trans "Aliases:" %}
-
{{ author.aliases|join:', ' }}
+
+
{% trans "Aliases:" %}
+ {% for alias in author.aliases %} +
+ {{alias}}{% if not forloop.last %}, {% endif %} +
+ {% endfor %}
{% endif %} + {% if author.born %} -
-
{% trans "Born:" %}
-
{{ author.born|naturalday }}
+
+
{% trans "Born:" %}
+
{{ author.born|naturalday }}
{% endif %} - {% if author.aliases %} -
-
{% trans "Died:" %}
-
{{ author.died|naturalday }}
+ + {% if author.died %} +
+
{% trans "Died:" %}
+
{{ author.died|naturalday }}
{% endif %}
{% if author.wikipedia_link %} -

{% trans "Wikipedia" %}

- {% endif %} - {% if author.openlibrary_key %} -

- {% trans "View on OpenLibrary" %} +

+ + {% trans "Wikipedia" %} +

{% endif %} + + {% if author.openlibrary_key %} +

+ + {% trans "View on OpenLibrary" %} + +

+ {% endif %} + {% if author.inventaire_id %} -

- {% trans "View on Inventaire" %} +

+ + {% trans "View on Inventaire" %} + +

+ {% endif %} + + {% if author.librarything_key %} +

+ + {% trans "View on LibraryThing" %} + +

+ {% endif %} + + {% if author.goodreads_key %} +

+ + {% trans "View on Goodreads" %} +

{% endif %}
diff --git a/bookwyrm/templates/author/edit_author.html b/bookwyrm/templates/author/edit_author.html index 010d36efc..103341bfd 100644 --- a/bookwyrm/templates/author/edit_author.html +++ b/bookwyrm/templates/author/edit_author.html @@ -29,67 +29,85 @@

{% trans "Metadata" %}

-

{{ form.name }}

- {% for error in form.name.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.name }} + {% for error in form.name.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

+

{{ form.aliases }} {% trans "Separate multiple values with commas." %} -

- {% for error in form.aliases.errors %} -

{{ error | escape }}

- {% endfor %} + {% for error in form.aliases.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

{{ form.bio }}

- {% for error in form.bio.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.bio }} + {% for error in form.bio.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

{{ form.wikipedia_link }}

+

{{ form.wikipedia_link }}

{% for error in form.wikipedia_link.errors %}

{{ error | escape }}

{% endfor %} -

+

-

- {% for error in form.born.errors %} -

{{ error | escape }}

- {% endfor %} + {% for error in form.born.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

+

-

- {% for error in form.died.errors %} -

{{ error | escape }}

- {% endfor %} + {% for error in form.died.errors %} +

{{ error | escape }}

+ {% endfor %} +

{% trans "Author Identifiers" %}

-

{{ form.openlibrary_key }}

- {% for error in form.openlibrary_key.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.openlibrary_key }} + {% for error in form.openlibrary_key.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

{{ form.inventaire_id }}

- {% for error in form.inventaire_id.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.inventaire_id }} + {% for error in form.inventaire_id.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

{{ form.librarything_key }}

- {% for error in form.librarything_key.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.librarything_key }} + {% for error in form.librarything_key.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

{{ form.goodreads_key }}

- {% for error in form.goodreads_key.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.goodreads_key }} + {% for error in form.goodreads_key.errors %} +

{{ error | escape }}

+ {% endfor %} +
diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index fb0a45101..26bd8322c 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -1,35 +1,47 @@ {% extends 'layout.html' %} -{% load i18n %}{% load bookwyrm_tags %}{% load humanize %}{% load utilities %} +{% load i18n %}{% load bookwyrm_tags %}{% load humanize %}{% load utilities %}{% load layout %} -{% block title %}{{ book.title }}{% endblock %} +{% block title %}{{ book|book_title }}{% endblock %} + +{% block opengraph_images %} + {% include 'snippets/opengraph_images.html' with image=book.preview_image %} +{% endblock %} {% block content %} {% with user_authenticated=request.user.is_authenticated can_edit_book=perms.bookwyrm.edit_book %}
-

- - {{ book.title }}{% if book.subtitle %}: - {{ book.subtitle }} - {% endif %} - - - {% if book.series %} - - - - - ({{ book.series }} - {% if book.series_number %} #{{ book.series_number }}{% endif %}) - -
- {% endif %} +

+ {{ book.title }}

+ + {% if book.subtitle or book.series %} +

+ {% if book.subtitle %} + + + + {{ book.subtitle }} + + {% endif %} + + {% if book.series %} + + + + ({{ book.series }}{% if book.series_number %} #{{ book.series_number }}{% endif %}) + {% endif %} +

+ {% endif %} + {% if book.authors %} -

- {% trans "by" %} {% include 'snippets/authors.html' with book=book %} -

+
+ {% trans "by" %} {% include 'snippets/authors.html' with book=book %} +
{% endif %}
@@ -37,7 +49,7 @@ {% endif %} @@ -85,7 +97,7 @@
-

+

{% with full=book|book_description itemprop='abstract' %} {% include 'snippets/trimmed_text.html' %} @@ -137,7 +149,7 @@ {# user's relationship to the book #}
- {% for shelf in user_shelves %} + {% for shelf in user_shelfbooks %}

{% blocktrans with path=shelf.shelf.local_path shelf_name=shelf.shelf.name %}This edition is on your {{ shelf_name }} shelf.{% endblocktrans %} {% include 'snippets/shelf_selector.html' with current=shelf.shelf %} @@ -181,7 +193,7 @@

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

{% endif %} {% for readthrough in readthroughs %} - {% include 'snippets/readthrough.html' with readthrough=readthrough %} + {% include 'book/readthrough.html' with readthrough=readthrough %} {% endfor %} diff --git a/bookwyrm/templates/book/edit_book.html b/bookwyrm/templates/book/edit_book.html index 8dae1d044..32018a251 100644 --- a/bookwyrm/templates/book/edit_book.html +++ b/bookwyrm/templates/book/edit_book.html @@ -14,11 +14,25 @@ {% endif %} {% if book %} -
-

{% trans "Added:" %} {{ book.created_date | naturaltime }}

-

{% trans "Updated:" %} {{ book.updated_date | naturaltime }}

-

{% trans "Last edited by:" %} {{ book.last_edited_by.display_name }}

-
+
+
+
{% trans "Added:" %}
+
{{ book.created_date | naturaltime }}
+
+ +
+
{% trans "Updated:" %}
+
{{ book.updated_date | naturaltime }}
+
+ + {% if book.last_edited_by %} +
+
{% trans "Last edited by:" %}
+
{{ book.last_edited_by.display_name }}
+
+ {% endif %} + +
{% endif %} @@ -38,21 +52,28 @@ {% if confirm_mode %}

{% trans "Confirm Book Info" %}

-
+
{% if author_matches %}
{% for author in author_matches %} -
- {% blocktrans with name=author.name %}Is "{{ name }}" an existing author?{% endblocktrans %} +
+ + {% blocktrans with name=author.name %}Is "{{ name }}" an existing author?{% endblocktrans %} + {% with forloop.counter0 as counter %} {% for match in author.matches %} - +

{% blocktrans with book_title=match.book_set.first.title %}Author of {{ book_title }}{% endblocktrans %}

{% endfor %} - + {% endwith %}
{% endfor %} @@ -64,11 +85,17 @@ {% if not book %}
- {% trans "Is this an edition of an existing work?" %} + + {% trans "Is this an edition of an existing work?" %} + {% for match in book_matches %} - + {% endfor %} - +
{% endif %} @@ -89,76 +116,79 @@

{% trans "Metadata" %}

-

+

-

- {% for error in form.title.errors %} -

{{ error | escape }}

- {% endfor %} + {% for error in form.title.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

+

-

- {% for error in form.subtitle.errors %} -

{{ error | escape }}

- {% endfor %} + {% for error in form.subtitle.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

{{ form.description }}

- {% for error in form.description.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.description }} + {% for error in form.description.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

+

-

- {% for error in form.series.errors %} -

{{ error | escape }}

- {% endfor %} + {% for error in form.series.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

+

{{ form.series_number }} -

- {% for error in form.series_number.errors %} -

{{ error | escape }}

- {% endfor %} + {% for error in form.series_number.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

+

{{ form.languages }} {% trans "Separate multiple values with commas." %} -

- {% for error in form.languages.errors %} -

{{ error | escape }}

- {% endfor %} + {% for error in form.languages.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

+

{{ form.publishers }} {% trans "Separate multiple values with commas." %} -

- {% for error in form.publishers.errors %} -

{{ error | escape }}

- {% endfor %} + {% for error in form.publishers.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

+

-

- {% for error in form.first_published_date.errors %} -

{{ error | escape }}

- {% endfor %} + {% for error in form.first_published_date.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

+

-

- {% for error in form.published_date.errors %} -

{{ error | escape }}

- {% endfor %} + {% for error in form.published_date.errors %} +

{{ error | escape }}

+ {% endfor %} +
@@ -166,16 +196,23 @@ {% if book.authors.exists %}
{% for author in book.authors.all %} - +
+ +

+ {% blocktrans with name=author.name %}Author page for {{ name }}{% endblocktrans %} +

+
{% endfor %}
{% endif %} - - - {% trans "Separate multiple values with commas." %} +
+ + + {% trans "Separate multiple values with commas." %} +
@@ -188,17 +225,17 @@
-

+

{{ form.cover }} -

+
{% if book %} -

+

-

+
{% endif %} {% for error in form.cover.errors %}

{{ error | escape }}

@@ -209,51 +246,72 @@

{% trans "Physical Properties" %}

-

{{ form.physical_format }}

- {% for error in form.physical_format.errors %} -

{{ error | escape }}

- {% endfor %} - {% for error in form.physical_format.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.physical_format }} + {% for error in form.physical_format.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

{{ form.pages }}

- {% for error in form.pages.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.pages }} + {% for error in form.pages.errors %} +

{{ error | escape }}

+ {% endfor %} +

{% trans "Book Identifiers" %}

-

{{ form.isbn_13 }}

- {% for error in form.isbn_13.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.isbn_13 }} + {% for error in form.isbn_13.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

{{ form.isbn_10 }}

- {% for error in form.isbn_10.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.isbn_10 }} + {% for error in form.isbn_10.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

{{ form.openlibrary_key }}

- {% for error in form.openlibrary_key.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.openlibrary_key }} + {% for error in form.openlibrary_key.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

{{ form.inventaire_id }}

- {% for error in form.inventaire_id.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.inventaire_id }} + {% for error in form.inventaire_id.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

{{ form.oclc_number }}

- {% for error in form.oclc_number.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.oclc_number }} + {% for error in form.oclc_number.errors %} +

{{ error | escape }}

+ {% endfor %} +
-

{{ form.asin }}

- {% for error in form.ASIN.errors %} -

{{ error | escape }}

- {% endfor %} +
+ + {{ form.asin }} + {% for error in form.ASIN.errors %} +

{{ error | escape }}

+ {% endfor %} +
@@ -261,7 +319,7 @@ {% if not confirm_mode %} {% endif %} diff --git a/bookwyrm/templates/book/editions.html b/bookwyrm/templates/book/editions.html index 0d5c10447..e2a0bdda5 100644 --- a/bookwyrm/templates/book/editions.html +++ b/bookwyrm/templates/book/editions.html @@ -40,7 +40,7 @@
- {% include 'snippets/shelve_button/shelve_button.html' with book=book switch_mode=True %} + {% include 'snippets/shelve_button/shelve_button.html' with book=book switch_mode=True right=True %}
{% endfor %} diff --git a/bookwyrm/templates/snippets/readthrough.html b/bookwyrm/templates/book/readthrough.html similarity index 97% rename from bookwyrm/templates/snippets/readthrough.html rename to bookwyrm/templates/book/readthrough.html index d5e79b864..751407461 100644 --- a/bookwyrm/templates/snippets/readthrough.html +++ b/bookwyrm/templates/book/readthrough.html @@ -1,8 +1,8 @@ {% load i18n %} {% load humanize %} {% load tz %} -
-
+
+
{% trans "Progress Updates:" %} diff --git a/bookwyrm/templates/components/modal.html b/bookwyrm/templates/components/modal.html index 0a05f9512..b29ff8d91 100644 --- a/bookwyrm/templates/components/modal.html +++ b/bookwyrm/templates/components/modal.html @@ -1,7 +1,7 @@ {% load i18n %} diff --git a/bookwyrm/templates/get_started/book_preview.html b/bookwyrm/templates/get_started/book_preview.html index 578fef708..d8941ad50 100644 --- a/bookwyrm/templates/get_started/book_preview.html +++ b/bookwyrm/templates/get_started/book_preview.html @@ -5,7 +5,7 @@
diff --git a/bookwyrm/templates/import.html b/bookwyrm/templates/import.html index 2b7d69e10..d2e407486 100644 --- a/bookwyrm/templates/import.html +++ b/bookwyrm/templates/import.html @@ -41,8 +41,8 @@
-
diff --git a/bookwyrm/templates/import_status.html b/bookwyrm/templates/import_status.html index b1145611d..11ff47f19 100644 --- a/bookwyrm/templates/import_status.html +++ b/bookwyrm/templates/import_status.html @@ -7,14 +7,19 @@ {% block content %}{% spaceless %}

{% trans "Import Status" %}

+ {% trans "Back to imports" %} -

- {% trans "Import started:" %} {{ job.created_date | naturaltime }} -

- {% if job.complete %} -

- {% trans "Import completed:" %} {{ task.date_done | naturaltime }} -

+
+
+
{% trans "Import started:" %}
+
{{ job.created_date | naturaltime }}
+
+ {% if job.complete %} +
+
{% trans "Import completed:" %}
+
{{ task.date_done | naturaltime }}
+
+
{% elif task.failed %}
{% trans "TASK FAILED" %}
{% endif %} @@ -22,8 +27,9 @@
{% if not job.complete %} - {% trans "Import still in progress." %}

+ {% trans "Import still in progress." %} +
{% trans "(Hit reload to update!)" %}

{% endif %} @@ -49,16 +55,13 @@
    {% for item in failed_items %} -
  • - - -

    +

  • + +
  • {% endfor %}
@@ -104,7 +107,11 @@ {% endif %}
+ {% if job.complete %}

{% trans "Successfully imported" %}

+ {% else %} +

{% trans "Import Progress" %}

+ {% endif %} {% url 'settings-federation' as url %} diff --git a/bookwyrm/templates/user_admin/user_moderation_actions.html b/bookwyrm/templates/user_admin/user_moderation_actions.html index 816e787a2..e4460a687 100644 --- a/bookwyrm/templates/user_admin/user_moderation_actions.html +++ b/bookwyrm/templates/user_admin/user_moderation_actions.html @@ -1,6 +1,12 @@ {% load i18n %}
+ {% if not user.is_active and user.deactivation_reason == "self_deletion" %} +
+ {% trans "Permanently deleted" %} +
+ {% else %}

{% trans "Actions" %}

+

{% trans "Send direct message" %} @@ -14,6 +20,7 @@ {% endif %}

+ {% if user.local %}
@@ -39,4 +46,6 @@
{% endif %} + + {% endif %}
diff --git a/bookwyrm/templates/widgets/clearable_file_input_with_warning.html b/bookwyrm/templates/widgets/clearable_file_input_with_warning.html new file mode 100644 index 000000000..e4906cb9a --- /dev/null +++ b/bookwyrm/templates/widgets/clearable_file_input_with_warning.html @@ -0,0 +1,24 @@ +{% load i18n %} +{% load utilities %} + +{% if widget.is_initial %} +

+ {{ widget.initial_text }}: + {{ widget.value|truncatepath:10 }} +

+{% if not widget.required %} +

+ {% endif %} +

+

+{{ widget.input_text }}: +{% else %} +

+{% endif %} + + +

+ diff --git a/bookwyrm/templatetags/bookwyrm_tags.py b/bookwyrm/templatetags/bookwyrm_tags.py index 70228799f..5cba5455f 100644 --- a/bookwyrm/templatetags/bookwyrm_tags.py +++ b/bookwyrm/templatetags/bookwyrm_tags.py @@ -3,8 +3,6 @@ from django import template from django.db.models import Avg from bookwyrm import models, views -from bookwyrm.views.status import to_markdown -from bookwyrm.templatetags.utilities import get_user_identifier register = template.Library() @@ -27,6 +25,7 @@ def get_user_rating(book, user): user=user, book=book, rating__isnull=False, + deleted=False, ) .order_by("-published_date") .first() @@ -71,9 +70,14 @@ def related_status(notification): @register.simple_tag(takes_context=True) def active_shelf(context, book): """check what shelf a user has a book on, if any""" - shelf = models.ShelfBook.objects.filter( - shelf__user=context["request"].user, book__in=book.parent_work.editions.all() - ).first() + shelf = ( + models.ShelfBook.objects.filter( + shelf__user=context["request"].user, + book__in=book.parent_work.editions.all(), + ) + .select_related("book", "shelf") + .first() + ) return shelf if shelf else {"book": book} diff --git a/bookwyrm/templatetags/interaction.py b/bookwyrm/templatetags/interaction.py index 6e8d0549c..bbe74600c 100644 --- a/bookwyrm/templatetags/interaction.py +++ b/bookwyrm/templatetags/interaction.py @@ -9,14 +9,10 @@ register = template.Library() @register.filter(name="liked") def get_user_liked(user, status): """did the given user fav a status?""" - try: - models.Favorite.objects.get(user=user, status=status) - return True - except models.Favorite.DoesNotExist: - return False + return models.Favorite.objects.filter(user=user, status=status).exists() @register.filter(name="boosted") def get_user_boosted(user, status): """did the given user fav a status?""" - return user.id in status.boosters.all().values_list("user", flat=True) + return status.boosters.filter(user=user).exists() diff --git a/bookwyrm/templatetags/layout.py b/bookwyrm/templatetags/layout.py index e0f1d8ba6..f42f3bda1 100644 --- a/bookwyrm/templatetags/layout.py +++ b/bookwyrm/templatetags/layout.py @@ -1,7 +1,6 @@ """ template filters used for creating the layout""" from django import template, utils - register = template.Library() diff --git a/bookwyrm/templatetags/status_display.py b/bookwyrm/templatetags/status_display.py index 4d9984c86..69c0c2a50 100644 --- a/bookwyrm/templatetags/status_display.py +++ b/bookwyrm/templatetags/status_display.py @@ -42,7 +42,12 @@ def get_parent(status): @register.filter(name="boosted_status") def get_boosted(boost): """load a boosted status. have to do this or it won't get foreign keys""" - return models.Status.objects.select_subclasses().get(id=boost.boosted_status.id) + return ( + models.Status.objects.select_subclasses() + .select_related("user", "reply_parent") + .prefetch_related("mention_books", "mention_users") + .get(id=boost.boosted_status.id) + ) @register.filter(name="published_date") diff --git a/bookwyrm/templatetags/utilities.py b/bookwyrm/templatetags/utilities.py index 68befa54a..23b6685d6 100644 --- a/bookwyrm/templatetags/utilities.py +++ b/bookwyrm/templatetags/utilities.py @@ -1,6 +1,8 @@ """ template filters for really common utilities """ +import os from uuid import uuid4 from django import template +from django.utils.translation import gettext_lazy as _ register = template.Library() @@ -19,13 +21,16 @@ def get_user_identifier(user): @register.filter(name="book_title") -def get_title(book): +def get_title(book, too_short=5): """display the subtitle if the title is short""" if not book: return "" title = book.title - if len(title) < 6 and book.subtitle: - title = "{:s}: {:s}".format(title, book.subtitle) + if len(title) <= too_short and book.subtitle: + title = _("%(title)s: %(subtitle)s") % { + "title": title, + "subtitle": book.subtitle, + } return title @@ -33,3 +38,15 @@ def get_title(book): def comparison_bool(str1, str2): """idk why I need to write a tag for this, it reutrns a bool""" return str1 == str2 + + +@register.filter(is_safe=True) +def truncatepath(value, arg): + """Truncate a path by removing all directories except the first and truncating .""" + path = os.path.normpath(value.name) + path_list = path.split(os.sep) + try: + length = int(arg) + except ValueError: # invalid literal for int() + return path_list[-1] # Fail silently. + return "%s/…%s" % (path_list[0], path_list[-1][-length:]) diff --git a/bookwyrm/tests/activitypub/test_author.py b/bookwyrm/tests/activitypub/test_author.py index e65f86b76..fbb38da26 100644 --- a/bookwyrm/tests/activitypub/test_author.py +++ b/bookwyrm/tests/activitypub/test_author.py @@ -1,15 +1,17 @@ import datetime +from unittest.mock import patch from django.test import TestCase from bookwyrm import models class Author(TestCase): def setUp(self): - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + ) self.author = models.Author.objects.create( name="Author fullname", aliases=["One", "Two"], diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index 77844a222..9a88b1c66 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -25,24 +25,25 @@ class BaseActivity(TestCase): def setUp(self): """we're probably going to re-use this so why copy/paste""" - self.user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) - self.user.remote_id = "http://example.com/a/b" - self.user.save(broadcast=False) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) + self.user.remote_id = "http://example.com/a/b" + self.user.save(broadcast=False) - datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") - self.userdata = json.loads(datafile.read_bytes()) - # don't try to load the user icon - del self.userdata["icon"] + datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") + self.userdata = json.loads(datafile.read_bytes()) + # don't try to load the user icon + del self.userdata["icon"] - image_file = pathlib.Path(__file__).parent.joinpath( - "../../static/images/default_avi.jpg" - ) - image = Image.open(image_file) - output = BytesIO() - image.save(output, format=image.format) - self.image_data = output.getvalue() + image_file = pathlib.Path(__file__).parent.joinpath( + "../../static/images/default_avi.jpg" + ) + image = Image.open(image_file) + output = BytesIO() + image.save(output, format=image.format) + self.image_data = output.getvalue() def test_init(self, _): """simple successfuly init""" @@ -97,10 +98,11 @@ class BaseActivity(TestCase): status=200, ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - result = resolve_remote_id( - "https://example.com/user/mouse", model=models.User - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.set_remote_server.delay"): + result = resolve_remote_id( + "https://example.com/user/mouse", model=models.User + ) self.assertIsInstance(result, models.User) self.assertEqual(result.remote_id, "https://example.com/user/mouse") self.assertEqual(result.name, "MOUSE?? MOUSE!!") @@ -139,8 +141,9 @@ class BaseActivity(TestCase): self.user.avatar.file # pylint: disable=pointless-statement # this would trigger a broadcast because it's a local user - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - activity.to_model(model=models.User, instance=self.user) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + activity.to_model(model=models.User, instance=self.user) self.assertIsNotNone(self.user.avatar.file) self.assertEqual(self.user.name, "New Name") self.assertEqual(self.user.key_pair.public_key, "hi") @@ -152,9 +155,10 @@ class BaseActivity(TestCase): content="test status", user=self.user, ) - book = models.Edition.objects.create( - title="Test Edition", remote_id="http://book.com/book" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + book = models.Edition.objects.create( + title="Test Edition", remote_id="http://book.com/book" + ) update_data = activitypub.Note( id=status.remote_id, content=status.content, diff --git a/bookwyrm/tests/activitypub/test_person.py b/bookwyrm/tests/activitypub/test_person.py index 67aaf891e..20dbcdfb5 100644 --- a/bookwyrm/tests/activitypub/test_person.py +++ b/bookwyrm/tests/activitypub/test_person.py @@ -20,8 +20,9 @@ class Person(TestCase): def test_user_to_model(self): activity = activitypub.Person(**self.user_data) - with patch("bookwyrm.models.user.set_remote_server.delay"): - user = activity.to_model(model=models.User) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.set_remote_server.delay"): + user = activity.to_model(model=models.User) self.assertEqual(user.username, "mouse@example.com") self.assertEqual(user.remote_id, "https://example.com/user/mouse") self.assertFalse(user.local) diff --git a/bookwyrm/tests/activitypub/test_quotation.py b/bookwyrm/tests/activitypub/test_quotation.py index c90348bc3..59d4b1b61 100644 --- a/bookwyrm/tests/activitypub/test_quotation.py +++ b/bookwyrm/tests/activitypub/test_quotation.py @@ -12,20 +12,22 @@ class Quotation(TestCase): def setUp(self): """model objects we'll need""" - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.user = models.User.objects.create_user( - "mouse", - "mouse@mouse.mouse", - "mouseword", - local=False, - inbox="https://example.com/user/mouse/inbox", - outbox="https://example.com/user/mouse/outbox", - remote_id="https://example.com/user/mouse", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.user = models.User.objects.create_user( + "mouse", + "mouse@mouse.mouse", + "mouseword", + local=False, + inbox="https://example.com/user/mouse/inbox", + outbox="https://example.com/user/mouse/outbox", + remote_id="https://example.com/user/mouse", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", ) - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - ) datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_quotation.json") self.status_data = json.loads(datafile.read_bytes()) diff --git a/bookwyrm/tests/connectors/test_abstract_connector.py b/bookwyrm/tests/connectors/test_abstract_connector.py index 5c50e4b73..4390a220a 100644 --- a/bookwyrm/tests/connectors/test_abstract_connector.py +++ b/bookwyrm/tests/connectors/test_abstract_connector.py @@ -74,11 +74,12 @@ class AbstractConnector(TestCase): Mapping("openlibraryKey"), ] - self.book = models.Edition.objects.create( - title="Test Book", - remote_id="https://example.com/book/1234", - openlibrary_key="OL1234M", - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.book = models.Edition.objects.create( + title="Test Book", + remote_id="https://example.com/book/1234", + openlibrary_key="OL1234M", + ) def test_abstract_connector_init(self): """barebones connector for search with defaults""" @@ -110,8 +111,11 @@ class AbstractConnector(TestCase): responses.add( responses.GET, "https://example.com/book/abcd", json=self.edition_data ) - with patch("bookwyrm.connectors.abstract_connector.load_more_data.delay"): - result = self.connector.get_or_create_book("https://example.com/book/abcd") + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.connectors.abstract_connector.load_more_data.delay"): + result = self.connector.get_or_create_book( + "https://example.com/book/abcd" + ) self.assertEqual(result, self.book) self.assertEqual(models.Edition.objects.count(), 1) self.assertEqual(models.Edition.objects.count(), 1) diff --git a/bookwyrm/tests/connectors/test_bookwyrm_connector.py b/bookwyrm/tests/connectors/test_bookwyrm_connector.py index 46ea54a91..3a2480de4 100644 --- a/bookwyrm/tests/connectors/test_bookwyrm_connector.py +++ b/bookwyrm/tests/connectors/test_bookwyrm_connector.py @@ -1,4 +1,5 @@ """ testing book data connectors """ +from unittest.mock import patch import json import pathlib from django.test import TestCase @@ -25,8 +26,9 @@ class BookWyrmConnector(TestCase): def test_get_or_create_book_existing(self): """load book activity""" - work = models.Work.objects.create(title="Test Work") - book = models.Edition.objects.create(title="Test Edition", parent_work=work) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="Test Work") + book = models.Edition.objects.create(title="Test Edition", parent_work=work) result = self.connector.get_or_create_book(book.remote_id) self.assertEqual(book, result) diff --git a/bookwyrm/tests/connectors/test_connector_manager.py b/bookwyrm/tests/connectors/test_connector_manager.py index 67b108dd1..95920f072 100644 --- a/bookwyrm/tests/connectors/test_connector_manager.py +++ b/bookwyrm/tests/connectors/test_connector_manager.py @@ -1,4 +1,5 @@ """ interface between the app and various connectors """ +from unittest.mock import patch from django.test import TestCase import responses @@ -13,14 +14,15 @@ class ConnectorManager(TestCase): def setUp(self): """we'll need some books and a connector info entry""" - self.work = models.Work.objects.create(title="Example Work") + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create(title="Example Work") - self.edition = models.Edition.objects.create( - title="Example Edition", parent_work=self.work, isbn_10="0000000000" - ) - self.edition = models.Edition.objects.create( - title="Another Edition", parent_work=self.work, isbn_10="1111111111" - ) + self.edition = models.Edition.objects.create( + title="Example Edition", parent_work=self.work, isbn_10="0000000000" + ) + self.edition = models.Edition.objects.create( + title="Another Edition", parent_work=self.work, isbn_10="1111111111" + ) self.connector = models.Connector.objects.create( identifier="test_connector", diff --git a/bookwyrm/tests/connectors/test_openlibrary_connector.py b/bookwyrm/tests/connectors/test_openlibrary_connector.py index 699b26ed4..1cca34d68 100644 --- a/bookwyrm/tests/connectors/test_openlibrary_connector.py +++ b/bookwyrm/tests/connectors/test_openlibrary_connector.py @@ -178,20 +178,26 @@ class Openlibrary(TestCase): @responses.activate def test_expand_book_data(self): """given a book, get more editions""" - work = models.Work.objects.create(title="Test Work", openlibrary_key="OL1234W") - edition = models.Edition.objects.create(title="Test Edition", parent_work=work) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create( + title="Test Work", openlibrary_key="OL1234W" + ) + edition = models.Edition.objects.create( + title="Test Edition", parent_work=work + ) responses.add( responses.GET, "https://openlibrary.org/works/OL1234W/editions", json={"entries": []}, ) - with patch( - "bookwyrm.connectors.abstract_connector.AbstractConnector." - "create_edition_from_data" - ): - self.connector.expand_book_data(edition) - self.connector.expand_book_data(work) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch( + "bookwyrm.connectors.abstract_connector.AbstractConnector." + "create_edition_from_data" + ): + self.connector.expand_book_data(edition) + self.connector.expand_book_data(work) def test_get_description(self): """should do some cleanup on the description data""" @@ -224,11 +230,14 @@ class Openlibrary(TestCase): json={"hi": "there"}, status=200, ) - with patch( - "bookwyrm.connectors.openlibrary.Connector." "get_authors_from_data" - ) as mock: - mock.return_value = [] - result = self.connector.create_edition_from_data(work, self.edition_data) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch( + "bookwyrm.connectors.openlibrary.Connector." "get_authors_from_data" + ) as mock: + mock.return_value = [] + result = self.connector.create_edition_from_data( + work, self.edition_data + ) self.assertEqual(result.parent_work, work) self.assertEqual(result.title, "Sabriel") self.assertEqual(result.isbn_10, "0060273224") diff --git a/bookwyrm/tests/connectors/test_self_connector.py b/bookwyrm/tests/connectors/test_self_connector.py index db97b65a0..4f2173085 100644 --- a/bookwyrm/tests/connectors/test_self_connector.py +++ b/bookwyrm/tests/connectors/test_self_connector.py @@ -1,4 +1,5 @@ """ testing book data connectors """ +from unittest.mock import patch import datetime from django.test import TestCase from django.utils import timezone @@ -29,12 +30,13 @@ class SelfConnector(TestCase): def test_format_search_result(self): """create a SearchResult""" author = models.Author.objects.create(name="Anonymous") - edition = models.Edition.objects.create( - title="Edition of Example Work", - published_date=datetime.datetime(1980, 5, 10, tzinfo=timezone.utc), - ) - edition.authors.add(author) - result = self.connector.search("Edition of Example")[0] + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + edition = models.Edition.objects.create( + title="Edition of Example Work", + published_date=datetime.datetime(1980, 5, 10, tzinfo=timezone.utc), + ) + edition.authors.add(author) + result = self.connector.search("Edition of Example")[0] self.assertEqual(result.title, "Edition of Example Work") self.assertEqual(result.key, edition.remote_id) self.assertEqual(result.author, "Anonymous") @@ -44,34 +46,35 @@ class SelfConnector(TestCase): def test_search_rank(self): """prioritize certain results""" author = models.Author.objects.create(name="Anonymous") - edition = models.Edition.objects.create( - title="Edition of Example Work", - published_date=datetime.datetime(1980, 5, 10, tzinfo=timezone.utc), - parent_work=models.Work.objects.create(title=""), - ) - # author text is rank C - edition.authors.add(author) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + edition = models.Edition.objects.create( + title="Edition of Example Work", + published_date=datetime.datetime(1980, 5, 10, tzinfo=timezone.utc), + parent_work=models.Work.objects.create(title=""), + ) + # author text is rank C + edition.authors.add(author) - # series is rank D - models.Edition.objects.create( - title="Another Edition", - series="Anonymous", - parent_work=models.Work.objects.create(title=""), - ) - # subtitle is rank B - models.Edition.objects.create( - title="More Editions", - subtitle="The Anonymous Edition", - parent_work=models.Work.objects.create(title=""), - ) - # title is rank A - models.Edition.objects.create(title="Anonymous") - # doesn't rank in this search - edition = models.Edition.objects.create( - title="An Edition", parent_work=models.Work.objects.create(title="") - ) + # series is rank D + models.Edition.objects.create( + title="Another Edition", + series="Anonymous", + parent_work=models.Work.objects.create(title=""), + ) + # subtitle is rank B + models.Edition.objects.create( + title="More Editions", + subtitle="The Anonymous Edition", + parent_work=models.Work.objects.create(title=""), + ) + # title is rank A + models.Edition.objects.create(title="Anonymous") + # doesn't rank in this search + edition = models.Edition.objects.create( + title="An Edition", parent_work=models.Work.objects.create(title="") + ) - results = self.connector.search("Anonymous") + results = self.connector.search("Anonymous") self.assertEqual(len(results), 3) self.assertEqual(results[0].title, "Anonymous") self.assertEqual(results[1].title, "More Editions") @@ -79,28 +82,29 @@ class SelfConnector(TestCase): def test_search_multiple_editions(self): """it should get rid of duplicate editions for the same work""" - work = models.Work.objects.create(title="Work Title") - edition_1 = models.Edition.objects.create( - title="Edition 1 Title", parent_work=work - ) - edition_2 = models.Edition.objects.create( - title="Edition 2 Title", - parent_work=work, - edition_rank=20, # that's default babey - ) - edition_3 = models.Edition.objects.create(title="Fish", parent_work=work) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="Work Title") + edition_1 = models.Edition.objects.create( + title="Edition 1 Title", parent_work=work + ) + edition_2 = models.Edition.objects.create( + title="Edition 2 Title", + parent_work=work, + edition_rank=20, # that's default babey + ) + edition_3 = models.Edition.objects.create(title="Fish", parent_work=work) - # pick the best edition - results = self.connector.search("Edition 1 Title") - self.assertEqual(len(results), 1) - self.assertEqual(results[0].key, edition_1.remote_id) + # pick the best edition + results = self.connector.search("Edition 1 Title") + self.assertEqual(len(results), 1) + self.assertEqual(results[0].key, edition_1.remote_id) - # pick the default edition when no match is best - results = self.connector.search("Edition Title") - self.assertEqual(len(results), 1) - self.assertEqual(results[0].key, edition_2.remote_id) + # pick the default edition when no match is best + results = self.connector.search("Edition Title") + self.assertEqual(len(results), 1) + self.assertEqual(results[0].key, edition_2.remote_id) - # only matches one edition, so no deduplication takes place - results = self.connector.search("Fish") - self.assertEqual(len(results), 1) - self.assertEqual(results[0].key, edition_3.remote_id) + # only matches one edition, so no deduplication takes place + results = self.connector.search("Fish") + self.assertEqual(len(results), 1) + self.assertEqual(results[0].key, edition_3.remote_id) diff --git a/bookwyrm/tests/importers/test_goodreads_import.py b/bookwyrm/tests/importers/test_goodreads_import.py index 655567d05..81f47e6f5 100644 --- a/bookwyrm/tests/importers/test_goodreads_import.py +++ b/bookwyrm/tests/importers/test_goodreads_import.py @@ -21,9 +21,10 @@ class GoodreadsImport(TestCase): self.importer = GoodreadsImporter() datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") self.csv = open(datafile, "r", encoding=self.importer.encoding) - self.user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "password", local=True - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "password", local=True + ) models.Connector.objects.create( identifier=DOMAIN, @@ -36,12 +37,13 @@ class GoodreadsImport(TestCase): search_url="https://%s/search?q=" % DOMAIN, priority=1, ) - work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - parent_work=work, - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, + ) def test_create_job(self): """creates the import job entry and checks csv""" @@ -92,7 +94,8 @@ class GoodreadsImport(TestCase): def test_import_data(self): """resolve entry""" import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") - book = models.Edition.objects.create(title="Test Book") + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + book = models.Edition.objects.create(title="Test Book") with patch( "bookwyrm.models.import_job.ImportItem.get_book_from_isbn" @@ -119,10 +122,11 @@ class GoodreadsImport(TestCase): ) break - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - handle_imported_book( - self.importer.service, self.user, import_item, False, "public" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + handle_imported_book( + self.importer.service, self.user, import_item, False, "public" + ) shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) @@ -183,13 +187,14 @@ class GoodreadsImport(TestCase): ) break - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - handle_imported_book( - self.importer.service, self.user, import_item, False, "public" - ) - handle_imported_book( - self.importer.service, self.user, import_item, False, "public" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + handle_imported_book( + self.importer.service, self.user, import_item, False, "public" + ) + handle_imported_book( + self.importer.service, self.user, import_item, False, "public" + ) shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) @@ -216,10 +221,11 @@ class GoodreadsImport(TestCase): job_id=import_job.id, index=0, data=entry, book=self.book ) - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - handle_imported_book( - self.importer.service, self.user, import_item, True, "unlisted" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + handle_imported_book( + self.importer.service, self.user, import_item, True, "unlisted" + ) review = models.Review.objects.get(book=self.book, user=self.user) self.assertEqual(review.content, "mixed feelings") self.assertEqual(review.rating, 2) @@ -242,10 +248,11 @@ class GoodreadsImport(TestCase): job_id=import_job.id, index=0, data=entry, book=self.book ) - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - handle_imported_book( - self.importer.service, self.user, import_item, True, "unlisted" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + handle_imported_book( + self.importer.service, self.user, import_item, True, "unlisted" + ) review = models.ReviewRating.objects.get(book=self.book, user=self.user) self.assertIsInstance(review, models.ReviewRating) self.assertEqual(review.rating, 2) @@ -265,10 +272,11 @@ class GoodreadsImport(TestCase): job_id=import_job.id, index=0, data=entry, book=self.book ) - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - handle_imported_book( - self.importer.service, self.user, import_item, False, "unlisted" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + handle_imported_book( + self.importer.service, self.user, import_item, False, "unlisted" + ) self.assertFalse( models.Review.objects.filter(book=self.book, user=self.user).exists() ) diff --git a/bookwyrm/tests/importers/test_librarything_import.py b/bookwyrm/tests/importers/test_librarything_import.py index 5ae0944c0..8e299d567 100644 --- a/bookwyrm/tests/importers/test_librarything_import.py +++ b/bookwyrm/tests/importers/test_librarything_import.py @@ -22,9 +22,10 @@ class LibrarythingImport(TestCase): # Librarything generates latin encoded exports... self.csv = open(datafile, "r", encoding=self.importer.encoding) - self.user = models.User.objects.create_user( - "mmai", "mmai@mmai.mmai", "password", local=True - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.user = models.User.objects.create_user( + "mmai", "mmai@mmai.mmai", "password", local=True + ) models.Connector.objects.create( identifier=DOMAIN, @@ -37,12 +38,13 @@ class LibrarythingImport(TestCase): search_url="https://%s/search?q=" % DOMAIN, priority=1, ) - work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - parent_work=work, - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, + ) def test_create_job(self): """creates the import job entry and checks csv""" @@ -82,7 +84,8 @@ class LibrarythingImport(TestCase): def test_import_data(self): """resolve entry""" import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") - book = models.Edition.objects.create(title="Test Book") + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + book = models.Edition.objects.create(title="Test Book") with patch( "bookwyrm.models.import_job.ImportItem.get_book_from_isbn" @@ -111,10 +114,11 @@ class LibrarythingImport(TestCase): ) break - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - handle_imported_book( - self.importer.service, self.user, import_item, False, "public" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + handle_imported_book( + self.importer.service, self.user, import_item, False, "public" + ) shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) @@ -147,10 +151,11 @@ class LibrarythingImport(TestCase): ) break - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - handle_imported_book( - self.importer.service, self.user, import_item, False, "public" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + handle_imported_book( + self.importer.service, self.user, import_item, False, "public" + ) shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) @@ -179,13 +184,14 @@ class LibrarythingImport(TestCase): ) break - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - handle_imported_book( - self.importer.service, self.user, import_item, False, "public" - ) - handle_imported_book( - self.importer.service, self.user, import_item, False, "public" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + handle_imported_book( + self.importer.service, self.user, import_item, False, "public" + ) + handle_imported_book( + self.importer.service, self.user, import_item, False, "public" + ) shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) @@ -212,10 +218,11 @@ class LibrarythingImport(TestCase): job_id=import_job.id, index=0, data=entry, book=self.book ) - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - handle_imported_book( - self.importer.service, self.user, import_item, True, "unlisted" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + handle_imported_book( + self.importer.service, self.user, import_item, True, "unlisted" + ) review = models.Review.objects.get(book=self.book, user=self.user) self.assertEqual(review.content, "chef d'oeuvre") self.assertEqual(review.rating, 5) @@ -235,10 +242,11 @@ class LibrarythingImport(TestCase): job_id=import_job.id, index=0, data=entry, book=self.book ) - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - handle_imported_book( - self.importer.service, self.user, import_item, False, "unlisted" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + handle_imported_book( + self.importer.service, self.user, import_item, False, "unlisted" + ) self.assertFalse( models.Review.objects.filter(book=self.book, user=self.user).exists() ) diff --git a/bookwyrm/tests/management/test_populate_streams.py b/bookwyrm/tests/management/test_populate_streams.py index d187c054b..1705cd348 100644 --- a/bookwyrm/tests/management/test_populate_streams.py +++ b/bookwyrm/tests/management/test_populate_streams.py @@ -12,23 +12,29 @@ class Activitystreams(TestCase): def setUp(self): """we need some stuff""" - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" - ) - self.another_user = models.User.objects.create_user( - "nutria", "nutria@nutria.nutria", "password", local=True, localname="nutria" - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" ) - self.book = models.Edition.objects.create(title="test book") + self.another_user = models.User.objects.create_user( + "nutria", + "nutria@nutria.nutria", + "password", + local=True, + localname="nutria", + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.book = models.Edition.objects.create(title="test book") def test_populate_streams(self, _): """make sure the function on the redis manager gets called""" diff --git a/bookwyrm/tests/models/test_activitypub_mixin.py b/bookwyrm/tests/models/test_activitypub_mixin.py index 1c0975c44..020d031d2 100644 --- a/bookwyrm/tests/models/test_activitypub_mixin.py +++ b/bookwyrm/tests/models/test_activitypub_mixin.py @@ -19,21 +19,22 @@ class ActivitypubMixins(TestCase): def setUp(self): """shared data""" - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" - ) - self.local_user.remote_id = "http://example.com/a/b" - self.local_user.save(broadcast=False) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" ) + self.local_user.remote_id = "http://example.com/a/b" + self.local_user.save(broadcast=False) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) self.object_mock = { "to": "to field", @@ -70,9 +71,10 @@ class ActivitypubMixins(TestCase): """attempt to match a remote id to an object in the db""" # uses a different remote id scheme # this isn't really part of this test directly but it's helpful to state - book = models.Edition.objects.create( - title="Test Edition", remote_id="http://book.com/book" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + book = models.Edition.objects.create( + title="Test Edition", remote_id="http://book.com/book" + ) self.assertEqual(book.origin_id, "http://book.com/book") self.assertNotEqual(book.remote_id, "http://book.com/book") @@ -101,10 +103,11 @@ class ActivitypubMixins(TestCase): def test_find_existing(self, _): """match a blob of data to a model""" - book = models.Edition.objects.create( - title="Test edition", - openlibrary_key="OL1234", - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + book = models.Edition.objects.create( + title="Test edition", + openlibrary_key="OL1234", + ) result = models.Edition.find_existing({"openlibraryKey": "OL1234"}) self.assertEqual(result, book) @@ -140,16 +143,17 @@ class ActivitypubMixins(TestCase): MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) self.local_user.followers.add(self.remote_user) - with patch("bookwyrm.models.user.set_remote_server.delay"): - another_remote_user = models.User.objects.create_user( - "nutria", - "nutria@nutria.com", - "nutriaword", - local=False, - remote_id="https://example.com/users/nutria", - inbox="https://example.com/users/nutria/inbox", - outbox="https://example.com/users/nutria/outbox", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.set_remote_server.delay"): + another_remote_user = models.User.objects.create_user( + "nutria", + "nutria@nutria.com", + "nutriaword", + local=False, + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + outbox="https://example.com/users/nutria/outbox", + ) MockSelf = namedtuple("Self", ("privacy", "user", "recipients")) mock_self = MockSelf("public", self.local_user, [another_remote_user]) @@ -163,16 +167,17 @@ class ActivitypubMixins(TestCase): MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) self.local_user.followers.add(self.remote_user) - with patch("bookwyrm.models.user.set_remote_server.delay"): - another_remote_user = models.User.objects.create_user( - "nutria", - "nutria@nutria.com", - "nutriaword", - local=False, - remote_id="https://example.com/users/nutria", - inbox="https://example.com/users/nutria/inbox", - outbox="https://example.com/users/nutria/outbox", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.set_remote_server.delay"): + another_remote_user = models.User.objects.create_user( + "nutria", + "nutria@nutria.com", + "nutriaword", + local=False, + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + outbox="https://example.com/users/nutria/outbox", + ) MockSelf = namedtuple("Self", ("privacy", "user", "recipients")) mock_self = MockSelf("direct", self.local_user, [another_remote_user]) @@ -184,17 +189,18 @@ class ActivitypubMixins(TestCase): """should combine users with the same shared_inbox""" self.remote_user.shared_inbox = "http://example.com/inbox" self.remote_user.save(broadcast=False) - with patch("bookwyrm.models.user.set_remote_server.delay"): - another_remote_user = models.User.objects.create_user( - "nutria", - "nutria@nutria.com", - "nutriaword", - local=False, - remote_id="https://example.com/users/nutria", - inbox="https://example.com/users/nutria/inbox", - shared_inbox="http://example.com/inbox", - outbox="https://example.com/users/nutria/outbox", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.set_remote_server.delay"): + another_remote_user = models.User.objects.create_user( + "nutria", + "nutria@nutria.com", + "nutriaword", + local=False, + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + shared_inbox="http://example.com/inbox", + outbox="https://example.com/users/nutria/outbox", + ) MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) self.local_user.followers.add(self.remote_user) @@ -206,17 +212,18 @@ class ActivitypubMixins(TestCase): def test_get_recipients_software(self, _): """should differentiate between bookwyrm and other remote users""" - with patch("bookwyrm.models.user.set_remote_server.delay"): - another_remote_user = models.User.objects.create_user( - "nutria", - "nutria@nutria.com", - "nutriaword", - local=False, - remote_id="https://example.com/users/nutria", - inbox="https://example.com/users/nutria/inbox", - outbox="https://example.com/users/nutria/outbox", - bookwyrm_user=False, - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.set_remote_server.delay"): + another_remote_user = models.User.objects.create_user( + "nutria", + "nutria@nutria.com", + "nutriaword", + local=False, + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + outbox="https://example.com/users/nutria/outbox", + bookwyrm_user=False, + ) MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) self.local_user.followers.add(self.remote_user) diff --git a/bookwyrm/tests/models/test_base_model.py b/bookwyrm/tests/models/test_base_model.py index 75d0444c1..496a86104 100644 --- a/bookwyrm/tests/models/test_base_model.py +++ b/bookwyrm/tests/models/test_base_model.py @@ -12,19 +12,20 @@ class BaseModel(TestCase): def setUp(self): """shared data""" - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) class BookWyrmTestModel(base_model.BookWyrmModel): """just making it not abstract""" diff --git a/bookwyrm/tests/models/test_book_model.py b/bookwyrm/tests/models/test_book_model.py index cad00d43a..df61514c1 100644 --- a/bookwyrm/tests/models/test_book_model.py +++ b/bookwyrm/tests/models/test_book_model.py @@ -2,6 +2,7 @@ from dateutil.parser import parse from django.test import TestCase from django.utils import timezone +from unittest.mock import patch from bookwyrm import models, settings from bookwyrm.models.book import isbn_10_to_13, isbn_13_to_10 @@ -12,17 +13,18 @@ class Book(TestCase): def setUp(self): """we'll need some books""" - self.work = models.Work.objects.create( - title="Example Work", remote_id="https://example.com/book/1" - ) - self.first_edition = models.Edition.objects.create( - title="Example Edition", - parent_work=self.work, - ) - self.second_edition = models.Edition.objects.create( - title="Another Example Edition", - parent_work=self.work, - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create( + title="Example Work", remote_id="https://example.com/book/1" + ) + self.first_edition = models.Edition.objects.create( + title="Example Edition", + parent_work=self.work, + ) + self.second_edition = models.Edition.objects.create( + title="Another Example Edition", + parent_work=self.work, + ) def test_remote_id(self): """fanciness with remote/origin ids""" @@ -56,7 +58,8 @@ class Book(TestCase): def test_get_edition_info(self): """text slug about an edition""" - book = models.Edition.objects.create(title="Test Edition") + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + book = models.Edition.objects.create(title="Test Edition") self.assertEqual(book.edition_info, "") book.physical_format = "worm" diff --git a/bookwyrm/tests/models/test_federated_server.py b/bookwyrm/tests/models/test_federated_server.py index 43724568d..fabd69eea 100644 --- a/bookwyrm/tests/models/test_federated_server.py +++ b/bookwyrm/tests/models/test_federated_server.py @@ -11,29 +11,30 @@ class FederatedServer(TestCase): def setUp(self): """we'll need a user""" self.server = models.FederatedServer.objects.create(server_name="test.server") - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - federated_server=self.server, - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", - ) - self.inactive_remote_user = models.User.objects.create_user( - "nutria", - "nutria@nutria.com", - "nutriaword", - federated_server=self.server, - local=False, - remote_id="https://example.com/users/nutria", - inbox="https://example.com/users/nutria/inbox", - outbox="https://example.com/users/nutria/outbox", - is_active=False, - deactivation_reason="self_deletion", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + federated_server=self.server, + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + self.inactive_remote_user = models.User.objects.create_user( + "nutria", + "nutria@nutria.com", + "nutriaword", + federated_server=self.server, + local=False, + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + outbox="https://example.com/users/nutria/outbox", + is_active=False, + deactivation_reason="self_deletion", + ) def test_block_unblock(self): """block a server and all users on it""" diff --git a/bookwyrm/tests/models/test_fields.py b/bookwyrm/tests/models/test_fields.py index ea692b625..339d35685 100644 --- a/bookwyrm/tests/models/test_fields.py +++ b/bookwyrm/tests/models/test_fields.py @@ -188,9 +188,10 @@ class ActivitypubFields(TestCase): @patch("bookwyrm.activitystreams.ActivityStream.add_status") def test_privacy_field_set_activity_from_field(self, *_): """translate between to/cc fields and privacy""" - user = User.objects.create_user( - "rat", "rat@rat.rat", "ratword", local=True, localname="rat" - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + user = User.objects.create_user( + "rat", "rat@rat.rat", "ratword", local=True, localname="rat" + ) public = "https://www.w3.org/ns/activitystreams#Public" followers = "%s/followers" % user.remote_id @@ -248,16 +249,20 @@ class ActivitypubFields(TestCase): del userdata["icon"] # it shouldn't match with this unrelated user: - unrelated_user = User.objects.create_user( - "rat", "rat@rat.rat", "ratword", local=True, localname="rat" - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + unrelated_user = User.objects.create_user( + "rat", "rat@rat.rat", "ratword", local=True, localname="rat" + ) - # test receiving an unknown remote id and loading data - responses.add( - responses.GET, "https://example.com/user/mouse", json=userdata, status=200 - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - value = instance.field_from_activity("https://example.com/user/mouse") + # test receiving an unknown remote id and loading data + responses.add( + responses.GET, + "https://example.com/user/mouse", + json=userdata, + status=200, + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + value = instance.field_from_activity("https://example.com/user/mouse") self.assertIsInstance(value, User) self.assertNotEqual(value, unrelated_user) self.assertEqual(value.remote_id, "https://example.com/user/mouse") @@ -272,11 +277,12 @@ class ActivitypubFields(TestCase): del userdata["icon"] # it shouldn't match with this unrelated user: - unrelated_user = User.objects.create_user( - "rat", "rat@rat.rat", "ratword", local=True, localname="rat" - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - value = instance.field_from_activity(activitypub.Person(**userdata)) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + unrelated_user = User.objects.create_user( + "rat", "rat@rat.rat", "ratword", local=True, localname="rat" + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + value = instance.field_from_activity(activitypub.Person(**userdata)) self.assertIsInstance(value, User) self.assertNotEqual(value, unrelated_user) self.assertEqual(value.remote_id, "https://example.com/user/mouse") @@ -288,28 +294,31 @@ class ActivitypubFields(TestCase): instance = fields.ForeignKey(User, on_delete=models.CASCADE) datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") userdata = json.loads(datafile.read_bytes()) - user = User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) - user.remote_id = "https://example.com/user/mouse" - user.save(broadcast=False) - User.objects.create_user( - "rat", "rat@rat.rat", "ratword", local=True, localname="rat" - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + user = User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) + user.remote_id = "https://example.com/user/mouse" + user.save(broadcast=False) - with patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast"): - value = instance.field_from_activity(activitypub.Person(**userdata)) + User.objects.create_user( + "rat", "rat@rat.rat", "ratword", local=True, localname="rat" + ) + + with patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast"): + value = instance.field_from_activity(activitypub.Person(**userdata)) self.assertEqual(value, user) def test_foreign_key_from_activity_str_existing(self): """test receiving a remote id of an existing object in the db""" instance = fields.ForeignKey(User, on_delete=models.CASCADE) - user = User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) - User.objects.create_user( - "rat", "rat@rat.rat", "ratword", local=True, localname="rat" - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + user = User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) + User.objects.create_user( + "rat", "rat@rat.rat", "ratword", local=True, localname="rat" + ) value = instance.field_from_activity(user.remote_id) self.assertEqual(value, user) @@ -351,10 +360,11 @@ class ActivitypubFields(TestCase): responses.add( responses.GET, "https://example.com/user/mouse", json=userdata, status=200 ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - value = instance.field_from_activity( - ["https://example.com/user/mouse", "bleh"] - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.set_remote_server.delay"): + value = instance.field_from_activity( + ["https://example.com/user/mouse", "bleh"] + ) self.assertIsInstance(value, list) self.assertEqual(len(value), 1) self.assertIsInstance(value[0], User) @@ -386,16 +396,17 @@ class ActivitypubFields(TestCase): @patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast") def test_image_field(self, _): """storing images""" - user = User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) - image_file = pathlib.Path(__file__).parent.joinpath( - "../../static/images/default_avi.jpg" - ) - image = Image.open(image_file) - output = BytesIO() - image.save(output, format=image.format) - user.avatar.save("test.jpg", ContentFile(output.getvalue())) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + user = User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) + image_file = pathlib.Path(__file__).parent.joinpath( + "../../static/images/default_avi.jpg" + ) + image = Image.open(image_file) + output = BytesIO() + image.save(output, format=image.format) + user.avatar.save("test.jpg", ContentFile(output.getvalue())) output = fields.image_serializer(user.avatar, alt="alt text") self.assertIsNotNone( diff --git a/bookwyrm/tests/models/test_import_model.py b/bookwyrm/tests/models/test_import_model.py index 76a914d1b..be6702b31 100644 --- a/bookwyrm/tests/models/test_import_model.py +++ b/bookwyrm/tests/models/test_import_model.py @@ -59,9 +59,10 @@ class ImportJob(TestCase): unknown_read_data["Exclusive Shelf"] = "read" unknown_read_data["Date Read"] = "" - user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) job = models.ImportJob.objects.create(user=user) self.item_1 = models.ImportItem.objects.create( job=job, index=1, data=currently_reading_data @@ -174,6 +175,9 @@ class ImportJob(TestCase): with patch( "bookwyrm.connectors.openlibrary.Connector." "get_authors_from_data" ): - book = self.item_1.get_book_from_isbn() + with patch( + "bookwyrm.preview_images.generate_edition_preview_image_task.delay" + ): + book = self.item_1.get_book_from_isbn() self.assertEqual(book.title, "Sabriel") diff --git a/bookwyrm/tests/models/test_list.py b/bookwyrm/tests/models/test_list.py index 8f5bd4976..34517ddcd 100644 --- a/bookwyrm/tests/models/test_list.py +++ b/bookwyrm/tests/models/test_list.py @@ -11,11 +11,13 @@ class List(TestCase): def setUp(self): """look, a list""" - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) - work = models.Work.objects.create(title="hello") - self.book = models.Edition.objects.create(title="hi", parent_work=work) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="hello") + self.book = models.Edition.objects.create(title="hi", parent_work=work) def test_remote_id(self, _): """shelves use custom remote ids""" diff --git a/bookwyrm/tests/models/test_readthrough_model.py b/bookwyrm/tests/models/test_readthrough_model.py index 986b739b6..186e7ad05 100644 --- a/bookwyrm/tests/models/test_readthrough_model.py +++ b/bookwyrm/tests/models/test_readthrough_model.py @@ -1,6 +1,7 @@ """ testing models """ from django.test import TestCase from django.core.exceptions import ValidationError +from unittest.mock import patch from bookwyrm import models @@ -10,15 +11,16 @@ class ReadThrough(TestCase): def setUp(self): """look, a shelf""" - self.user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) - self.work = models.Work.objects.create(title="Example Work") - - self.edition = models.Edition.objects.create( - title="Example Edition", parent_work=self.work - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create(title="Example Work") + self.edition = models.Edition.objects.create( + title="Example Edition", parent_work=self.work + ) self.readthrough = models.ReadThrough.objects.create( user=self.user, book=self.edition diff --git a/bookwyrm/tests/models/test_relationship_models.py b/bookwyrm/tests/models/test_relationship_models.py index d629b5c7a..0a630dd12 100644 --- a/bookwyrm/tests/models/test_relationship_models.py +++ b/bookwyrm/tests/models/test_relationship_models.py @@ -10,21 +10,22 @@ class Relationship(TestCase): def setUp(self): """we need some users for this""" - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" ) - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" - ) - self.local_user.remote_id = "http://local.com/user/mouse" - self.local_user.save(broadcast=False) + self.local_user.remote_id = "http://local.com/user/mouse" + self.local_user.save(broadcast=False) def test_user_follows_from_request(self): """convert a follow request into a follow""" diff --git a/bookwyrm/tests/models/test_shelf_model.py b/bookwyrm/tests/models/test_shelf_model.py index 911df059d..f5c255a63 100644 --- a/bookwyrm/tests/models/test_shelf_model.py +++ b/bookwyrm/tests/models/test_shelf_model.py @@ -12,11 +12,15 @@ class Shelf(TestCase): def setUp(self): """look, a shelf""" - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) - work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create(title="test book", parent_work=work) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="test book", parent_work=work + ) def test_remote_id(self): """shelves use custom remote ids""" diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index 4c8930bc4..f8d49abc3 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -16,34 +16,37 @@ from bookwyrm import activitypub, models, settings # pylint: disable=too-many-public-methods @patch("bookwyrm.models.Status.broadcast") @patch("bookwyrm.activitystreams.ActivityStream.add_status") +@patch("bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores") class Status(TestCase): """lotta types of statuses""" def setUp(self): """useful things for creating a status""" - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" ) - self.book = models.Edition.objects.create(title="Test Edition") + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.book = models.Edition.objects.create(title="Test Edition") - image_file = pathlib.Path(__file__).parent.joinpath( - "../../static/images/default_avi.jpg" - ) - image = Image.open(image_file) - output = BytesIO() - with patch("bookwyrm.models.Status.broadcast"): - image.save(output, format=image.format) - self.book.cover.save("test.jpg", ContentFile(output.getvalue())) + image_file = pathlib.Path(__file__).parent.joinpath( + "../../static/images/default_avi.jpg" + ) + image = Image.open(image_file) + output = BytesIO() + with patch("bookwyrm.models.Status.broadcast"): + image.save(output, format=image.format) + self.book.cover.save("test.jpg", ContentFile(output.getvalue())) def test_status_generated_fields(self, *_): """setting remote id""" @@ -58,9 +61,10 @@ class Status(TestCase): child = models.Status.objects.create( content="hello", reply_parent=parent, user=self.local_user ) - models.Review.objects.create( - content="hey", reply_parent=parent, user=self.local_user, book=self.book - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + models.Review.objects.create( + content="hey", reply_parent=parent, user=self.local_user, book=self.book + ) models.Status.objects.create( content="hi hello", reply_parent=child, user=self.local_user ) @@ -92,9 +96,10 @@ class Status(TestCase): child = models.Status.objects.create( content="hello", reply_parent=parent, user=self.local_user ) - models.Review.objects.create( - content="hey", reply_parent=parent, user=self.local_user, book=self.book - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + models.Review.objects.create( + content="hey", reply_parent=parent, user=self.local_user, book=self.book + ) models.Status.objects.create( content="hi hello", reply_parent=child, user=self.local_user ) @@ -251,14 +256,15 @@ class Status(TestCase): def test_review_to_activity(self, *_): """subclass of the base model version with a "pure" serializer""" - status = models.Review.objects.create( - name="Review name", - content="test content", - rating=3.0, - user=self.local_user, - book=self.book, - ) - activity = status.to_activity() + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + status = models.Review.objects.create( + name="Review name", + content="test content", + rating=3.0, + user=self.local_user, + book=self.book, + ) + activity = status.to_activity() self.assertEqual(activity["id"], status.remote_id) self.assertEqual(activity["type"], "Review") self.assertEqual(activity["rating"], 3) @@ -268,14 +274,15 @@ class Status(TestCase): def test_review_to_pure_activity(self, *_): """subclass of the base model version with a "pure" serializer""" - status = models.Review.objects.create( - name="Review's name", - content="test content", - rating=3.0, - user=self.local_user, - book=self.book, - ) - activity = status.to_activity(pure=True) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + status = models.Review.objects.create( + name="Review's name", + content="test content", + rating=3.0, + user=self.local_user, + book=self.book, + ) + activity = status.to_activity(pure=True) self.assertEqual(activity["id"], status.remote_id) self.assertEqual(activity["type"], "Article") self.assertEqual( @@ -292,13 +299,14 @@ class Status(TestCase): def test_review_to_pure_activity_no_rating(self, *_): """subclass of the base model version with a "pure" serializer""" - status = models.Review.objects.create( - name="Review name", - content="test content", - user=self.local_user, - book=self.book, - ) - activity = status.to_activity(pure=True) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + status = models.Review.objects.create( + name="Review name", + content="test content", + user=self.local_user, + book=self.book, + ) + activity = status.to_activity(pure=True) self.assertEqual(activity["id"], status.remote_id) self.assertEqual(activity["type"], "Article") self.assertEqual( @@ -314,12 +322,13 @@ class Status(TestCase): def test_reviewrating_to_pure_activity(self, *_): """subclass of the base model version with a "pure" serializer""" - status = models.ReviewRating.objects.create( - rating=3.0, - user=self.local_user, - book=self.book, - ) - activity = status.to_activity(pure=True) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + status = models.ReviewRating.objects.create( + rating=3.0, + user=self.local_user, + book=self.book, + ) + activity = status.to_activity(pure=True) self.assertEqual(activity["id"], status.remote_id) self.assertEqual(activity["type"], "Note") self.assertEqual( @@ -348,11 +357,12 @@ class Status(TestCase): status = models.Status.objects.create( content="test content", user=self.local_user ) - fav = models.Favorite.objects.create(status=status, user=self.local_user) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + fav = models.Favorite.objects.create(status=status, user=self.local_user) - # can't fav a status twice - with self.assertRaises(IntegrityError): - models.Favorite.objects.create(status=status, user=self.local_user) + # can't fav a status twice + with self.assertRaises(IntegrityError): + models.Favorite.objects.create(status=status, user=self.local_user) activity = fav.to_activity() self.assertEqual(activity["type"], "Like") @@ -384,7 +394,8 @@ class Status(TestCase): user=self.local_user, notification_type="GLORB" ) - def test_create_broadcast(self, _, broadcast_mock): + # pylint: disable=unused-argument + def test_create_broadcast(self, one, two, broadcast_mock, *_): """should send out two verions of a status on create""" models.Comment.objects.create( content="hi", user=self.local_user, book=self.book diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index b2791379d..b6c716638 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -11,15 +11,16 @@ from bookwyrm.settings import DOMAIN # pylint: disable=missing-function-docstring class User(TestCase): def setUp(self): - self.user = models.User.objects.create_user( - "mouse@%s" % DOMAIN, - "mouse@mouse.mouse", - "mouseword", - local=True, - localname="mouse", - name="hi", - bookwyrm_user=False, - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.user = models.User.objects.create_user( + "mouse@%s" % DOMAIN, + "mouse@mouse.mouse", + "mouseword", + local=True, + localname="mouse", + name="hi", + bookwyrm_user=False, + ) def test_computed_fields(self): """username instead of id here""" @@ -34,15 +35,16 @@ class User(TestCase): self.assertIsNotNone(self.user.key_pair.public_key) def test_remote_user(self): - with patch("bookwyrm.models.user.set_remote_server.delay"): - user = models.User.objects.create_user( - "rat", - "rat@rat.rat", - "ratword", - local=False, - remote_id="https://example.com/dfjkg", - bookwyrm_user=False, - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.set_remote_server.delay"): + user = models.User.objects.create_user( + "rat", + "rat@rat.rat", + "ratword", + local=False, + remote_id="https://example.com/dfjkg", + bookwyrm_user=False, + ) self.assertEqual(user.username, "rat@example.com") def test_user_shelves(self): diff --git a/bookwyrm/tests/test_activitystreams.py b/bookwyrm/tests/test_activitystreams.py index 59266383f..da335e894 100644 --- a/bookwyrm/tests/test_activitystreams.py +++ b/bookwyrm/tests/test_activitystreams.py @@ -11,23 +11,29 @@ class Activitystreams(TestCase): def setUp(self): """use a test csv""" - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" - ) - self.another_user = models.User.objects.create_user( - "nutria", "nutria@nutria.nutria", "password", local=True, localname="nutria" - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" ) - self.book = models.Edition.objects.create(title="test book") + self.another_user = models.User.objects.create_user( + "nutria", + "nutria@nutria.nutria", + "password", + local=True, + localname="nutria", + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.book = models.Edition.objects.create(title="test book") class TestStream(activitystreams.ActivityStream): """test stream, don't have to do anything here""" diff --git a/bookwyrm/tests/test_emailing.py b/bookwyrm/tests/test_emailing.py index 0f9cc3659..e7b1bb1ca 100644 --- a/bookwyrm/tests/test_emailing.py +++ b/bookwyrm/tests/test_emailing.py @@ -15,14 +15,16 @@ class Emailing(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_invite_email(self, email_mock): """load the invite email""" diff --git a/bookwyrm/tests/test_preview_images.py b/bookwyrm/tests/test_preview_images.py new file mode 100644 index 000000000..ac9e3f1c5 --- /dev/null +++ b/bookwyrm/tests/test_preview_images.py @@ -0,0 +1,119 @@ +""" test generating preview images """ +import pathlib +from unittest.mock import patch +from PIL import Image + +from django.test import TestCase +from django.test.client import RequestFactory +from django.core.files.uploadedfile import SimpleUploadedFile +from django.db.models.fields.files import ImageFieldFile + +from bookwyrm import models, settings + +from bookwyrm.preview_images import ( + generate_site_preview_image_task, + generate_edition_preview_image_task, + generate_user_preview_image_task, + generate_preview_image, + save_and_cleanup, +) + + +# pylint: disable=unused-argument +# pylint: disable=missing-function-docstring +class PreviewImages(TestCase): + """every response to a get request, html or json""" + + def setUp(self): + """we need basic test data and mocks""" + self.factory = RequestFactory() + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + avatar_file = pathlib.Path(__file__).parent.joinpath( + "../static/images/no_cover.jpg" + ) + self.local_user = models.User.objects.create_user( + "possum@local.com", + "possum@possum.possum", + "password", + local=True, + localname="possum", + avatar=SimpleUploadedFile( + avatar_file, + open(avatar_file, "rb").read(), + content_type="image/jpeg", + ), + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create(title="Test Work") + self.edition = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=self.work, + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + self.site = models.SiteSettings.objects.create() + + def test_generate_preview_image(self, *args, **kwargs): + image_file = pathlib.Path(__file__).parent.joinpath( + "../static/images/no_cover.jpg" + ) + + texts = { + "text_one": "Awesome Possum", + "text_three": "@possum@local.com", + } + + result = generate_preview_image(texts=texts, picture=image_file, rating=5) + self.assertIsInstance(result, Image.Image) + self.assertEqual( + result.size, (settings.PREVIEW_IMG_WIDTH, settings.PREVIEW_IMG_HEIGHT) + ) + + def test_store_preview_image(self, *args, **kwargs): + image = Image.new("RGB", (200, 200), color="#F00") + + result = save_and_cleanup(image, instance=self.local_user) + self.assertTrue(result) + + self.local_user.refresh_from_db() + self.assertIsInstance(self.local_user.preview_image, ImageFieldFile) + self.assertIsNotNone(self.local_user.preview_image) + self.assertEqual(self.local_user.preview_image.width, 200) + self.assertEqual(self.local_user.preview_image.height, 200) + + def test_site_preview(self, *args, **kwargs): + """generate site preview""" + generate_site_preview_image_task() + + self.site.refresh_from_db() + + self.assertIsInstance(self.site.preview_image, ImageFieldFile) + self.assertIsNotNone(self.site.preview_image) + self.assertEqual(self.site.preview_image.width, settings.PREVIEW_IMG_WIDTH) + self.assertEqual(self.site.preview_image.height, settings.PREVIEW_IMG_HEIGHT) + + def test_edition_preview(self, *args, **kwargs): + """generate edition preview""" + generate_edition_preview_image_task(self.edition.id) + + self.edition.refresh_from_db() + + self.assertIsInstance(self.edition.preview_image, ImageFieldFile) + self.assertIsNotNone(self.edition.preview_image) + self.assertEqual(self.edition.preview_image.width, settings.PREVIEW_IMG_WIDTH) + self.assertEqual(self.edition.preview_image.height, settings.PREVIEW_IMG_HEIGHT) + + def test_user_preview(self, *args, **kwargs): + """generate user preview""" + generate_user_preview_image_task(self.local_user.id) + + self.local_user.refresh_from_db() + + self.assertIsInstance(self.local_user.preview_image, ImageFieldFile) + self.assertIsNotNone(self.local_user.preview_image) + self.assertEqual( + self.local_user.preview_image.width, settings.PREVIEW_IMG_WIDTH + ) + self.assertEqual( + self.local_user.preview_image.height, settings.PREVIEW_IMG_HEIGHT + ) diff --git a/bookwyrm/tests/test_signing.py b/bookwyrm/tests/test_signing.py index 758ba9bbc..b9b1af261 100644 --- a/bookwyrm/tests/test_signing.py +++ b/bookwyrm/tests/test_signing.py @@ -37,15 +37,20 @@ class Signature(TestCase): def setUp(self): """create users and test data""" - self.mouse = models.User.objects.create_user( - "mouse@%s" % DOMAIN, "mouse@example.com", "", local=True, localname="mouse" - ) - self.rat = models.User.objects.create_user( - "rat@%s" % DOMAIN, "rat@example.com", "", local=True, localname="rat" - ) - self.cat = models.User.objects.create_user( - "cat@%s" % DOMAIN, "cat@example.com", "", local=True, localname="cat" - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.mouse = models.User.objects.create_user( + "mouse@%s" % DOMAIN, + "mouse@example.com", + "", + local=True, + localname="mouse", + ) + self.rat = models.User.objects.create_user( + "rat@%s" % DOMAIN, "rat@example.com", "", local=True, localname="rat" + ) + self.cat = models.User.objects.create_user( + "cat@%s" % DOMAIN, "cat@example.com", "", local=True, localname="cat" + ) private_key, public_key = create_key_pair() @@ -53,7 +58,8 @@ class Signature(TestCase): "http://localhost/user/remote", KeyPair(private_key, public_key) ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def send(self, signature, now, data, digest): """test request""" @@ -113,9 +119,10 @@ class Signature(TestCase): status=200, ) - with patch("bookwyrm.models.user.get_remote_reviews.delay"): - response = self.send_test_request(sender=self.fake_remote) - self.assertEqual(response.status_code, 200) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.get_remote_reviews.delay"): + response = self.send_test_request(sender=self.fake_remote) + self.assertEqual(response.status_code, 200) @responses.activate def test_key_needs_refresh(self): @@ -136,22 +143,23 @@ class Signature(TestCase): data["publicKey"]["publicKeyPem"] = key_pair.public_key responses.add(responses.GET, self.fake_remote.remote_id, json=data, status=200) - with patch("bookwyrm.models.user.get_remote_reviews.delay"): - # Key correct: - response = self.send_test_request(sender=self.fake_remote) - self.assertEqual(response.status_code, 200) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.get_remote_reviews.delay"): + # Key correct: + response = self.send_test_request(sender=self.fake_remote) + self.assertEqual(response.status_code, 200) - # Old key is cached, so still works: - response = self.send_test_request(sender=self.fake_remote) - self.assertEqual(response.status_code, 200) + # Old key is cached, so still works: + response = self.send_test_request(sender=self.fake_remote) + self.assertEqual(response.status_code, 200) - # Try with new key: - response = self.send_test_request(sender=new_sender) - self.assertEqual(response.status_code, 200) + # Try with new key: + response = self.send_test_request(sender=new_sender) + self.assertEqual(response.status_code, 200) - # Now the old key will fail: - response = self.send_test_request(sender=self.fake_remote) - self.assertEqual(response.status_code, 401) + # Now the old key will fail: + response = self.send_test_request(sender=self.fake_remote) + self.assertEqual(response.status_code, 401) @responses.activate def test_nonexistent_signer(self): diff --git a/bookwyrm/tests/test_templatetags.py b/bookwyrm/tests/test_templatetags.py index 4b1aa5946..2e10d9308 100644 --- a/bookwyrm/tests/test_templatetags.py +++ b/bookwyrm/tests/test_templatetags.py @@ -16,55 +16,62 @@ from bookwyrm.templatetags import ( @patch("bookwyrm.activitystreams.ActivityStream.add_status") +@patch("bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores") class TemplateTags(TestCase): """lotta different things here""" def setUp(self): """create some filler objects""" - self.user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.mouse", - "mouseword", - local=True, - localname="mouse", - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.rat", - "ratword", - remote_id="http://example.com/rat", - local=False, + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.mouse", + "mouseword", + local=True, + localname="mouse", ) - self.book = models.Edition.objects.create(title="Test Book") + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.rat", + "ratword", + remote_id="http://example.com/rat", + local=False, + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.book = models.Edition.objects.create(title="Test Book") - def test_get_user_rating(self, _): + def test_get_user_rating(self, *_): """get a user's most recent rating of a book""" - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - models.Review.objects.create(user=self.user, book=self.book, rating=3) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.Review.objects.create(user=self.user, book=self.book, rating=3) self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 3) - def test_get_user_rating_doesnt_exist(self, _): + def test_get_user_rating_doesnt_exist(self, *_): """there is no rating available""" self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 0) - def test_get_user_identifer_local(self, _): + def test_get_user_identifer_local(self, *_): """fall back to the simplest uid available""" self.assertNotEqual(self.user.username, self.user.localname) self.assertEqual(utilities.get_user_identifier(self.user), "mouse") - def test_get_user_identifer_remote(self, _): + def test_get_user_identifer_remote(self, *_): """for a remote user, should be their full username""" self.assertEqual( utilities.get_user_identifier(self.remote_user), "rat@example.com" ) - def test_get_replies(self, _): + def test_get_replies(self, *_): """direct replies to a status""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - parent = models.Review.objects.create( - user=self.user, book=self.book, content="hi" - ) + with patch( + "bookwyrm.preview_images.generate_edition_preview_image_task.delay" + ): + parent = models.Review.objects.create( + user=self.user, book=self.book, content="hi" + ) first_child = models.Status.objects.create( reply_parent=parent, user=self.user, content="hi" ) @@ -87,12 +94,15 @@ class TemplateTags(TestCase): self.assertTrue(second_child in replies) self.assertFalse(third_child in replies) - def test_get_parent(self, _): + def test_get_parent(self, *_): """get the reply parent of a status""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - parent = models.Review.objects.create( - user=self.user, book=self.book, content="hi" - ) + with patch( + "bookwyrm.preview_images.generate_edition_preview_image_task.delay" + ): + parent = models.Review.objects.create( + user=self.user, book=self.book, content="hi" + ) child = models.Status.objects.create( reply_parent=parent, user=self.user, content="hi" ) @@ -101,55 +111,63 @@ class TemplateTags(TestCase): self.assertEqual(result, parent) self.assertIsInstance(result, models.Review) - def test_get_user_liked(self, _): + def test_get_user_liked(self, *_): """did a user like a status""" - status = models.Review.objects.create(user=self.remote_user, book=self.book) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + status = models.Review.objects.create(user=self.remote_user, book=self.book) self.assertFalse(interaction.get_user_liked(self.user, status)) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Favorite.objects.create(user=self.user, status=status) self.assertTrue(interaction.get_user_liked(self.user, status)) - def test_get_user_boosted(self, _): + def test_get_user_boosted(self, *_): """did a user boost a status""" - status = models.Review.objects.create(user=self.remote_user, book=self.book) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + status = models.Review.objects.create(user=self.remote_user, book=self.book) self.assertFalse(interaction.get_user_boosted(self.user, status)) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Boost.objects.create(user=self.user, boosted_status=status) self.assertTrue(interaction.get_user_boosted(self.user, status)) - def test_get_boosted(self, _): + def test_get_boosted(self, *_): """load a boosted status""" - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - status = models.Review.objects.create(user=self.remote_user, book=self.book) - boost = models.Boost.objects.create(user=self.user, boosted_status=status) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + status = models.Review.objects.create( + user=self.remote_user, book=self.book + ) + boost = models.Boost.objects.create( + user=self.user, boosted_status=status + ) boosted = status_display.get_boosted(boost) self.assertIsInstance(boosted, models.Review) self.assertEqual(boosted, status) - def test_get_book_description(self, _): + def test_get_book_description(self, *_): """grab it from the edition or the parent""" - work = models.Work.objects.create(title="Test Work") - self.book.parent_work = work - self.book.save() + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="Test Work") + self.book.parent_work = work + self.book.save() - self.assertIsNone(bookwyrm_tags.get_book_description(self.book)) + self.assertIsNone(bookwyrm_tags.get_book_description(self.book)) - work.description = "hi" - work.save() - self.assertEqual(bookwyrm_tags.get_book_description(self.book), "hi") + work.description = "hi" + work.save() + self.assertEqual(bookwyrm_tags.get_book_description(self.book), "hi") - self.book.description = "hello" - self.book.save() - self.assertEqual(bookwyrm_tags.get_book_description(self.book), "hello") + self.book.description = "hello" + self.book.save() + self.assertEqual(bookwyrm_tags.get_book_description(self.book), "hello") - def test_get_uuid(self, _): + def test_get_uuid(self, *_): """uuid functionality""" uuid = utilities.get_uuid("hi") self.assertTrue(re.match(r"hi[A-Za-z0-9\-]", uuid)) - def test_get_markdown(self, _): + def test_get_markdown(self, *_): """mardown format data""" result = markdown.get_markdown("_hi_") self.assertEqual(result, "

hi

") @@ -157,13 +175,13 @@ class TemplateTags(TestCase): result = markdown.get_markdown("_hi_") self.assertEqual(result, "

hi

") - def test_get_mentions(self, _): + def test_get_mentions(self, *_): """list of people mentioned""" status = models.Status.objects.create(content="hi", user=self.remote_user) result = status_display.get_mentions(status, self.user) self.assertEqual(result, "@rat@example.com ") - def test_related_status(self, _): + def test_related_status(self, *_): """gets the subclass model for a notification status""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(content="hi", user=self.user) diff --git a/bookwyrm/tests/views/inbox/test_inbox.py b/bookwyrm/tests/views/inbox/test_inbox.py index 697f40100..0ac813bb1 100644 --- a/bookwyrm/tests/views/inbox/test_inbox.py +++ b/bookwyrm/tests/views/inbox/test_inbox.py @@ -18,25 +18,27 @@ class Inbox(TestCase): """basic user and book data""" self.client = Client() self.factory = RequestFactory() - local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) - local_user.remote_id = "https://example.com/user/mouse" - local_user.save(broadcast=False) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", ) + local_user.remote_id = "https://example.com/user/mouse" + local_user.save(broadcast=False) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) self.create_json = { "id": "hi", "type": "Create", @@ -45,7 +47,8 @@ class Inbox(TestCase): "cc": ["https://example.com/user/mouse/followers"], "object": {}, } - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_inbox_invalid_get(self): """shouldn't try to handle if the user is not found""" diff --git a/bookwyrm/tests/views/inbox/test_inbox_add.py b/bookwyrm/tests/views/inbox/test_inbox_add.py index 5f86b1215..2862fdf57 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_add.py +++ b/bookwyrm/tests/views/inbox/test_inbox_add.py @@ -13,33 +13,35 @@ class InboxAdd(TestCase): def setUp(self): """basic user and book data""" - local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) - local_user.remote_id = "https://example.com/user/mouse" - local_user.save(broadcast=False) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", ) - work = models.Work.objects.create(title="work title") - self.book = models.Edition.objects.create( - title="Test", - remote_id="https://example.com/book/37292", - parent_work=work, - ) - - models.SiteSettings.objects.create() + local_user.remote_id = "https://example.com/user/mouse" + local_user.save(broadcast=False) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="work title") + self.book = models.Edition.objects.create( + title="Test", + remote_id="https://example.com/book/37292", + parent_work=work, + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() @responses.activate def test_handle_add_book_to_shelf(self): diff --git a/bookwyrm/tests/views/inbox/test_inbox_announce.py b/bookwyrm/tests/views/inbox/test_inbox_announce.py index 4243dc78e..43900e339 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_announce.py +++ b/bookwyrm/tests/views/inbox/test_inbox_announce.py @@ -13,32 +13,34 @@ class InboxActivities(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) - self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", ) - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - self.status = models.Status.objects.create( - user=self.local_user, - content="Test status", - remote_id="https://example.com/status/1", + self.local_user.remote_id = "https://example.com/user/mouse" + self.local_user.save(broadcast=False) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + self.status = models.Status.objects.create( + user=self.local_user, + content="Test status", + remote_id="https://example.com/status/1", + ) self.create_json = { "id": "hi", @@ -48,10 +50,12 @@ class InboxActivities(TestCase): "cc": ["https://example.com/user/mouse/followers"], "object": {}, } - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_boost(self, redis_mock): + @patch("bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores") + def test_boost(self, redis_mock, _): """boost a status""" self.assertEqual(models.Notification.objects.count(), 0) activity = { @@ -81,14 +85,16 @@ class InboxActivities(TestCase): @responses.activate @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_boost_remote_status(self, redis_mock): + @patch("bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores") + def test_boost_remote_status(self, redis_mock, _): """boost a status from a remote server""" - work = models.Work.objects.create(title="work title") - book = models.Edition.objects.create( - title="Test", - remote_id="https://bookwyrm.social/book/37292", - parent_work=work, - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="work title") + book = models.Edition.objects.create( + title="Test", + remote_id="https://bookwyrm.social/book/37292", + parent_work=work, + ) self.assertEqual(models.Notification.objects.count(), 0) activity = { "type": "Announce", @@ -153,12 +159,13 @@ class InboxActivities(TestCase): views.inbox.activity_task(activity) self.assertEqual(models.Boost.objects.count(), 0) - def test_unboost(self): + @patch("bookwyrm.activitystreams.ActivityStream.add_status") + @patch("bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores") + def test_unboost(self, *_): """undo a boost""" - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - boost = models.Boost.objects.create( - boosted_status=self.status, user=self.remote_user - ) + boost = models.Boost.objects.create( + boosted_status=self.status, user=self.remote_user + ) activity = { "type": "Undo", "actor": "hi", @@ -175,11 +182,7 @@ class InboxActivities(TestCase): "published": "Mon, 25 May 2020 19:31:20 GMT", }, } - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" - ) as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) self.assertFalse(models.Boost.objects.exists()) def test_unboost_unknown_boost(self): diff --git a/bookwyrm/tests/views/inbox/test_inbox_block.py b/bookwyrm/tests/views/inbox/test_inbox_block.py index 956cf538a..8c12e42bf 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_block.py +++ b/bookwyrm/tests/views/inbox/test_inbox_block.py @@ -12,27 +12,28 @@ class InboxBlock(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) - self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", ) - - models.SiteSettings.objects.create() + self.local_user.remote_id = "https://example.com/user/mouse" + self.local_user.save(broadcast=False) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_handle_blocks(self): """create a "block" database entry from an activity""" diff --git a/bookwyrm/tests/views/inbox/test_inbox_create.py b/bookwyrm/tests/views/inbox/test_inbox_create.py index 958dfee8c..ed5e6f937 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_create.py +++ b/bookwyrm/tests/views/inbox/test_inbox_create.py @@ -15,15 +15,26 @@ class InboxCreate(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) - self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + ) + self.local_user.remote_id = "https://example.com/user/mouse" + self.local_user.save(broadcast=False) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.activitystreams.ActivityStream.add_status"): self.status = models.Status.objects.create( @@ -31,16 +42,6 @@ class InboxCreate(TestCase): content="Test status", remote_id="https://example.com/status/1", ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", - ) self.create_json = { "id": "hi", @@ -50,7 +51,8 @@ class InboxCreate(TestCase): "cc": ["https://example.com/user/mouse/followers"], "object": {}, } - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_create_status(self): """the "it justs works" mode""" @@ -60,9 +62,11 @@ class InboxCreate(TestCase): "../../data/ap_quotation.json" ) status_data = json.loads(datafile.read_bytes()) - models.Edition.objects.create( - title="Test Book", remote_id="https://example.com/book/1" - ) + + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + models.Edition.objects.create( + title="Test Book", remote_id="https://example.com/book/1" + ) activity = self.create_json activity["object"] = status_data @@ -129,9 +133,10 @@ class InboxCreate(TestCase): def test_create_rating(self): """a remote rating activity""" - book = models.Edition.objects.create( - title="Test Book", remote_id="https://example.com/book/1" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + book = models.Edition.objects.create( + title="Test Book", remote_id="https://example.com/book/1" + ) activity = self.create_json activity["object"] = { "id": "https://example.com/user/mouse/reviewrating/12", @@ -157,9 +162,12 @@ class InboxCreate(TestCase): "rating": 3, "@context": "https://www.w3.org/ns/activitystreams", } - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch( + "bookwyrm.activitystreams.ActivityStream.add_status" + ) as redis_mock: + views.inbox.activity_task(activity) + self.assertTrue(redis_mock.called) rating = models.ReviewRating.objects.first() self.assertEqual(rating.book, book) self.assertEqual(rating.rating, 3.0) diff --git a/bookwyrm/tests/views/inbox/test_inbox_delete.py b/bookwyrm/tests/views/inbox/test_inbox_delete.py index 617dcf6f5..067a30ad3 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_delete.py +++ b/bookwyrm/tests/views/inbox/test_inbox_delete.py @@ -13,25 +13,26 @@ class InboxActivities(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) - self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", ) + self.local_user.remote_id = "https://example.com/user/mouse" + self.local_user.save(broadcast=False) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) with patch("bookwyrm.activitystreams.ActivityStream.add_status"): self.status = models.Status.objects.create( user=self.remote_user, @@ -47,7 +48,8 @@ class InboxActivities(TestCase): "cc": ["https://example.com/user/mouse/followers"], "object": {}, } - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_delete_status(self): """remove a status""" @@ -117,7 +119,8 @@ class InboxActivities(TestCase): "object": self.remote_user.remote_id, } - views.inbox.activity_task(activity) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + views.inbox.activity_task(activity) self.assertFalse(models.User.objects.get(username="rat@example.com").is_active) def test_delete_user_unknown(self): diff --git a/bookwyrm/tests/views/inbox/test_inbox_follow.py b/bookwyrm/tests/views/inbox/test_inbox_follow.py index f5332b7a3..063ad5d39 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_follow.py +++ b/bookwyrm/tests/views/inbox/test_inbox_follow.py @@ -13,27 +13,29 @@ class InboxRelationships(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) - self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", ) + self.local_user.remote_id = "https://example.com/user/mouse" + self.local_user.save(broadcast=False) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_follow(self): """remote user wants to follow local user""" diff --git a/bookwyrm/tests/views/inbox/test_inbox_like.py b/bookwyrm/tests/views/inbox/test_inbox_like.py index a5f3a9f09..5d072fb0c 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_like.py +++ b/bookwyrm/tests/views/inbox/test_inbox_like.py @@ -12,25 +12,27 @@ class InboxActivities(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) - self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", ) + self.local_user.remote_id = "https://example.com/user/mouse" + self.local_user.save(broadcast=False) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.activitystreams.ActivityStream.add_status"): self.status = models.Status.objects.create( @@ -47,7 +49,9 @@ class InboxActivities(TestCase): "cc": ["https://example.com/user/mouse/followers"], "object": {}, } - models.SiteSettings.objects.create() + + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_handle_favorite(self): """fav a status""" diff --git a/bookwyrm/tests/views/inbox/test_inbox_remove.py b/bookwyrm/tests/views/inbox/test_inbox_remove.py index a0c4cdcf9..2812ef1ff 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_remove.py +++ b/bookwyrm/tests/views/inbox/test_inbox_remove.py @@ -12,33 +12,37 @@ class InboxRemove(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) - self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", ) - self.work = models.Work.objects.create(title="work title") - self.book = models.Edition.objects.create( - title="Test", - remote_id="https://bookwyrm.social/book/37292", - parent_work=self.work, - ) + self.local_user.remote_id = "https://example.com/user/mouse" + self.local_user.save(broadcast=False) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create(title="work title") + self.book = models.Edition.objects.create( + title="Test", + remote_id="https://bookwyrm.social/book/37292", + parent_work=self.work, + ) + + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_handle_unshelve_book(self): """remove a book from a shelf""" diff --git a/bookwyrm/tests/views/inbox/test_inbox_update.py b/bookwyrm/tests/views/inbox/test_inbox_update.py index 9fdc97922..db9b0ca0b 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_update.py +++ b/bookwyrm/tests/views/inbox/test_inbox_update.py @@ -14,25 +14,26 @@ class InboxUpdate(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) - self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", ) + self.local_user.remote_id = "https://example.com/user/mouse" + self.local_user.save(broadcast=False) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) self.create_json = { "id": "hi", @@ -42,7 +43,8 @@ class InboxUpdate(TestCase): "cc": ["https://example.com/user/mouse/followers"], "object": {}, } - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_update_list(self): """a new list""" @@ -96,16 +98,18 @@ class InboxUpdate(TestCase): del userdata["icon"] self.assertIsNone(self.remote_user.name) self.assertFalse(self.remote_user.discoverable) - views.inbox.activity_task( - { - "type": "Update", - "to": [], - "cc": [], - "actor": "hi", - "id": "sdkjf", - "object": userdata, - } - ) + + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + views.inbox.activity_task( + { + "type": "Update", + "to": [], + "cc": [], + "actor": "hi", + "id": "sdkjf", + "object": userdata, + } + ) user = models.User.objects.get(id=self.remote_user.id) self.assertEqual(user.name, "RAT???") self.assertEqual(user.username, "rat@example.com") @@ -120,27 +124,28 @@ class InboxUpdate(TestCase): datafile = pathlib.Path(__file__).parent.joinpath("../../data/bw_edition.json") bookdata = json.loads(datafile.read_bytes()) - models.Work.objects.create( - title="Test Work", remote_id="https://bookwyrm.social/book/5988" - ) - book = models.Edition.objects.create( - title="Test Book", remote_id="https://bookwyrm.social/book/5989" - ) - - del bookdata["authors"] - self.assertEqual(book.title, "Test Book") - - with patch("bookwyrm.activitypub.base_activity.set_related_field.delay"): - views.inbox.activity_task( - { - "type": "Update", - "to": [], - "cc": [], - "actor": "hi", - "id": "sdkjf", - "object": bookdata, - } + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + models.Work.objects.create( + title="Test Work", remote_id="https://bookwyrm.social/book/5988" ) + book = models.Edition.objects.create( + title="Test Book", remote_id="https://bookwyrm.social/book/5989" + ) + + del bookdata["authors"] + self.assertEqual(book.title, "Test Book") + + with patch("bookwyrm.activitypub.base_activity.set_related_field.delay"): + views.inbox.activity_task( + { + "type": "Update", + "to": [], + "cc": [], + "actor": "hi", + "id": "sdkjf", + "object": bookdata, + } + ) book = models.Edition.objects.get(id=book.id) self.assertEqual(book.title, "Piranesi") self.assertEqual(book.last_edited_by, self.remote_user) @@ -150,9 +155,10 @@ class InboxUpdate(TestCase): datafile = pathlib.Path(__file__).parent.joinpath("../../data/bw_work.json") bookdata = json.loads(datafile.read_bytes()) - book = models.Work.objects.create( - title="Test Book", remote_id="https://bookwyrm.social/book/5988" - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + book = models.Work.objects.create( + title="Test Book", remote_id="https://bookwyrm.social/book/5988" + ) del bookdata["authors"] self.assertEqual(book.title, "Test Book") diff --git a/bookwyrm/tests/views/test_announcements.py b/bookwyrm/tests/views/test_announcements.py index 0f17d3b9a..e25923949 100644 --- a/bookwyrm/tests/views/test_announcements.py +++ b/bookwyrm/tests/views/test_announcements.py @@ -2,6 +2,7 @@ from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory +from unittest.mock import patch from bookwyrm import forms, models, views @@ -12,14 +13,16 @@ class AnnouncementViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_announcements_page(self): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_authentication.py b/bookwyrm/tests/views/test_authentication.py index c310b0a26..94213c610 100644 --- a/bookwyrm/tests/views/test_authentication.py +++ b/bookwyrm/tests/views/test_authentication.py @@ -19,16 +19,18 @@ class AuthenticationViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "password", + local=True, + localname="mouse", + ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False - self.settings = models.SiteSettings.objects.create(id=1) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + self.settings = models.SiteSettings.objects.create(id=1) def test_login_get(self): """there are so many views, this just makes sure it LOADS""" @@ -58,8 +60,9 @@ class AuthenticationViews(TestCase): "email": "aa@bb.cccc", }, ) - with patch("bookwyrm.views.authentication.login"): - response = view(request) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.views.authentication.login"): + response = view(request) self.assertEqual(models.User.objects.count(), 2) self.assertEqual(response.status_code, 302) nutria = models.User.objects.last() @@ -74,8 +77,9 @@ class AuthenticationViews(TestCase): "register/", {"localname": "nutria ", "password": "mouseword", "email": "aa@bb.ccc"}, ) - with patch("bookwyrm.views.authentication.login"): - response = view(request) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.views.authentication.login"): + response = view(request) self.assertEqual(models.User.objects.count(), 2) self.assertEqual(response.status_code, 302) nutria = models.User.objects.last() @@ -153,8 +157,9 @@ class AuthenticationViews(TestCase): "invite_code": "testcode", }, ) - with patch("bookwyrm.views.authentication.login"): - response = view(request) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.views.authentication.login"): + response = view(request) self.assertEqual(models.User.objects.count(), 2) self.assertEqual(response.status_code, 302) self.assertEqual(models.SiteInvite.objects.get().times_used, 1) diff --git a/bookwyrm/tests/views/test_author.py b/bookwyrm/tests/views/test_author.py index 5dfbc3504..06080bb25 100644 --- a/bookwyrm/tests/views/test_author.py +++ b/bookwyrm/tests/views/test_author.py @@ -17,14 +17,15 @@ class AuthorViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) self.group = Group.objects.create(name="editor") self.group.permissions.add( Permission.objects.create( @@ -33,13 +34,15 @@ class AuthorViews(TestCase): content_type=ContentType.objects.get_for_model(models.User), ).id ) - self.work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - parent_work=self.work, - ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=self.work, + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_author_page(self): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_block.py b/bookwyrm/tests/views/test_block.py index 0b754689d..d640617cd 100644 --- a/bookwyrm/tests/views/test_block.py +++ b/bookwyrm/tests/views/test_block.py @@ -14,24 +14,26 @@ class BlockViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", ) - models.SiteSettings.objects.create() + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_block_get(self, _): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_book.py b/bookwyrm/tests/views/test_book.py index 909718ec3..375cdb038 100644 --- a/bookwyrm/tests/views/test_book.py +++ b/bookwyrm/tests/views/test_book.py @@ -23,14 +23,15 @@ class BookViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) self.group = Group.objects.create(name="editor") self.group.permissions.add( Permission.objects.create( @@ -39,13 +40,15 @@ class BookViews(TestCase): content_type=ContentType.objects.get_for_model(models.User), ).id ) - self.work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - parent_work=self.work, - ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=self.work, + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_book_page(self): """there are so many views, this just makes sure it LOADS""" @@ -71,7 +74,8 @@ class BookViews(TestCase): request = self.factory.get("") request.user = self.local_user request.user.is_superuser = True - result = view(request, self.book.id) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + result = view(request, self.book.id) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) @@ -85,8 +89,11 @@ class BookViews(TestCase): form.data["last_edited_by"] = self.local_user.id request = self.factory.post("", form.data) request.user = self.local_user - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - view(request, self.book.id) + + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + view(request, self.book.id) + self.book.refresh_from_db() self.assertEqual(self.book.title, "New Title") @@ -101,8 +108,9 @@ class BookViews(TestCase): request = self.factory.post("", form.data) request.user = self.local_user - result = view(request, self.book.id) - result.render() + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + result = view(request, self.book.id) + result.render() # the changes haven't been saved yet self.book.refresh_from_db() @@ -120,8 +128,9 @@ class BookViews(TestCase): request = self.factory.post("", form.data) request.user = self.local_user - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - view(request, self.book.id) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + view(request, self.book.id) self.book.refresh_from_db() self.assertEqual(self.book.title, "New Title") @@ -141,8 +150,9 @@ class BookViews(TestCase): request = self.factory.post("", form.data) request.user = self.local_user - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - view(request, self.book.id) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + view(request, self.book.id) self.book.refresh_from_db() self.assertEqual(self.book.title, "New Title") self.assertFalse(self.book.authors.exists()) @@ -157,7 +167,8 @@ class BookViews(TestCase): request = self.factory.post("", form.data) request.user = self.local_user - view(request) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + view(request) book = models.Edition.objects.get(title="New Title") self.assertEqual(book.parent_work.title, "New Title") @@ -172,7 +183,8 @@ class BookViews(TestCase): request = self.factory.post("", form.data) request.user = self.local_user - view(request) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + view(request) book = models.Edition.objects.get(title="New Title") self.assertEqual(book.parent_work, self.work) @@ -188,7 +200,8 @@ class BookViews(TestCase): request = self.factory.post("", form.data) request.user = self.local_user - view(request) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + view(request) book = models.Edition.objects.get(title="New Title") self.assertEqual(book.parent_work.title, "New Title") self.assertEqual(book.authors.first().name, "Sappho") @@ -197,8 +210,11 @@ class BookViews(TestCase): def test_switch_edition(self): """updates user's relationships to a book""" work = models.Work.objects.create(title="test work") - edition1 = models.Edition.objects.create(title="first ed", parent_work=work) - edition2 = models.Edition.objects.create(title="second ed", parent_work=work) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + edition1 = models.Edition.objects.create(title="first ed", parent_work=work) + edition2 = models.Edition.objects.create( + title="second ed", parent_work=work + ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): shelf = models.Shelf.objects.create(name="Test Shelf", user=self.local_user) models.ShelfBook.objects.create( @@ -251,11 +267,12 @@ class BookViews(TestCase): request = self.factory.post("", form.data) request.user = self.local_user - with patch( - "bookwyrm.models.activitypub_mixin.broadcast_task.delay" - ) as delay_mock: - views.upload_cover(request, self.book.id) - self.assertEqual(delay_mock.call_count, 1) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch( + "bookwyrm.models.activitypub_mixin.broadcast_task.delay" + ) as delay_mock: + views.upload_cover(request, self.book.id) + self.assertEqual(delay_mock.call_count, 1) self.book.refresh_from_db() self.assertTrue(self.book.cover) @@ -283,11 +300,12 @@ class BookViews(TestCase): request = self.factory.post("", form.data) request.user = self.local_user - with patch( - "bookwyrm.models.activitypub_mixin.broadcast_task.delay" - ) as delay_mock: - views.upload_cover(request, self.book.id) - self.assertEqual(delay_mock.call_count, 1) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch( + "bookwyrm.models.activitypub_mixin.broadcast_task.delay" + ) as delay_mock: + views.upload_cover(request, self.book.id) + self.assertEqual(delay_mock.call_count, 1) self.book.refresh_from_db() self.assertTrue(self.book.cover) diff --git a/bookwyrm/tests/views/test_directory.py b/bookwyrm/tests/views/test_directory.py index cada50bce..79dbd2a23 100644 --- a/bookwyrm/tests/views/test_directory.py +++ b/bookwyrm/tests/views/test_directory.py @@ -2,6 +2,7 @@ from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory +from unittest.mock import patch from bookwyrm import models, views @@ -12,24 +13,26 @@ class DirectoryViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - self.rat = models.User.objects.create_user( - "rat@local.com", - "rat@rat.com", - "ratword", - local=True, - localname="rat", - remote_id="https://example.com/users/rat", - discoverable=True, - ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + self.rat = models.User.objects.create_user( + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", + discoverable=True, + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_directory_page(self): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_edit_user.py b/bookwyrm/tests/views/test_edit_user.py new file mode 100644 index 000000000..af39f0054 --- /dev/null +++ b/bookwyrm/tests/views/test_edit_user.py @@ -0,0 +1,156 @@ +""" test for app action functionality """ +import json +import pathlib +from unittest.mock import patch +from PIL import Image + +from django.contrib.auth.models import AnonymousUser +from django.contrib.sessions.middleware import SessionMiddleware +from django.core.files.base import ContentFile +from django.core.files.uploadedfile import SimpleUploadedFile +from django.template.response import TemplateResponse +from django.test import TestCase +from django.test.client import RequestFactory + +from bookwyrm import forms, models, views + + +class EditUserViews(TestCase): + """view user and edit profile""" + + def setUp(self): + """we need basic test data and mocks""" + self.factory = RequestFactory() + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + self.rat = models.User.objects.create_user( + "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" + ) + + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.book = models.Edition.objects.create(title="test") + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.ShelfBook.objects.create( + book=self.book, + user=self.local_user, + shelf=self.local_user.shelf_set.first(), + ) + + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() + self.anonymous_user = AnonymousUser + self.anonymous_user.is_authenticated = False + + def test_edit_user_page(self): + """there are so many views, this just makes sure it LOADS""" + view = views.EditUser.as_view() + request = self.factory.get("") + request.user = self.local_user + result = view(request) + self.assertIsInstance(result, TemplateResponse) + result.render() + self.assertEqual(result.status_code, 200) + + def test_edit_user(self): + """use a form to update a user""" + view = views.EditUser.as_view() + form = forms.EditUserForm(instance=self.local_user) + form.data["name"] = "New Name" + form.data["email"] = "wow@email.com" + form.data["preferred_timezone"] = "UTC" + request = self.factory.post("", form.data) + request.user = self.local_user + + self.assertIsNone(self.local_user.name) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch( + "bookwyrm.models.activitypub_mixin.broadcast_task.delay" + ) as delay_mock: + view(request) + self.assertEqual(delay_mock.call_count, 1) + self.assertEqual(self.local_user.name, "New Name") + self.assertEqual(self.local_user.email, "wow@email.com") + + def test_edit_user_avatar(self): + """use a form to update a user""" + view = views.EditUser.as_view() + form = forms.EditUserForm(instance=self.local_user) + form.data["name"] = "New Name" + form.data["email"] = "wow@email.com" + form.data["preferred_timezone"] = "UTC" + image_file = pathlib.Path(__file__).parent.joinpath( + "../../static/images/no_cover.jpg" + ) + form.data["avatar"] = SimpleUploadedFile( + image_file, open(image_file, "rb").read(), content_type="image/jpeg" + ) + request = self.factory.post("", form.data) + request.user = self.local_user + + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch( + "bookwyrm.models.activitypub_mixin.broadcast_task.delay" + ) as delay_mock: + view(request) + self.assertEqual(delay_mock.call_count, 1) + self.assertEqual(self.local_user.name, "New Name") + self.assertEqual(self.local_user.email, "wow@email.com") + self.assertIsNotNone(self.local_user.avatar) + self.assertEqual(self.local_user.avatar.width, 120) + self.assertEqual(self.local_user.avatar.height, 120) + + def test_crop_avatar(self): + """reduce that image size""" + image_file = pathlib.Path(__file__).parent.joinpath( + "../../static/images/no_cover.jpg" + ) + image = Image.open(image_file) + + result = views.edit_user.crop_avatar(image) + self.assertIsInstance(result, ContentFile) + image_result = Image.open(result) + self.assertEqual(image_result.size, (120, 120)) + + def test_delete_user_page(self): + """there are so many views, this just makes sure it LOADS""" + view = views.DeleteUser.as_view() + request = self.factory.get("") + request.user = self.local_user + result = view(request) + self.assertIsInstance(result, TemplateResponse) + result.render() + self.assertEqual(result.status_code, 200) + + def test_delete_user(self): + """use a form to update a user""" + view = views.DeleteUser.as_view() + form = forms.DeleteUserForm() + form.data["password"] = "password" + request = self.factory.post("", form.data) + request.user = self.local_user + middleware = SessionMiddleware() + middleware.process_request(request) + request.session.save() + + self.assertIsNone(self.local_user.name) + with patch( + "bookwyrm.models.activitypub_mixin.broadcast_task.delay" + ) as delay_mock: + view(request) + self.assertEqual(delay_mock.call_count, 1) + activity = json.loads(delay_mock.call_args[0][1]) + self.assertEqual(activity["type"], "Delete") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual( + activity["cc"][0], "https://www.w3.org/ns/activitystreams#Public" + ) + + self.local_user.refresh_from_db() + self.assertFalse(self.local_user.is_active) + self.assertEqual(self.local_user.deactivation_reason, "self_deletion") diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 4469040ee..9ff122552 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -15,24 +15,26 @@ class FederationViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", ) - models.SiteSettings.objects.create() + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_federation_page(self): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_feed.py b/bookwyrm/tests/views/test_feed.py index a6a3d9677..81087cd1e 100644 --- a/bookwyrm/tests/views/test_feed.py +++ b/bookwyrm/tests/views/test_feed.py @@ -22,19 +22,22 @@ class FeedViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - self.book = models.Edition.objects.create( - parent_work=models.Work.objects.create(title="hi"), - title="Example Edition", - remote_id="https://example.com/book/1", - ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.book = models.Edition.objects.create( + parent_work=models.Work.objects.create(title="hi"), + title="Example Edition", + remote_id="https://example.com/book/1", + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_feed(self, *_): """there are so many views, this just makes sure it LOADS""" @@ -89,11 +92,14 @@ class FeedViews(TestCase): output = BytesIO() image.save(output, format=image.format) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - status = models.Review.objects.create( - content="hi", - user=self.local_user, - book=self.book, - ) + with patch( + "bookwyrm.preview_images.generate_edition_preview_image_task.delay" + ): + status = models.Review.objects.create( + content="hi", + user=self.local_user, + book=self.book, + ) attachment = models.Image.objects.create( status=status, caption="alt text here" ) @@ -146,12 +152,13 @@ class FeedViews(TestCase): def test_get_suggested_book(self, *_): """gets books the ~*~ algorithm ~*~ thinks you want to post about""" - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - models.ShelfBook.objects.create( - book=self.book, - user=self.local_user, - shelf=self.local_user.shelf_set.get(identifier="reading"), - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.ShelfBook.objects.create( + book=self.book, + user=self.local_user, + shelf=self.local_user.shelf_set.get(identifier="reading"), + ) suggestions = views.feed.get_suggested_books(self.local_user) self.assertEqual(suggestions[0]["name"], "Currently Reading") self.assertEqual(suggestions[0]["books"][0], self.book) diff --git a/bookwyrm/tests/views/test_follow.py b/bookwyrm/tests/views/test_follow.py index 45e60d3cb..f37370cc6 100644 --- a/bookwyrm/tests/views/test_follow.py +++ b/bookwyrm/tests/views/test_follow.py @@ -16,24 +16,25 @@ class BookViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - with patch("bookwyrm.models.user.set_remote_server"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@email.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) + with patch("bookwyrm.models.user.set_remote_server"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@email.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) self.group = Group.objects.create(name="editor") self.group.permissions.add( Permission.objects.create( @@ -42,12 +43,13 @@ class BookViews(TestCase): content_type=ContentType.objects.get_for_model(models.User), ).id ) - self.work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - parent_work=self.work, - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=self.work, + ) def test_handle_follow_remote(self): """send a follow request""" @@ -66,15 +68,16 @@ class BookViews(TestCase): def test_handle_follow_local_manually_approves(self): """send a follow request""" - rat = models.User.objects.create_user( - "rat@local.com", - "rat@rat.com", - "ratword", - local=True, - localname="rat", - remote_id="https://example.com/users/rat", - manually_approves_followers=True, - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + rat = models.User.objects.create_user( + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", + manually_approves_followers=True, + ) request = self.factory.post("", {"user": rat}) request.user = self.local_user self.assertEqual(models.UserFollowRequest.objects.count(), 0) @@ -89,14 +92,15 @@ class BookViews(TestCase): def test_handle_follow_local(self): """send a follow request""" - rat = models.User.objects.create_user( - "rat@local.com", - "rat@rat.com", - "ratword", - local=True, - localname="rat", - remote_id="https://example.com/users/rat", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + rat = models.User.objects.create_user( + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", + ) request = self.factory.post("", {"user": rat}) request.user = self.local_user self.assertEqual(models.UserFollowRequest.objects.count(), 0) diff --git a/bookwyrm/tests/views/test_get_started.py b/bookwyrm/tests/views/test_get_started.py index 1c55da086..7a3b0c444 100644 --- a/bookwyrm/tests/views/test_get_started.py +++ b/bookwyrm/tests/views/test_get_started.py @@ -13,22 +13,25 @@ class GetStartedViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - self.book = models.Edition.objects.create( - parent_work=models.Work.objects.create(title="hi"), - title="Example Edition", - remote_id="https://example.com/book/1", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.book = models.Edition.objects.create( + parent_work=models.Work.objects.create(title="hi"), + title="Example Edition", + remote_id="https://example.com/book/1", + ) models.Connector.objects.create( identifier="self", connector_file="self_connector", local=True ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_profile_view(self): """there are so many views, this just makes sure it LOADS""" @@ -52,11 +55,12 @@ class GetStartedViews(TestCase): request.user = self.local_user self.assertIsNone(self.local_user.name) - with patch( - "bookwyrm.models.activitypub_mixin.broadcast_task.delay" - ) as delay_mock: - view(request) - self.assertEqual(delay_mock.call_count, 1) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch( + "bookwyrm.models.activitypub_mixin.broadcast_task.delay" + ) as delay_mock: + view(request) + self.assertEqual(delay_mock.call_count, 1) self.assertEqual(self.local_user.name, "New Name") self.assertTrue(self.local_user.discoverable) diff --git a/bookwyrm/tests/views/test_goal.py b/bookwyrm/tests/views/test_goal.py index 4e8f6ee23..013d15950 100644 --- a/bookwyrm/tests/views/test_goal.py +++ b/bookwyrm/tests/views/test_goal.py @@ -16,29 +16,32 @@ class GoalViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - self.rat = models.User.objects.create_user( - "rat@local.com", - "rat@rat.com", - "ratword", - local=True, - localname="rat", - remote_id="https://example.com/users/rat", - ) - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + self.rat = models.User.objects.create_user( + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_goal_page_no_goal(self): """view a reading goal page for another's unset goal""" diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index ab5abed0b..e8d1895cf 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -19,31 +19,33 @@ class ViewsHelpers(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - discoverable=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - self.work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Test Book", - remote_id="https://example.com/book/1", - parent_work=self.work, - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, discoverable=True, - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + discoverable=True, + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Test Book", + remote_id="https://example.com/book/1", + parent_work=self.work, ) datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") self.userdata = json.loads(datafile.read_bytes()) @@ -142,10 +144,11 @@ class ViewsHelpers(TestCase): json=self.userdata, status=200, ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - result = views.helpers.handle_remote_webfinger("@mouse@example.com") - self.assertIsInstance(result, models.User) - self.assertEqual(result.username, "mouse@example.com") + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + with patch("bookwyrm.models.user.set_remote_server.delay"): + result = views.helpers.handle_remote_webfinger("@mouse@example.com") + self.assertIsInstance(result, models.User) + self.assertEqual(result.username, "mouse@example.com") def test_user_on_blocked_server(self, _): """find a remote user using webfinger""" @@ -202,21 +205,22 @@ class ViewsHelpers(TestCase): def test_get_annotated_users(self, _): """list of people you might know""" - user_1 = models.User.objects.create_user( - "nutria@local.com", - "nutria@nutria.com", - "nutriaword", - local=True, - localname="nutria", - discoverable=True, - ) - user_2 = models.User.objects.create_user( - "fish@local.com", - "fish@fish.com", - "fishword", - local=True, - localname="fish", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + user_1 = models.User.objects.create_user( + "nutria@local.com", + "nutria@nutria.com", + "nutriaword", + local=True, + localname="nutria", + discoverable=True, + ) + user_2 = models.User.objects.create_user( + "fish@local.com", + "fish@fish.com", + "fishword", + local=True, + localname="fish", + ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): # 1 shared follow self.local_user.following.add(user_2) @@ -249,39 +253,42 @@ class ViewsHelpers(TestCase): def test_get_annotated_users_counts(self, _): """correct counting for multiple shared attributed""" - user_1 = models.User.objects.create_user( - "nutria@local.com", - "nutria@nutria.com", - "nutriaword", - local=True, - localname="nutria", - discoverable=True, - ) - for i in range(3): - user = models.User.objects.create_user( - "{:d}@local.com".format(i), - "{:d}@nutria.com".format(i), - "password", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + user_1 = models.User.objects.create_user( + "nutria@local.com", + "nutria@nutria.com", + "nutriaword", local=True, - localname=i, + localname="nutria", + discoverable=True, ) - user.following.add(user_1) - user.followers.add(self.local_user) - - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): for i in range(3): - book = models.Edition.objects.create( - title=i, - parent_work=models.Work.objects.create(title=i), - ) - models.ShelfBook.objects.create( - user=self.local_user, - book=book, - shelf=self.local_user.shelf_set.first(), - ) - models.ShelfBook.objects.create( - user=user_1, book=book, shelf=user_1.shelf_set.first() + user = models.User.objects.create_user( + "{:d}@local.com".format(i), + "{:d}@nutria.com".format(i), + "password", + local=True, + localname=i, ) + user.following.add(user_1) + user.followers.add(self.local_user) + + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + for i in range(3): + book = models.Edition.objects.create( + title=i, + parent_work=models.Work.objects.create(title=i), + ) + models.ShelfBook.objects.create( + user=self.local_user, + book=book, + shelf=self.local_user.shelf_set.first(), + ) + models.ShelfBook.objects.create( + user=user_1, book=book, shelf=user_1.shelf_set.first() + ) result = views.helpers.get_annotated_users( self.local_user, diff --git a/bookwyrm/tests/views/test_import.py b/bookwyrm/tests/views/test_import.py index 22694623a..da8ade989 100644 --- a/bookwyrm/tests/views/test_import.py +++ b/bookwyrm/tests/views/test_import.py @@ -14,14 +14,16 @@ class ImportViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_import_page(self): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_interaction.py b/bookwyrm/tests/views/test_interaction.py index 876d6053c..1661676cb 100644 --- a/bookwyrm/tests/views/test_interaction.py +++ b/bookwyrm/tests/views/test_interaction.py @@ -8,39 +8,42 @@ from bookwyrm import models, views @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") +@patch("bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores") class InteractionViews(TestCase): """viewing and creating statuses""" def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - with patch("bookwyrm.models.user.set_remote_server"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@email.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + with patch("bookwyrm.models.user.set_remote_server"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@email.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, ) - work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - parent_work=work, - ) - - def test_favorite(self, _): + def test_favorite(self, *_): """create and broadcast faving a status""" view = views.Favorite.as_view() request = self.factory.post("") @@ -58,7 +61,7 @@ class InteractionViews(TestCase): self.assertEqual(notification.user, self.local_user) self.assertEqual(notification.related_user, self.remote_user) - def test_unfavorite(self, _): + def test_unfavorite(self, *_): """unfav a status""" view = views.Unfavorite.as_view() request = self.factory.post("") @@ -75,7 +78,7 @@ class InteractionViews(TestCase): self.assertEqual(models.Favorite.objects.count(), 0) self.assertEqual(models.Notification.objects.count(), 0) - def test_boost(self, _): + def test_boost(self, *_): """boost a status""" view = views.Boost.as_view() request = self.factory.post("") @@ -97,7 +100,7 @@ class InteractionViews(TestCase): self.assertEqual(notification.related_user, self.remote_user) self.assertEqual(notification.related_status, status) - def test_self_boost(self, _): + def test_self_boost(self, *_): """boost your own status""" view = views.Boost.as_view() request = self.factory.post("") @@ -121,7 +124,7 @@ class InteractionViews(TestCase): self.assertFalse(models.Notification.objects.exists()) - def test_boost_unlisted(self, _): + def test_boost_unlisted(self, *_): """boost a status""" view = views.Boost.as_view() request = self.factory.post("") @@ -136,7 +139,7 @@ class InteractionViews(TestCase): boost = models.Boost.objects.get() self.assertEqual(boost.privacy, "unlisted") - def test_boost_private(self, _): + def test_boost_private(self, *_): """boost a status""" view = views.Boost.as_view() request = self.factory.post("") @@ -149,7 +152,7 @@ class InteractionViews(TestCase): view(request, status.id) self.assertFalse(models.Boost.objects.exists()) - def test_boost_twice(self, _): + def test_boost_twice(self, *_): """boost a status""" view = views.Boost.as_view() request = self.factory.post("") @@ -161,13 +164,17 @@ class InteractionViews(TestCase): view(request, status.id) self.assertEqual(models.Boost.objects.count(), 1) - def test_unboost(self, _): + @patch("bookwyrm.activitystreams.ActivityStream.add_status") + def test_unboost(self, *_): """undo a boost""" view = views.Unboost.as_view() request = self.factory.post("") request.user = self.remote_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - status = models.Status.objects.create(user=self.local_user, content="hi") + status = models.Status.objects.create(user=self.local_user, content="hi") + + with patch( + "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" + ): views.Boost.as_view()(request, status.id) self.assertEqual(models.Boost.objects.count(), 1) diff --git a/bookwyrm/tests/views/test_invite.py b/bookwyrm/tests/views/test_invite.py index 7b5071b36..0743c65e1 100644 --- a/bookwyrm/tests/views/test_invite.py +++ b/bookwyrm/tests/views/test_invite.py @@ -16,14 +16,16 @@ class InviteViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_invite_page(self): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_isbn.py b/bookwyrm/tests/views/test_isbn.py index 2aedd3cea..f26a08e53 100644 --- a/bookwyrm/tests/views/test_isbn.py +++ b/bookwyrm/tests/views/test_isbn.py @@ -16,25 +16,28 @@ class IsbnViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - self.work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Test Book", - isbn_13="1234567890123", - remote_id="https://example.com/book/1", - parent_work=self.work, - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Test Book", + isbn_13="1234567890123", + remote_id="https://example.com/book/1", + parent_work=self.work, + ) models.Connector.objects.create( identifier="self", connector_file="self_connector", local=True ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_isbn_json_response(self): """searches local data only and returns book data in json format""" diff --git a/bookwyrm/tests/views/test_landing.py b/bookwyrm/tests/views/test_landing.py index 864e48f7f..7def39585 100644 --- a/bookwyrm/tests/views/test_landing.py +++ b/bookwyrm/tests/views/test_landing.py @@ -15,16 +15,18 @@ class LandingViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_home_page(self): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_list.py b/bookwyrm/tests/views/test_list.py index d6ad0e865..3783b9b69 100644 --- a/bookwyrm/tests/views/test_list.py +++ b/bookwyrm/tests/views/test_list.py @@ -17,46 +17,48 @@ class ListViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - self.rat = models.User.objects.create_user( - "rat@local.com", - "rat@rat.com", - "ratword", - local=True, - localname="rat", - remote_id="https://example.com/users/rat", - ) - work = models.Work.objects.create(title="Work") - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - parent_work=work, - ) - work_two = models.Work.objects.create(title="Labori") - self.book_two = models.Edition.objects.create( - title="Example Edition 2", - remote_id="https://example.com/book/2", - parent_work=work_two, - ) - work_three = models.Work.objects.create(title="Trabajar") - self.book_three = models.Edition.objects.create( - title="Example Edition 3", - remote_id="https://example.com/book/3", - parent_work=work_three, - ) - work_four = models.Work.objects.create(title="Travailler") - self.book_four = models.Edition.objects.create( - title="Example Edition 4", - remote_id="https://example.com/book/4", - parent_work=work_four, - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + self.rat = models.User.objects.create_user( + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="Work") + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, + ) + work_two = models.Work.objects.create(title="Labori") + self.book_two = models.Edition.objects.create( + title="Example Edition 2", + remote_id="https://example.com/book/2", + parent_work=work_two, + ) + work_three = models.Work.objects.create(title="Trabajar") + self.book_three = models.Edition.objects.create( + title="Example Edition 3", + remote_id="https://example.com/book/3", + parent_work=work_three, + ) + work_four = models.Work.objects.create(title="Travailler") + self.book_four = models.Edition.objects.create( + title="Example Edition 4", + remote_id="https://example.com/book/4", + parent_work=work_four, + ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): self.list = models.List.objects.create( @@ -64,7 +66,8 @@ class ListViews(TestCase): ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_lists_page(self): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_list_actions.py b/bookwyrm/tests/views/test_list_actions.py index 30c44a2cb..cbe00d27a 100644 --- a/bookwyrm/tests/views/test_list_actions.py +++ b/bookwyrm/tests/views/test_list_actions.py @@ -15,46 +15,49 @@ class ListActionViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - self.rat = models.User.objects.create_user( - "rat@local.com", - "rat@rat.com", - "ratword", - local=True, - localname="rat", - remote_id="https://example.com/users/rat", - ) - work = models.Work.objects.create(title="Work") - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - parent_work=work, - ) - work_two = models.Work.objects.create(title="Labori") - self.book_two = models.Edition.objects.create( - title="Example Edition 2", - remote_id="https://example.com/book/2", - parent_work=work_two, - ) - work_three = models.Work.objects.create(title="Trabajar") - self.book_three = models.Edition.objects.create( - title="Example Edition 3", - remote_id="https://example.com/book/3", - parent_work=work_three, - ) - work_four = models.Work.objects.create(title="Travailler") - self.book_four = models.Edition.objects.create( - title="Example Edition 4", - remote_id="https://example.com/book/4", - parent_work=work_four, - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + self.rat = models.User.objects.create_user( + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", + ) + + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="Work") + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, + ) + work_two = models.Work.objects.create(title="Labori") + self.book_two = models.Edition.objects.create( + title="Example Edition 2", + remote_id="https://example.com/book/2", + parent_work=work_two, + ) + work_three = models.Work.objects.create(title="Trabajar") + self.book_three = models.Edition.objects.create( + title="Example Edition 3", + remote_id="https://example.com/book/3", + parent_work=work_three, + ) + work_four = models.Work.objects.create(title="Travailler") + self.book_four = models.Edition.objects.create( + title="Example Edition 4", + remote_id="https://example.com/book/4", + parent_work=work_four, + ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): self.list = models.List.objects.create( @@ -62,7 +65,8 @@ class ListActionViews(TestCase): ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_curate_approve(self): """approve a pending item""" diff --git a/bookwyrm/tests/views/test_notifications.py b/bookwyrm/tests/views/test_notifications.py index 182753f91..1de196c56 100644 --- a/bookwyrm/tests/views/test_notifications.py +++ b/bookwyrm/tests/views/test_notifications.py @@ -2,6 +2,7 @@ from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory +from unittest.mock import patch from bookwyrm import models from bookwyrm import views @@ -13,14 +14,16 @@ class NotificationViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_notifications_page(self): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_outbox.py b/bookwyrm/tests/views/test_outbox.py index f89258e5f..8877a2441 100644 --- a/bookwyrm/tests/views/test_outbox.py +++ b/bookwyrm/tests/views/test_outbox.py @@ -18,20 +18,22 @@ class OutboxView(TestCase): def setUp(self): """we'll need some data""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - parent_work=work, - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, + ) def test_outbox(self, _): """returns user's statuses""" @@ -79,13 +81,16 @@ class OutboxView(TestCase): def test_outbox_filter(self, _): """if we only care about reviews, only get reviews""" with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - models.Review.objects.create( - content="look at this", - name="hi", - rating=1, - book=self.book, - user=self.local_user, - ) + with patch( + "bookwyrm.preview_images.generate_edition_preview_image_task.delay" + ): + models.Review.objects.create( + content="look at this", + name="hi", + rating=1, + book=self.book, + user=self.local_user, + ) models.Status.objects.create(content="look at this", user=self.local_user) request = self.factory.get("", {"type": "bleh"}) @@ -105,13 +110,16 @@ class OutboxView(TestCase): def test_outbox_bookwyrm_request_true(self, _): """should differentiate between bookwyrm and outside requests""" with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - models.Review.objects.create( - name="hi", - content="look at this", - user=self.local_user, - book=self.book, - privacy="public", - ) + with patch( + "bookwyrm.preview_images.generate_edition_preview_image_task.delay" + ): + models.Review.objects.create( + name="hi", + content="look at this", + user=self.local_user, + book=self.book, + privacy="public", + ) request = self.factory.get("", {"page": 1}, HTTP_USER_AGENT=USER_AGENT) result = views.Outbox.as_view()(request, "mouse") @@ -123,13 +131,16 @@ class OutboxView(TestCase): def test_outbox_bookwyrm_request_false(self, _): """should differentiate between bookwyrm and outside requests""" with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - models.Review.objects.create( - name="hi", - content="look at this", - user=self.local_user, - book=self.book, - privacy="public", - ) + with patch( + "bookwyrm.preview_images.generate_edition_preview_image_task.delay" + ): + models.Review.objects.create( + name="hi", + content="look at this", + user=self.local_user, + book=self.book, + privacy="public", + ) request = self.factory.get("", {"page": 1}) result = views.Outbox.as_view()(request, "mouse") diff --git a/bookwyrm/tests/views/test_password.py b/bookwyrm/tests/views/test_password.py index ec686db74..982056b5a 100644 --- a/bookwyrm/tests/views/test_password.py +++ b/bookwyrm/tests/views/test_password.py @@ -15,16 +15,18 @@ class PasswordViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "password", + local=True, + localname="mouse", + ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False - models.SiteSettings.objects.create(id=1) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create(id=1) def test_password_reset_request(self): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_reading.py b/bookwyrm/tests/views/test_reading.py index c591aa604..ec0d1339b 100644 --- a/bookwyrm/tests/views/test_reading.py +++ b/bookwyrm/tests/views/test_reading.py @@ -15,29 +15,31 @@ class ReadingViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - self.work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Test Book", - remote_id="https://example.com/book/1", - parent_work=self.work, - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Test Book", + remote_id="https://example.com/book/1", + parent_work=self.work, ) def test_start_reading(self, _): @@ -56,7 +58,7 @@ class ReadingViews(TestCase): ) request.user = self.local_user with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - views.start_reading(request, self.book.id) + views.ReadingStatus.as_view()(request, "start", self.book.id) self.assertEqual(shelf.books.get(), self.book) @@ -86,7 +88,7 @@ class ReadingViews(TestCase): request = self.factory.post("") request.user = self.local_user with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - views.start_reading(request, self.book.id) + views.ReadingStatus.as_view()(request, "start", self.book.id) self.assertFalse(to_read_shelf.books.exists()) self.assertEqual(shelf.books.get(), self.book) @@ -112,7 +114,7 @@ class ReadingViews(TestCase): request.user = self.local_user with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - views.finish_reading(request, self.book.id) + views.ReadingStatus.as_view()(request, "finish", self.book.id) self.assertEqual(shelf.books.get(), self.book) diff --git a/bookwyrm/tests/views/test_readthrough.py b/bookwyrm/tests/views/test_readthrough.py index 882c79291..ed4795700 100644 --- a/bookwyrm/tests/views/test_readthrough.py +++ b/bookwyrm/tests/views/test_readthrough.py @@ -15,15 +15,17 @@ class ReadThrough(TestCase): """basic user and book data""" self.client = Client() - self.work = models.Work.objects.create(title="Example Work") + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create(title="Example Work") - self.edition = models.Edition.objects.create( - title="Example Edition", parent_work=self.work - ) + self.edition = models.Edition.objects.create( + title="Example Edition", parent_work=self.work + ) - self.user = models.User.objects.create_user( - "cinco", "cinco@example.com", "seissiete", local=True, localname="cinco" - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.user = models.User.objects.create_user( + "cinco", "cinco@example.com", "seissiete", local=True, localname="cinco" + ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): self.client.force_login(self.user) @@ -33,7 +35,7 @@ class ReadThrough(TestCase): self.assertEqual(self.edition.readthrough_set.count(), 0) self.client.post( - "/start-reading/{}".format(self.edition.id), + "/reading-status/start/{}".format(self.edition.id), { "start_date": "2020-11-27", }, @@ -54,10 +56,9 @@ class ReadThrough(TestCase): self.assertEqual(self.edition.readthrough_set.count(), 0) self.client.post( - "/start-reading/{}".format(self.edition.id), + "/reading-status/start/{}".format(self.edition.id), { "start_date": "2020-11-27", - "progress": 50, }, ) @@ -66,15 +67,8 @@ class ReadThrough(TestCase): self.assertEqual( readthroughs[0].start_date, datetime(2020, 11, 27, tzinfo=timezone.utc) ) - self.assertEqual(readthroughs[0].progress, 50) self.assertEqual(readthroughs[0].finish_date, None) - progress_updates = readthroughs[0].progressupdate_set.all() - self.assertEqual(len(progress_updates), 1) - self.assertEqual(progress_updates[0].mode, models.ProgressMode.PAGE) - self.assertEqual(progress_updates[0].progress, 50) - self.assertEqual(delay_mock.call_count, 1) - # Update progress self.client.post( "/edit-readthrough", @@ -87,9 +81,9 @@ class ReadThrough(TestCase): progress_updates = ( readthroughs[0].progressupdate_set.order_by("updated_date").all() ) - self.assertEqual(len(progress_updates), 2) - self.assertEqual(progress_updates[1].mode, models.ProgressMode.PAGE) - self.assertEqual(progress_updates[1].progress, 100) + self.assertEqual(len(progress_updates), 1) + self.assertEqual(progress_updates[0].mode, models.ProgressMode.PAGE) + self.assertEqual(progress_updates[0].progress, 100) # Edit doesn't publish anything self.assertEqual(delay_mock.call_count, 1) diff --git a/bookwyrm/tests/views/test_reports.py b/bookwyrm/tests/views/test_reports.py index 84539489d..7944d8b45 100644 --- a/bookwyrm/tests/views/test_reports.py +++ b/bookwyrm/tests/views/test_reports.py @@ -2,6 +2,7 @@ from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory +from unittest.mock import patch from bookwyrm import forms, models, views @@ -12,21 +13,23 @@ class ReportViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - self.rat = models.User.objects.create_user( - "rat@local.com", - "rat@mouse.mouse", - "password", - local=True, - localname="rat", - ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + self.rat = models.User.objects.create_user( + "rat@local.com", + "rat@mouse.mouse", + "password", + local=True, + localname="rat", + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_reports_page(self): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_rss_feed.py b/bookwyrm/tests/views/test_rss_feed.py index eacb3c936..57961a316 100644 --- a/bookwyrm/tests/views/test_rss_feed.py +++ b/bookwyrm/tests/views/test_rss_feed.py @@ -12,39 +12,45 @@ class RssFeedView(TestCase): def setUp(self): """test data""" - self.site = models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + self.site = models.SiteSettings.objects.create() - self.user = models.User.objects.create_user( - "rss_user", "rss@test.rss", "password", local=True - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.user = models.User.objects.create_user( + "rss_user", "rss@test.rss", "password", local=True + ) - work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - parent_work=work, - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, + ) - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - self.review = models.Review.objects.create( - name="Review name", - content="test content", - rating=3, - user=self.user, - book=self.book, - ) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch( + "bookwyrm.preview_images.generate_edition_preview_image_task.delay" + ): + self.review = models.Review.objects.create( + name="Review name", + content="test content", + rating=3, + user=self.user, + book=self.book, + ) - self.quote = models.Quotation.objects.create( - quote="a sickening sense", - content="test content", - user=self.user, - book=self.book, - ) + self.quote = models.Quotation.objects.create( + quote="a sickening sense", + content="test content", + user=self.user, + book=self.book, + ) - self.generatednote = models.GeneratedNote.objects.create( - content="test content", user=self.user - ) + self.generatednote = models.GeneratedNote.objects.create( + content="test content", user=self.user + ) self.factory = RequestFactory() diff --git a/bookwyrm/tests/views/test_search.py b/bookwyrm/tests/views/test_search.py index ab2c910d4..56413de63 100644 --- a/bookwyrm/tests/views/test_search.py +++ b/bookwyrm/tests/views/test_search.py @@ -19,24 +19,27 @@ class Views(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - self.work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Test Book", - remote_id="https://example.com/book/1", - parent_work=self.work, - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Test Book", + remote_id="https://example.com/book/1", + parent_work=self.work, + ) models.Connector.objects.create( identifier="self", connector_file="self_connector", local=True ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_search_json_response(self): """searches local data only and returns book data in json format""" diff --git a/bookwyrm/tests/views/test_shelf.py b/bookwyrm/tests/views/test_shelf.py index 239b3318f..21a305444 100644 --- a/bookwyrm/tests/views/test_shelf.py +++ b/bookwyrm/tests/views/test_shelf.py @@ -16,25 +16,28 @@ class ShelfViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - self.work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - parent_work=self.work, - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=self.work, + ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): self.shelf = models.Shelf.objects.create( name="Test Shelf", identifier="test-shelf", user=self.local_user ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_shelf_page(self, _): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_status.py b/bookwyrm/tests/views/test_status.py index 6f2fd30d4..9253e6e57 100644 --- a/bookwyrm/tests/views/test_status.py +++ b/bookwyrm/tests/views/test_status.py @@ -15,32 +15,35 @@ class StatusViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - with patch("bookwyrm.models.user.set_remote_server"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@email.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) + with patch("bookwyrm.models.user.set_remote_server"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@email.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) - work = models.Work.objects.create(title="Test Work") - self.book = models.Edition.objects.create( - title="Example Edition", - remote_id="https://example.com/book/1", - parent_work=work, - ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_handle_status(self, _): """create a status""" @@ -68,9 +71,10 @@ class StatusViews(TestCase): def test_handle_status_reply(self, _): """create a status in reply to an existing status""" view = views.CreateStatus.as_view() - user = models.User.objects.create_user( - "rat", "rat@rat.com", "password", local=True - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + user = models.User.objects.create_user( + "rat", "rat@rat.com", "password", local=True + ) with patch("bookwyrm.activitystreams.ActivityStream.add_status"): parent = models.Status.objects.create( content="parent status", user=self.local_user @@ -98,9 +102,14 @@ class StatusViews(TestCase): def test_handle_status_mentions(self, _): """@mention a user in a post""" view = views.CreateStatus.as_view() - user = models.User.objects.create_user( - "rat@%s" % DOMAIN, "rat@rat.com", "password", local=True, localname="rat" - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + user = models.User.objects.create_user( + "rat@%s" % DOMAIN, + "rat@rat.com", + "password", + local=True, + localname="rat", + ) form = forms.CommentForm( { "content": "hi @rat", @@ -126,9 +135,10 @@ class StatusViews(TestCase): def test_handle_status_reply_with_mentions(self, _): """reply to a post with an @mention'ed user""" view = views.CreateStatus.as_view() - user = models.User.objects.create_user( - "rat", "rat@rat.com", "password", local=True, localname="rat" - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + user = models.User.objects.create_user( + "rat", "rat@rat.com", "password", local=True, localname="rat" + ) form = forms.CommentForm( { "content": "hi @rat@example.com", @@ -193,10 +203,11 @@ class StatusViews(TestCase): view = views.DeleteAndRedraft.as_view() request = self.factory.post("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - status = models.ReviewRating.objects.create( - book=self.book, rating=2.0, user=self.local_user - ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + status = models.ReviewRating.objects.create( + book=self.book, rating=2.0, user=self.local_user + ) with patch( "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" @@ -230,13 +241,14 @@ class StatusViews(TestCase): def test_find_mentions(self, _): """detect and look up @ mentions of users""" - user = models.User.objects.create_user( - "nutria@%s" % DOMAIN, - "nutria@nutria.com", - "password", - local=True, - localname="nutria", - ) + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + user = models.User.objects.create_user( + "nutria@%s" % DOMAIN, + "nutria@nutria.com", + "password", + local=True, + localname="nutria", + ) self.assertEqual(user.username, "nutria@%s" % DOMAIN) self.assertEqual( diff --git a/bookwyrm/tests/views/test_updates.py b/bookwyrm/tests/views/test_updates.py index fb003f8de..07800e2f7 100644 --- a/bookwyrm/tests/views/test_updates.py +++ b/bookwyrm/tests/views/test_updates.py @@ -14,15 +14,17 @@ class UpdateViews(TestCase): def setUp(self): """we need basic test data and mocks""" - self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.factory = RequestFactory() + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_get_notification_count(self): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_user.py b/bookwyrm/tests/views/test_user.py index 3fa78e355..3e7a5108d 100644 --- a/bookwyrm/tests/views/test_user.py +++ b/bookwyrm/tests/views/test_user.py @@ -1,17 +1,13 @@ """ test for app action functionality """ -import pathlib from unittest.mock import patch -from PIL import Image from django.contrib.auth.models import AnonymousUser -from django.core.files.base import ContentFile -from django.core.files.uploadedfile import SimpleUploadedFile from django.http.response import Http404 from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory -from bookwyrm import forms, models, views +from bookwyrm import models, views from bookwyrm.activitypub import ActivitypubResponse @@ -20,18 +16,20 @@ class UserViews(TestCase): def setUp(self): """we need basic test data and mocks""" - self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - self.rat = models.User.objects.create_user( - "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" - ) - self.book = models.Edition.objects.create(title="test") + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.factory = RequestFactory() + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + self.rat = models.User.objects.create_user( + "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" + ) + with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"): + self.book = models.Edition.objects.create(title="test") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.ShelfBook.objects.create( book=self.book, @@ -39,7 +37,8 @@ class UserViews(TestCase): shelf=self.local_user.shelf_set.first(), ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False @@ -137,71 +136,3 @@ class UserViews(TestCase): is_api.return_value = False with self.assertRaises(Http404): view(request, "rat") - - def test_edit_user_page(self): - """there are so many views, this just makes sure it LOADS""" - view = views.EditUser.as_view() - request = self.factory.get("") - request.user = self.local_user - result = view(request) - self.assertIsInstance(result, TemplateResponse) - result.render() - self.assertEqual(result.status_code, 200) - - def test_edit_user(self): - """use a form to update a user""" - view = views.EditUser.as_view() - form = forms.EditUserForm(instance=self.local_user) - form.data["name"] = "New Name" - form.data["email"] = "wow@email.com" - form.data["preferred_timezone"] = "UTC" - request = self.factory.post("", form.data) - request.user = self.local_user - - self.assertIsNone(self.local_user.name) - with patch( - "bookwyrm.models.activitypub_mixin.broadcast_task.delay" - ) as delay_mock: - view(request) - self.assertEqual(delay_mock.call_count, 1) - self.assertEqual(self.local_user.name, "New Name") - self.assertEqual(self.local_user.email, "wow@email.com") - - def test_edit_user_avatar(self): - """use a form to update a user""" - view = views.EditUser.as_view() - form = forms.EditUserForm(instance=self.local_user) - form.data["name"] = "New Name" - form.data["email"] = "wow@email.com" - form.data["preferred_timezone"] = "UTC" - image_file = pathlib.Path(__file__).parent.joinpath( - "../../static/images/no_cover.jpg" - ) - form.data["avatar"] = SimpleUploadedFile( - image_file, open(image_file, "rb").read(), content_type="image/jpeg" - ) - request = self.factory.post("", form.data) - request.user = self.local_user - - with patch( - "bookwyrm.models.activitypub_mixin.broadcast_task.delay" - ) as delay_mock: - view(request) - self.assertEqual(delay_mock.call_count, 1) - self.assertEqual(self.local_user.name, "New Name") - self.assertEqual(self.local_user.email, "wow@email.com") - self.assertIsNotNone(self.local_user.avatar) - self.assertEqual(self.local_user.avatar.width, 120) - self.assertEqual(self.local_user.avatar.height, 120) - - def test_crop_avatar(self): - """reduce that image size""" - image_file = pathlib.Path(__file__).parent.joinpath( - "../../static/images/no_cover.jpg" - ) - image = Image.open(image_file) - - result = views.user.crop_avatar(image) - self.assertIsInstance(result, ContentFile) - image_result = Image.open(result) - self.assertEqual(image_result.size, (120, 120)) diff --git a/bookwyrm/tests/views/test_user_admin.py b/bookwyrm/tests/views/test_user_admin.py index a044a22c5..ac750be00 100644 --- a/bookwyrm/tests/views/test_user_admin.py +++ b/bookwyrm/tests/views/test_user_admin.py @@ -14,14 +14,16 @@ class UserAdminViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - models.SiteSettings.objects.create() + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() def test_user_admin_list_page(self): """there are so many views, this just makes sure it LOADS""" diff --git a/bookwyrm/tests/views/test_wellknown.py b/bookwyrm/tests/views/test_wellknown.py index 244921d47..839240ed6 100644 --- a/bookwyrm/tests/views/test_wellknown.py +++ b/bookwyrm/tests/views/test_wellknown.py @@ -15,28 +15,30 @@ class UserViews(TestCase): def setUp(self): """we need basic test data and mocks""" - self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - models.User.objects.create_user( - "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - models.User.objects.create_user( - "rat", - "rat@remote.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", + with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"): + self.factory = RequestFactory() + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", ) - models.SiteSettings.objects.create() + models.User.objects.create_user( + "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + models.User.objects.create_user( + "rat", + "rat@remote.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"): + models.SiteSettings.objects.create() self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False diff --git a/bookwyrm/timezone_middleware.py b/bookwyrm/timezone_middleware.py index 633d6835d..5033397a5 100644 --- a/bookwyrm/timezone_middleware.py +++ b/bookwyrm/timezone_middleware.py @@ -1,9 +1,12 @@ +""" Makes the app aware of the users timezone """ import pytz from django.utils import timezone class TimezoneMiddleware: + """Determine the timezone based on the request""" + def __init__(self, get_response): self.get_response = get_response diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index cca9396f7..35540358c 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -7,8 +7,8 @@ from django.views.generic.base import TemplateView from bookwyrm import settings, views from bookwyrm.utils import regex -user_path = r"^user/(?P%s)" % regex.username -local_user_path = r"^user/(?P%s)" % regex.localname +USER_PATH = r"^user/(?P%s)" % regex.USERNAME +LOCAL_USER_PATH = r"^user/(?P%s)" % regex.LOCALNAME status_types = [ "status", @@ -19,9 +19,9 @@ status_types = [ "boost", "generatednote", ] -status_path = r"%s/(%s)/(?P\d+)" % (user_path, "|".join(status_types)) +STATUS_PATH = r"%s/(%s)/(?P\d+)" % (USER_PATH, "|".join(status_types)) -book_path = r"^book/(?P\d+)" +BOOK_PATH = r"^book/(?P\d+)" urlpatterns = [ path("admin/", admin.site.urls), @@ -31,8 +31,8 @@ urlpatterns = [ ), # federation endpoints re_path(r"^inbox/?$", views.Inbox.as_view()), - re_path(r"%s/inbox/?$" % local_user_path, views.Inbox.as_view()), - re_path(r"%s/outbox/?$" % local_user_path, views.Outbox.as_view()), + re_path(r"%s/inbox/?$" % LOCAL_USER_PATH, views.Inbox.as_view()), + re_path(r"%s/outbox/?$" % LOCAL_USER_PATH, views.Outbox.as_view()), re_path(r"^\.well-known/webfinger/?$", views.webfinger), re_path(r"^\.well-known/nodeinfo/?$", views.nodeinfo_pointer), re_path(r"^\.well-known/host-meta/?$", views.host_meta), @@ -182,7 +182,7 @@ urlpatterns = [ r"^direct-messages/?$", views.DirectMessage.as_view(), name="direct-messages" ), re_path( - r"^direct-messages/(?P%s)?$" % regex.username, + r"^direct-messages/(?P%s)?$" % regex.USERNAME, views.DirectMessage.as_view(), name="direct-messages-user", ), @@ -192,21 +192,21 @@ urlpatterns = [ re_path(r"^import/?$", views.Import.as_view(), name="import"), re_path(r"^import/(\d+)/?$", views.ImportStatus.as_view(), name="import-status"), # users - re_path(r"%s/?$" % user_path, views.User.as_view(), name="user-feed"), - re_path(r"%s\.json$" % user_path, views.User.as_view()), - re_path(r"%s/rss" % user_path, views.rss_feed.RssFeed(), name="user-rss"), + re_path(r"%s/?$" % USER_PATH, views.User.as_view(), name="user-feed"), + re_path(r"%s\.json$" % USER_PATH, views.User.as_view()), + re_path(r"%s/rss" % USER_PATH, views.rss_feed.RssFeed(), name="user-rss"), re_path( - r"%s/followers(.json)?/?$" % user_path, + r"%s/followers(.json)?/?$" % USER_PATH, views.Followers.as_view(), name="user-followers", ), re_path( - r"%s/following(.json)?/?$" % user_path, + r"%s/following(.json)?/?$" % USER_PATH, views.Following.as_view(), name="user-following", ), # lists - re_path(r"%s/lists/?$" % user_path, views.UserLists.as_view(), name="user-lists"), + re_path(r"%s/lists/?$" % USER_PATH, views.UserLists.as_view(), name="user-lists"), re_path(r"^list/?$", views.Lists.as_view(), name="lists"), re_path(r"^list/(?P\d+)(.json)?/?$", views.List.as_view(), name="list"), re_path(r"^list/add-book/?$", views.list.add_book, name="list-add-book"), @@ -223,15 +223,15 @@ urlpatterns = [ re_path( r"^list/(?P\d+)/curate/?$", views.Curate.as_view(), name="list-curate" ), - # Uyser books - re_path(r"%s/books/?$" % user_path, views.Shelf.as_view(), name="user-shelves"), + # User books + re_path(r"%s/books/?$" % USER_PATH, views.Shelf.as_view(), name="user-shelves"), re_path( - r"^%s/(helf|books)/(?P[\w-]+)(.json)?/?$" % user_path, + r"^%s/(helf|books)/(?P[\w-]+)(.json)?/?$" % USER_PATH, views.Shelf.as_view(), name="shelf", ), re_path( - r"^%s/(books|shelf)/(?P[\w-]+)(.json)?/?$" % local_user_path, + r"^%s/(books|shelf)/(?P[\w-]+)(.json)?/?$" % LOCAL_USER_PATH, views.Shelf.as_view(), name="shelf", ), @@ -241,7 +241,7 @@ urlpatterns = [ re_path(r"^unshelve/?$", views.unshelve), # goals re_path( - r"%s/goal/(?P\d{4})/?$" % user_path, + r"%s/goal/(?P\d{4})/?$" % USER_PATH, views.Goal.as_view(), name="user-goal", ), @@ -253,14 +253,15 @@ urlpatterns = [ views.ChangePassword.as_view(), name="prefs-password", ), + re_path(r"^preferences/delete/?$", views.DeleteUser.as_view(), name="prefs-delete"), re_path(r"^preferences/block/?$", views.Block.as_view(), name="prefs-block"), re_path(r"^block/(?P\d+)/?$", views.Block.as_view()), re_path(r"^unblock/(?P\d+)/?$", views.unblock), # statuses - re_path(r"%s(.json)?/?$" % status_path, views.Status.as_view(), name="status"), - re_path(r"%s/activity/?$" % status_path, views.Status.as_view(), name="status"), + re_path(r"%s(.json)?/?$" % STATUS_PATH, views.Status.as_view(), name="status"), + re_path(r"%s/activity/?$" % STATUS_PATH, views.Status.as_view(), name="status"), re_path( - r"%s/replies(.json)?/?$" % status_path, views.Replies.as_view(), name="replies" + r"%s/replies(.json)?/?$" % STATUS_PATH, views.Replies.as_view(), name="replies" ), re_path( r"^post/?$", @@ -288,17 +289,17 @@ urlpatterns = [ re_path(r"^boost/(?P\d+)/?$", views.Boost.as_view()), re_path(r"^unboost/(?P\d+)/?$", views.Unboost.as_view()), # books - re_path(r"%s(.json)?/?$" % book_path, views.Book.as_view(), name="book"), + re_path(r"%s(.json)?/?$" % BOOK_PATH, views.Book.as_view(), name="book"), re_path( - r"%s/(?Preview|comment|quote)/?$" % book_path, + r"%s/(?Preview|comment|quote)/?$" % BOOK_PATH, views.Book.as_view(), name="book-user-statuses", ), - re_path(r"%s/edit/?$" % book_path, views.EditBook.as_view()), - re_path(r"%s/confirm/?$" % book_path, views.ConfirmEditBook.as_view()), + re_path(r"%s/edit/?$" % BOOK_PATH, views.EditBook.as_view()), + re_path(r"%s/confirm/?$" % BOOK_PATH, views.ConfirmEditBook.as_view()), re_path(r"^create-book/?$", views.EditBook.as_view(), name="create-book"), re_path(r"^create-book/confirm?$", views.ConfirmEditBook.as_view()), - re_path(r"%s/editions(.json)?/?$" % book_path, views.Editions.as_view()), + re_path(r"%s/editions(.json)?/?$" % BOOK_PATH, views.Editions.as_view()), re_path( r"^upload-cover/(?P\d+)/?$", views.upload_cover, name="upload-cover" ), @@ -315,8 +316,12 @@ urlpatterns = [ re_path(r"^delete-readthrough/?$", views.delete_readthrough), re_path(r"^create-readthrough/?$", views.create_readthrough), re_path(r"^delete-progressupdate/?$", views.delete_progressupdate), - re_path(r"^start-reading/(?P\d+)/?$", views.start_reading), - re_path(r"^finish-reading/(?P\d+)/?$", views.finish_reading), + # shelve actions + re_path( + r"^reading-status/(?Pwant|start|finish)/(?P\d+)/?$", + views.ReadingStatus.as_view(), + name="reading-status", + ), # following re_path(r"^follow/?$", views.follow, name="follow"), re_path(r"^unfollow/?$", views.unfollow, name="unfollow"), diff --git a/bookwyrm/utils/__init__.py b/bookwyrm/utils/__init__.py index a90554c70..f15f59aaf 100644 --- a/bookwyrm/utils/__init__.py +++ b/bookwyrm/utils/__init__.py @@ -1 +1,2 @@ -from .regex import username +""" useful regex """ +from .regex import USERNAME diff --git a/bookwyrm/utils/regex.py b/bookwyrm/utils/regex.py index 6389c35d6..3ac5a0ffd 100644 --- a/bookwyrm/utils/regex.py +++ b/bookwyrm/utils/regex.py @@ -1,10 +1,10 @@ """ defining regexes for regularly used concepts """ -domain = r"[\w_\-\.]+\.[a-z]{2,}" -localname = r"@?[a-zA-Z_\-\.0-9]+" -strict_localname = r"@[a-zA-Z_\-\.0-9]+" -username = r"%s(@%s)?" % (localname, domain) -strict_username = r"\B%s(@%s)?\b" % (strict_localname, domain) -full_username = r"%s@%s\b" % (localname, domain) +DOMAIN = r"[\w_\-\.]+\.[a-z]{2,}" +LOCALNAME = r"@?[a-zA-Z_\-\.0-9]+" +STRICT_LOCALNAME = r"@[a-zA-Z_\-\.0-9]+" +USERNAME = r"%s(@%s)?" % (LOCALNAME, DOMAIN) +STRICT_USERNAME = r"\B%s(@%s)?\b" % (STRICT_LOCALNAME, DOMAIN) +FULL_USERNAME = r"%s@%s\b" % (LOCALNAME, DOMAIN) # should match (BookWyrm/1.0.0; or (BookWyrm/99.1.2; -bookwyrm_user_agent = r"\(BookWyrm/[0-9]+\.[0-9]+\.[0-9]+;" +BOOKWYRM_USER_AGENT = r"\(BookWyrm/[0-9]+\.[0-9]+\.[0-9]+;" diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 8bfaa7dac..41bb64e54 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -6,6 +6,7 @@ from .block import Block, unblock from .books import Book, EditBook, ConfirmEditBook, Editions from .books import upload_cover, add_description, switch_edition, resolve_book from .directory import Directory +from .edit_user import EditUser, DeleteUser from .federation import Federation, FederatedServer from .federation import AddFederatedServer, ImportServerBlocklist from .federation import block_server, unblock_server @@ -24,8 +25,9 @@ from .landing import About, Home, Discover from .list import Lists, List, Curate, UserLists from .notifications import Notifications from .outbox import Outbox -from .reading import edit_readthrough, create_readthrough, delete_readthrough -from .reading import start_reading, finish_reading, delete_progressupdate +from .reading import edit_readthrough, create_readthrough +from .reading import delete_readthrough, delete_progressupdate +from .reading import ReadingStatus from .reports import Report, Reports, make_report, resolve_report, suspend_user from .rss_feed import RssFeed from .password import PasswordResetRequest, PasswordReset, ChangePassword @@ -36,6 +38,6 @@ from .shelf import shelve, unshelve from .site import Site from .status import CreateStatus, DeleteStatus, DeleteAndRedraft from .updates import get_notification_count, get_unread_status_count -from .user import User, EditUser, Followers, Following +from .user import User, Followers, Following from .user_admin import UserAdmin, UserAdminList from .wellknown import * diff --git a/bookwyrm/views/announcements.py b/bookwyrm/views/announcements.py index 6cd6ecd8f..e522fc7f6 100644 --- a/bookwyrm/views/announcements.py +++ b/bookwyrm/views/announcements.py @@ -81,6 +81,7 @@ class Announcement(View): form = forms.AnnouncementForm(request.POST, instance=announcement) if form.is_valid(): announcement = form.save() + form = forms.AnnouncementForm(instance=announcement) data = { "announcement": announcement, "form": form, diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index ef3a6e28c..a4b9f312a 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -62,13 +62,16 @@ class Book(View): queryset = queryset.filter(user=request.user) else: queryset = reviews.exclude(Q(content__isnull=True) | Q(content="")) + queryset = queryset.select_related("user") paginated = Paginator(queryset, PAGE_LENGTH) data = { "book": book, "statuses": paginated.get_page(request.GET.get("page")), "review_count": reviews.count(), - "ratings": reviews.filter(Q(content__isnull=True) | Q(content="")) + "ratings": reviews.filter( + Q(content__isnull=True) | Q(content="") + ).select_related("user") if not user_statuses else None, "rating": reviews.aggregate(Avg("rating"))["rating__avg"], @@ -89,15 +92,15 @@ class Book(View): ) data["readthroughs"] = readthroughs - data["user_shelves"] = models.ShelfBook.objects.filter( + data["user_shelfbooks"] = models.ShelfBook.objects.filter( user=request.user, book=book - ) + ).select_related("shelf") data["other_edition_shelves"] = models.ShelfBook.objects.filter( ~Q(book=book), user=request.user, book__parent_work=book.parent_work, - ) + ).select_related("shelf", "book") data["user_statuses"] = { "review_count": book.review_set.filter(user=request.user).count(), diff --git a/bookwyrm/views/edit_user.py b/bookwyrm/views/edit_user.py new file mode 100644 index 000000000..c74b00c9d --- /dev/null +++ b/bookwyrm/views/edit_user.py @@ -0,0 +1,113 @@ +""" edit or delete ones own account""" +from io import BytesIO +from uuid import uuid4 +from PIL import Image + +from django.contrib.auth import logout +from django.contrib.auth.decorators import login_required +from django.core.files.base import ContentFile +from django.shortcuts import redirect +from django.template.response import TemplateResponse +from django.utils.decorators import method_decorator +from django.views import View + +from bookwyrm import forms, models + + +# pylint: disable=no-self-use +@method_decorator(login_required, name="dispatch") +class EditUser(View): + """edit user view""" + + def get(self, request): + """edit profile page for a user""" + data = { + "form": forms.EditUserForm(instance=request.user), + "user": request.user, + } + return TemplateResponse(request, "preferences/edit_user.html", data) + + def post(self, request): + """les get fancy with images""" + form = forms.EditUserForm(request.POST, request.FILES, instance=request.user) + if not form.is_valid(): + data = {"form": form, "user": request.user} + return TemplateResponse(request, "preferences/edit_user.html", data) + + user = save_user_form(form) + + return redirect(user.local_path) + + +# pylint: disable=no-self-use +@method_decorator(login_required, name="dispatch") +class DeleteUser(View): + """delete user view""" + + def get(self, request): + """delete page for a user""" + data = { + "form": forms.DeleteUserForm(), + "user": request.user, + } + return TemplateResponse(request, "preferences/delete_user.html", data) + + def post(self, request): + """les get fancy with images""" + form = forms.DeleteUserForm(request.POST, instance=request.user) + form.is_valid() + # idk why but I couldn't get check_password to work on request.user + user = models.User.objects.get(id=request.user.id) + if form.is_valid() and user.check_password(form.cleaned_data["password"]): + user.deactivation_reason = "self_deletion" + user.delete() + logout(request) + return redirect("/") + + form.errors["password"] = ["Invalid password"] + data = {"form": form, "user": request.user} + return TemplateResponse(request, "preferences/delete_user.html", data) + + +def save_user_form(form): + """special handling for the user form""" + user = form.save(commit=False) + + if "avatar" in form.files: + # crop and resize avatar upload + image = Image.open(form.files["avatar"]) + image = crop_avatar(image) + + # set the name to a hash + extension = form.files["avatar"].name.split(".")[-1] + filename = "%s.%s" % (uuid4(), extension) + user.avatar.save(filename, image, save=False) + user.save() + return user + + +def crop_avatar(image): + """reduce the size and make an avatar square""" + target_size = 120 + width, height = image.size + thumbnail_scale = ( + height / (width / target_size) + if height > width + else width / (height / target_size) + ) + image.thumbnail([thumbnail_scale, thumbnail_scale]) + width, height = image.size + + width_diff = width - target_size + height_diff = height - target_size + cropped = image.crop( + ( + int(width_diff / 2), + int(height_diff / 2), + int(width - (width_diff / 2)), + int(height - (height_diff / 2)), + ) + ) + output = BytesIO() + cropped.save(output, format=image.format) + return ContentFile(output.getvalue()) diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index 4d0f11167..3001c86e2 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -163,14 +163,15 @@ def get_suggested_books(user, max_books=5): else max_books - book_count ) shelf = user.shelf_set.get(identifier=preset) - - shelf_books = shelf.shelfbook_set.order_by("-updated_date")[:limit] - if not shelf_books: + if not shelf.books.exists(): continue + shelf_preview = { "name": shelf.name, "identifier": shelf.identifier, - "books": [s.book for s in shelf_books], + "books": shelf.books.order_by("shelfbook").prefetch_related("authors")[ + :limit + ], } suggested_books.append(shelf_preview) book_count += len(shelf_preview["books"]) diff --git a/bookwyrm/views/get_started.py b/bookwyrm/views/get_started.py index eeef1d9e2..3de88e104 100644 --- a/bookwyrm/views/get_started.py +++ b/bookwyrm/views/get_started.py @@ -14,7 +14,7 @@ from django.views import View from bookwyrm import forms, models from bookwyrm.connectors import connector_manager from bookwyrm.suggested_users import suggested_users -from .user import save_user_form +from .edit_user import save_user_form # pylint: disable= no-self-use diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 169e59b41..371bde8ab 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -13,6 +13,10 @@ from bookwyrm.utils import regex def get_user_from_username(viewer, username): """helper function to resolve a localname or a username to a user""" + if viewer.is_authenticated and viewer.localname == username: + # that's yourself, fool + return viewer + # raises 404 if the user isn't found try: return models.User.viewer_aware_objects(viewer).get(localname=username) @@ -34,7 +38,7 @@ def is_api_request(request): def is_bookwyrm_request(request): """check if the request is coming from another bookwyrm instance""" user_agent = request.headers.get("User-Agent") - if user_agent is None or re.search(regex.bookwyrm_user_agent, user_agent) is None: + if user_agent is None or re.search(regex.BOOKWYRM_USER_AGENT, user_agent) is None: return False return True diff --git a/bookwyrm/views/import_data.py b/bookwyrm/views/import_data.py index 910283c42..b2b88a2eb 100644 --- a/bookwyrm/views/import_data.py +++ b/bookwyrm/views/import_data.py @@ -78,10 +78,15 @@ class ImportStatus(View): def get(self, request, job_id): """status of an import job""" - job = models.ImportJob.objects.get(id=job_id) + job = get_object_or_404(models.ImportJob, id=job_id) if job.user != request.user: raise PermissionDenied - task = app.AsyncResult(job.task_id) + + try: + task = app.AsyncResult(job.task_id) + except ValueError: + task = None + items = job.items.order_by("index").all() failed_items = [i for i in items if i.fail_reason] items = [i for i in items if not i.fail_reason] diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index a558c571e..ff5fa46da 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -21,6 +21,7 @@ from bookwyrm.utils import regex class Inbox(View): """requests sent by outside servers""" + # pylint: disable=too-many-return-statements def post(self, request, username=None): """only works as POST request""" # first check if this server is on our shitlist @@ -70,7 +71,7 @@ def is_blocked_user_agent(request): user_agent = request.headers.get("User-Agent") if not user_agent: return False - url = re.search(r"https?://{:s}/?".format(regex.domain), user_agent) + url = re.search(r"https?://{:s}/?".format(regex.DOMAIN), user_agent) if not url: return False url = url.group() diff --git a/bookwyrm/views/invite.py b/bookwyrm/views/invite.py index 92f930f45..3b9fd17c5 100644 --- a/bookwyrm/views/invite.py +++ b/bookwyrm/views/invite.py @@ -37,8 +37,12 @@ class ManageInvites(View): PAGE_LENGTH, ) + page = paginated.get_page(request.GET.get("page")) data = { - "invites": paginated.get_page(request.GET.get("page")), + "invites": page, + "page_range": paginated.get_elided_page_range( + page.number, on_each_side=2, on_ends=1 + ), "form": forms.CreateInviteForm(), } return TemplateResponse(request, "settings/manage_invites.html", data) @@ -118,15 +122,16 @@ class ManageInviteRequests(View): reduce(operator.or_, (Q(**f) for f in filters)) ).distinct() - paginated = Paginator( - requests, - PAGE_LENGTH, - ) + paginated = Paginator(requests, PAGE_LENGTH) + page = paginated.get_page(request.GET.get("page")) data = { "ignored": ignored, "count": paginated.count, - "requests": paginated.get_page(request.GET.get("page")), + "requests": page, + "page_range": paginated.get_elided_page_range( + page.number, on_each_side=2, on_ends=1 + ), "sort": sort, } return TemplateResponse(request, "settings/manage_invite_requests.html", data) diff --git a/bookwyrm/views/isbn.py b/bookwyrm/views/isbn.py index 197088bab..12208a3d7 100644 --- a/bookwyrm/views/isbn.py +++ b/bookwyrm/views/isbn.py @@ -1,13 +1,8 @@ """ isbn search view """ -from django.http import HttpResponseNotFound from django.http import JsonResponse -from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse -from django.utils.decorators import method_decorator from django.views import View -from django.views.decorators.http import require_POST -from bookwyrm import forms, models from bookwyrm.connectors import connector_manager from .helpers import is_api_request @@ -23,7 +18,6 @@ class Isbn(View): return JsonResponse([r.json() for r in book_results], safe=False) data = { - "title": "ISBN Search Results", "results": book_results, "query": isbn, } diff --git a/bookwyrm/views/list.py b/bookwyrm/views/list.py index 75bb5d48c..6e872434c 100644 --- a/bookwyrm/views/list.py +++ b/bookwyrm/views/list.py @@ -314,8 +314,7 @@ def set_book_position(request, list_item_id): Max("order") )["order__max"] - if int_position > order_max: - int_position = order_max + int_position = min(int_position, order_max) if request.user not in (book_list.user, list_item.user): return HttpResponseNotFound() diff --git a/bookwyrm/views/reading.py b/bookwyrm/views/reading.py index 65ca717d4..1c897ab38 100644 --- a/bookwyrm/views/reading.py +++ b/bookwyrm/views/reading.py @@ -7,95 +7,79 @@ from dateutil.parser import ParserError from django.contrib.auth.decorators import login_required from django.http import HttpResponseBadRequest, HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect +from django.template.response import TemplateResponse +from django.utils.decorators import method_decorator +from django.views import View from django.views.decorators.http import require_POST from bookwyrm import models from .helpers import get_edition, handle_reading_status -from .shelf import handle_unshelve -# pylint: disable= no-self-use -@login_required -@require_POST -def start_reading(request, book_id): - """begin reading a book""" - book = get_edition(book_id) - reading_shelf = models.Shelf.objects.filter( - identifier=models.Shelf.READING, user=request.user - ).first() +@method_decorator(login_required, name="dispatch") +# pylint: disable=no-self-use +class ReadingStatus(View): + """consider reading a book""" - # create a readthrough - readthrough = update_readthrough(request, book=book) - if readthrough: - readthrough.save() + def get(self, request, status, book_id): + """modal page""" + book = get_edition(book_id) + template = { + "want": "want.html", + "start": "start.html", + "finish": "finish.html", + }.get(status) + if not template: + return HttpResponseNotFound() + return TemplateResponse(request, f"reading_progress/{template}", {"book": book}) - # create a progress update if we have a page - readthrough.create_update() + def post(self, request, status, book_id): + """desire a book""" + identifier = { + "want": models.Shelf.TO_READ, + "start": models.Shelf.READING, + "finish": models.Shelf.READ_FINISHED, + }.get(status) + if not identifier: + return HttpResponseBadRequest() - current_status_shelfbook = ( - models.ShelfBook.objects.select_related("shelf") - .filter( - shelf__identifier__in=models.Shelf.READ_STATUS_IDENTIFIERS, - user=request.user, - book=book, + desired_shelf = models.Shelf.objects.filter( + identifier=identifier, user=request.user + ).first() + + book = get_edition(book_id) + + current_status_shelfbook = ( + models.ShelfBook.objects.select_related("shelf") + .filter( + shelf__identifier__in=models.Shelf.READ_STATUS_IDENTIFIERS, + user=request.user, + book=book, + ) + .first() ) - .first() - ) - if current_status_shelfbook is not None: - if current_status_shelfbook.shelf.identifier != models.Shelf.READING: - handle_unshelve(book, current_status_shelfbook.shelf) - else: # It already was on the shelf - return redirect(request.headers.get("Referer", "/")) + if current_status_shelfbook is not None: + if current_status_shelfbook.shelf.identifier != desired_shelf.identifier: + current_status_shelfbook.delete() + else: # It already was on the shelf + return redirect(request.headers.get("Referer", "/")) - models.ShelfBook.objects.create(book=book, shelf=reading_shelf, user=request.user) - - # post about it (if you want) - if request.POST.get("post-status"): - privacy = request.POST.get("privacy") - handle_reading_status(request.user, reading_shelf, book, privacy) - - return redirect(request.headers.get("Referer", "/")) - - -@login_required -@require_POST -def finish_reading(request, book_id): - """a user completed a book, yay""" - book = get_edition(book_id) - finished_read_shelf = models.Shelf.objects.filter( - identifier=models.Shelf.READ_FINISHED, user=request.user - ).first() - - # update or create a readthrough - readthrough = update_readthrough(request, book=book) - if readthrough: - readthrough.save() - - current_status_shelfbook = ( - models.ShelfBook.objects.select_related("shelf") - .filter( - shelf__identifier__in=models.Shelf.READ_STATUS_IDENTIFIERS, - user=request.user, - book=book, + models.ShelfBook.objects.create( + book=book, shelf=desired_shelf, user=request.user ) - .first() - ) - if current_status_shelfbook is not None: - if current_status_shelfbook.shelf.identifier != models.Shelf.READ_FINISHED: - handle_unshelve(book, current_status_shelfbook.shelf) - else: # It already was on the shelf - return redirect(request.headers.get("Referer", "/")) - models.ShelfBook.objects.create( - book=book, shelf=finished_read_shelf, user=request.user - ) + if desired_shelf.identifier != models.Shelf.TO_READ: + # update or create a readthrough + readthrough = update_readthrough(request, book=book) + if readthrough: + readthrough.save() - # post about it (if you want) - if request.POST.get("post-status"): - privacy = request.POST.get("privacy") - handle_reading_status(request.user, finished_read_shelf, book, privacy) + # post about it (if you want) + if request.POST.get("post-status"): + privacy = request.POST.get("privacy") + handle_reading_status(request.user, desired_shelf, book, privacy) - return redirect(request.headers.get("Referer", "/")) + return redirect(request.headers.get("Referer", "/")) @login_required diff --git a/bookwyrm/views/rss_feed.py b/bookwyrm/views/rss_feed.py index f3613a091..0d3a8902e 100644 --- a/bookwyrm/views/rss_feed.py +++ b/bookwyrm/views/rss_feed.py @@ -10,7 +10,7 @@ class RssFeed(Feed): description_template = "rss/content.html" title_template = "rss/title.html" - def get_object(self, request, username): + def get_object(self, request, username): # pylint: disable=arguments-differ """the user who's posts get serialized""" return get_user_from_username(request.user, username) diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index e7b538e47..274a3bc2e 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -83,7 +83,7 @@ def user_search(query, viewer, *_): # use webfinger for mastodon style account@domain.com username to load the user if # they don't exist locally (handle_remote_webfinger will check the db) - if re.match(regex.full_username, query): + if re.match(regex.FULL_USERNAME, query): handle_remote_webfinger(query) return ( diff --git a/bookwyrm/views/shelf.py b/bookwyrm/views/shelf.py index 758e290ed..540975094 100644 --- a/bookwyrm/views/shelf.py +++ b/bookwyrm/views/shelf.py @@ -2,7 +2,7 @@ from collections import namedtuple from django.db import IntegrityError -from django.db.models import Count, OuterRef, Subquery, F, Q +from django.db.models import OuterRef, Subquery from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator from django.http import HttpResponseBadRequest, HttpResponseNotFound @@ -17,10 +17,10 @@ from bookwyrm import forms, models from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.settings import PAGE_LENGTH from .helpers import is_api_request, get_edition, get_user_from_username -from .helpers import handle_reading_status, privacy_filter +from .helpers import privacy_filter -# pylint: disable= no-self-use +# pylint: disable=no-self-use class Shelf(View): """shelf page""" @@ -28,7 +28,12 @@ class Shelf(View): """display a shelf""" user = get_user_from_username(request.user, username) - shelves = privacy_filter(request.user, user.shelf_set) + is_self = user == request.user + + if is_self: + shelves = user.shelf_set + else: + shelves = privacy_filter(request.user, user.shelf_set) # get the shelf and make sure the logged in user should be able to see it if shelf_identifier: @@ -53,26 +58,29 @@ class Shelf(View): if is_api_request(request): return ActivitypubResponse(shelf.to_activity(**request.GET)) - reviews = privacy_filter( - request.user, - models.Review.objects.filter( - user=user, - rating__isnull=False, - book__id=OuterRef("id"), - ), + reviews = models.Review.objects.filter( + user=user, + rating__isnull=False, + book__id=OuterRef("id"), + deleted=False, ).order_by("-published_date") - books = books.annotate(rating=Subquery(reviews.values("rating")[:1])) + if not is_self: + reviews = privacy_filter(request.user, reviews) + + books = books.annotate( + rating=Subquery(reviews.values("rating")[:1]) + ).prefetch_related("authors") paginated = Paginator( - books.order_by("-updated_date"), + books.order_by("-shelfbook__updated_date"), PAGE_LENGTH, ) page = paginated.get_page(request.GET.get("page")) data = { "user": user, - "is_self": request.user == user, + "is_self": is_self, "shelves": shelves.all(), "shelf": shelf, "books": page, @@ -170,11 +178,6 @@ def shelve(request): models.ShelfBook.objects.create( book=book, shelf=desired_shelf, user=request.user ) - if desired_shelf.identifier == models.Shelf.TO_READ and request.POST.get( - "post-status" - ): - privacy = request.POST.get("privacy") or desired_shelf.privacy - handle_reading_status(request.user, desired_shelf, book, privacy=privacy) else: try: models.ShelfBook.objects.create( @@ -198,7 +201,6 @@ def unshelve(request): return redirect(request.headers.get("Referer", "/")) -# pylint: disable=unused-argument def handle_unshelve(book, shelf): """unshelve a book""" row = models.ShelfBook.objects.get(book=book, shelf=shelf) diff --git a/bookwyrm/views/status.py b/bookwyrm/views/status.py index 27aeab07c..61b6dc7aa 100644 --- a/bookwyrm/views/status.py +++ b/bookwyrm/views/status.py @@ -133,7 +133,7 @@ def find_mentions(content): """detect @mentions in raw status content""" if not content: return - for match in re.finditer(regex.strict_username, content): + for match in re.finditer(regex.STRICT_USERNAME, content): username = match.group().strip().split("@")[1:] if len(username) == 1: # this looks like a local user (@user), fill in the domain @@ -150,7 +150,7 @@ def find_mentions(content): def format_links(content): """detect and format links""" return re.sub( - r'([^(href=")]|^|\()(https?:\/\/(%s([\w\.\-_\/+&\?=:;,])*))' % regex.domain, + r'([^(href=")]|^|\()(https?:\/\/(%s([\w\.\-_\/+&\?=:;,])*))' % regex.DOMAIN, r'\g<1>\g<3>', content, ) diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index 9684a68fe..f62913a93 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -1,25 +1,17 @@ """ non-interactive pages """ -from io import BytesIO -from uuid import uuid4 -from PIL import Image - -from django.contrib.auth.decorators import login_required -from django.core.files.base import ContentFile from django.core.paginator import Paginator -from django.shortcuts import redirect from django.template.response import TemplateResponse from django.utils import timezone -from django.utils.decorators import method_decorator from django.views import View -from bookwyrm import forms, models +from bookwyrm import models from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.settings import PAGE_LENGTH from .helpers import get_user_from_username, is_api_request from .helpers import privacy_filter -# pylint: disable= no-self-use +# pylint: disable=no-self-use class User(View): """user profile page""" @@ -59,10 +51,15 @@ class User(View): break # user's posts - activities = privacy_filter( - request.user, - user.status_set.select_subclasses(), + activities = ( + privacy_filter( + request.user, + user.status_set.select_subclasses(), + ) + .select_related("reply_parent") + .prefetch_related("mention_books", "mention_users") ) + paginated = Paginator(activities, PAGE_LENGTH) goal = models.AnnualGoal.objects.filter( user=user, year=timezone.now().year @@ -117,72 +114,3 @@ class Following(View): "follow_list": paginated.get_page(request.GET.get("page")), } return TemplateResponse(request, "user/relationships/following.html", data) - - -@method_decorator(login_required, name="dispatch") -class EditUser(View): - """edit user view""" - - def get(self, request): - """edit profile page for a user""" - data = { - "form": forms.EditUserForm(instance=request.user), - "user": request.user, - } - return TemplateResponse(request, "preferences/edit_user.html", data) - - def post(self, request): - """les get fancy with images""" - form = forms.EditUserForm(request.POST, request.FILES, instance=request.user) - if not form.is_valid(): - data = {"form": form, "user": request.user} - return TemplateResponse(request, "preferences/edit_user.html", data) - - user = save_user_form(form) - - return redirect(user.local_path) - - -def save_user_form(form): - """special handling for the user form""" - user = form.save(commit=False) - - if "avatar" in form.files: - # crop and resize avatar upload - image = Image.open(form.files["avatar"]) - image = crop_avatar(image) - - # set the name to a hash - extension = form.files["avatar"].name.split(".")[-1] - filename = "%s.%s" % (uuid4(), extension) - user.avatar.save(filename, image, save=False) - - user.save() - return user - - -def crop_avatar(image): - """reduce the size and make an avatar square""" - target_size = 120 - width, height = image.size - thumbnail_scale = ( - height / (width / target_size) - if height > width - else width / (height / target_size) - ) - image.thumbnail([thumbnail_scale, thumbnail_scale]) - width, height = image.size - - width_diff = width - target_size - height_diff = height - target_size - cropped = image.crop( - ( - int(width_diff / 2), - int(height_diff / 2), - int(width - (width_diff / 2)), - int(height - (height_diff / 2)), - ) - ) - output = BytesIO() - cropped.save(output, format=image.format) - return ContentFile(output.getvalue()) diff --git a/bw-dev b/bw-dev index 08bb5bc2b..f04ed63da 100755 --- a/bw-dev +++ b/bw-dev @@ -90,7 +90,7 @@ case "$CMD" in runweb python manage.py collectstatic --no-input ;; makemessages) - runweb django-admin makemessages --no-wrap --ignore=venv $@ + runweb django-admin makemessages --no-wrap --ignore=venv --all $@ ;; compilemessages) runweb django-admin compilemessages --ignore venv $@ @@ -113,4 +113,10 @@ case "$CMD" in *) echo "Unrecognised command. Try: build, clean, up, initdb, resetdb, makemigrations, migrate, bash, shell, dbshell, restart_celery, test, pytest, test_report, black, populate_streams, populate_suggestions" ;; + generate_preview_images) + runweb python manage.py generate_preview_images $@ + ;; + *) + echo "Unrecognised command. Try: build, clean, up, initdb, resetdb, makemigrations, migrate, bash, shell, dbshell, restart_celery, test, pytest, test_report, black, populate_feeds, generate_preview_images" + ;; esac diff --git a/celerywyrm/celery.py b/celerywyrm/celery.py index fcf75b049..3ab338621 100644 --- a/celerywyrm/celery.py +++ b/celerywyrm/celery.py @@ -24,6 +24,7 @@ app.autodiscover_tasks(["bookwyrm"], related_name="broadcast") app.autodiscover_tasks(["bookwyrm"], related_name="connectors.abstract_connector") app.autodiscover_tasks(["bookwyrm"], related_name="emailing") app.autodiscover_tasks(["bookwyrm"], related_name="goodreads_import") +app.autodiscover_tasks(["bookwyrm"], related_name="preview_images") app.autodiscover_tasks(["bookwyrm"], related_name="models.user") app.autodiscover_tasks(["bookwyrm"], related_name="suggested_users") app.autodiscover_tasks(["bookwyrm"], related_name="views.inbox") diff --git a/celerywyrm/settings.py b/celerywyrm/settings.py index e4fa73ca2..107a39572 100644 --- a/celerywyrm/settings.py +++ b/celerywyrm/settings.py @@ -1,10 +1,10 @@ """ bookwyrm settings and configuration """ from bookwyrm.settings import * -CELERY_BROKER_URL = env("CELERY_BROKER") +CELERY_BROKER_URL = CELERY_BROKER CELERY_ACCEPT_CONTENT = ["json"] CELERY_TASK_SERIALIZER = "json" -CELERY_RESULT_BACKEND = "redis" +FLOWER_PORT = env("FLOWER_PORT") INSTALLED_APPS = INSTALLED_APPS + [ "celerywyrm", diff --git a/docker-compose.yml b/docker-compose.yml index 18f836502..49d02e706 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -74,10 +74,10 @@ services: restart: on-failure flower: build: . - command: flower --port=${FLOWER_PORT} + command: flower -A celerywyrm env_file: .env - environment: - - CELERY_BROKER_URL=${CELERY_BROKER} + volumes: + - .:/app networks: - main depends_on: diff --git a/locale/de_DE/LC_MESSAGES/django.mo b/locale/de_DE/LC_MESSAGES/django.mo index 089ac656a..dde00be62 100644 Binary files a/locale/de_DE/LC_MESSAGES/django.mo and b/locale/de_DE/LC_MESSAGES/django.mo differ diff --git a/locale/de_DE/LC_MESSAGES/django.po b/locale/de_DE/LC_MESSAGES/django.po index 40f770b56..3125d147c 100644 --- a/locale/de_DE/LC_MESSAGES/django.po +++ b/locale/de_DE/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-05-20 14:40-0700\n" +"POT-Creation-Date: 2021-06-06 20:52+0000\n" "PO-Revision-Date: 2021-03-02 17:19-0800\n" "Last-Translator: Mouse Reeve \n" "Language-Team: English \n" @@ -62,12 +62,12 @@ msgid "Book Title" msgstr "Titel" #: bookwyrm/forms.py:301 bookwyrm/templates/snippets/create_status_form.html:34 -#: bookwyrm/templates/user/shelf/shelf.html:84 -#: bookwyrm/templates/user/shelf/shelf.html:115 +#: bookwyrm/templates/user/shelf/shelf.html:85 +#: bookwyrm/templates/user/shelf/shelf.html:116 msgid "Rating" msgstr "" -#: bookwyrm/forms.py:303 bookwyrm/templates/lists/list.html:101 +#: bookwyrm/forms.py:303 bookwyrm/templates/lists/list.html:107 msgid "Sort By" msgstr "" @@ -83,41 +83,41 @@ msgstr "Zu lesen angefangen" msgid "Descending" msgstr "Zu lesen angefangen" -#: bookwyrm/models/fields.py:24 +#: bookwyrm/models/fields.py:25 #, python-format msgid "%(value)s is not a valid remote_id" msgstr "%(value)s ist keine gültige remote_id" -#: bookwyrm/models/fields.py:33 bookwyrm/models/fields.py:42 +#: bookwyrm/models/fields.py:34 bookwyrm/models/fields.py:43 #, python-format msgid "%(value)s is not a valid username" msgstr "%(value)s ist kein gültiger Username" -#: bookwyrm/models/fields.py:165 bookwyrm/templates/layout.html:155 +#: bookwyrm/models/fields.py:166 bookwyrm/templates/layout.html:152 msgid "username" msgstr "Username" -#: bookwyrm/models/fields.py:170 +#: bookwyrm/models/fields.py:171 msgid "A user with that username already exists." msgstr "Dieser Benutzename ist bereits vergeben." -#: bookwyrm/settings.py:155 +#: bookwyrm/settings.py:156 msgid "English" msgstr "Englisch" -#: bookwyrm/settings.py:156 +#: bookwyrm/settings.py:157 msgid "German" msgstr "Deutsch" -#: bookwyrm/settings.py:157 +#: bookwyrm/settings.py:158 msgid "Spanish" msgstr "Spanisch" -#: bookwyrm/settings.py:158 +#: bookwyrm/settings.py:159 msgid "French" msgstr "Französisch" -#: bookwyrm/settings.py:159 +#: bookwyrm/settings.py:160 msgid "Simplified Chinese" msgstr "Vereinfachtes Chinesisch" @@ -266,7 +266,7 @@ msgstr "" #: bookwyrm/templates/book/edit_book.html:263 #: bookwyrm/templates/lists/form.html:42 #: bookwyrm/templates/preferences/edit_user.html:70 -#: bookwyrm/templates/settings/announcement_form.html:65 +#: bookwyrm/templates/settings/announcement_form.html:69 #: bookwyrm/templates/settings/edit_server.html:68 #: bookwyrm/templates/settings/federated_server.html:98 #: bookwyrm/templates/settings/site.html:97 @@ -398,7 +398,7 @@ msgstr "Themen" msgid "Places" msgstr "Orte" -#: bookwyrm/templates/book/book.html:276 bookwyrm/templates/layout.html:64 +#: bookwyrm/templates/book/book.html:276 bookwyrm/templates/layout.html:61 #: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 #: bookwyrm/templates/search/layout.html:25 #: bookwyrm/templates/search/layout.html:50 @@ -414,7 +414,7 @@ msgstr "Zur Liste" #: bookwyrm/templates/book/book.html:297 #: bookwyrm/templates/book/cover_modal.html:31 -#: bookwyrm/templates/lists/list.html:164 +#: bookwyrm/templates/lists/list.html:179 msgid "Add" msgstr "Hinzufügen" @@ -559,7 +559,7 @@ msgid "John Doe, Jane Smith" msgstr "" #: bookwyrm/templates/book/edit_book.html:183 -#: bookwyrm/templates/user/shelf/shelf.html:77 +#: bookwyrm/templates/user/shelf/shelf.html:78 msgid "Cover" msgstr "" @@ -688,7 +688,7 @@ msgstr "Föderiert" #: bookwyrm/templates/directory/directory.html:4 #: bookwyrm/templates/directory/directory.html:9 -#: bookwyrm/templates/layout.html:92 +#: bookwyrm/templates/layout.html:64 msgid "Directory" msgstr "" @@ -902,7 +902,7 @@ msgid "Direct Messages with %(username)s" msgstr "Direktnachrichten mit %(username)s" #: bookwyrm/templates/feed/direct_messages.html:10 -#: bookwyrm/templates/layout.html:87 +#: bookwyrm/templates/layout.html:92 msgid "Direct Messages" msgstr "Direktnachrichten" @@ -960,7 +960,6 @@ msgid "Updates" msgstr "" #: bookwyrm/templates/feed/feed_layout.html:10 -#: bookwyrm/templates/layout.html:58 #: bookwyrm/templates/user/shelf/books_header.html:3 msgid "Your books" msgstr "Deine Bücher" @@ -1022,7 +1021,7 @@ msgid "What are you reading?" msgstr "Zu lesen angefangen" #: bookwyrm/templates/get_started/books.html:9 -#: bookwyrm/templates/lists/list.html:120 +#: bookwyrm/templates/lists/list.html:135 msgid "Search for a book" msgstr "Nach einem Buch suchen" @@ -1042,7 +1041,7 @@ msgstr "" #: bookwyrm/templates/get_started/users.html:18 #: bookwyrm/templates/get_started/users.html:19 #: bookwyrm/templates/layout.html:37 bookwyrm/templates/layout.html:38 -#: bookwyrm/templates/lists/list.html:124 +#: bookwyrm/templates/lists/list.html:139 #: bookwyrm/templates/search/layout.html:4 #: bookwyrm/templates/search/layout.html:9 msgid "Search" @@ -1061,7 +1060,7 @@ msgid "Popular on %(site_name)s" msgstr "Über %(site_name)s" #: bookwyrm/templates/get_started/books.html:58 -#: bookwyrm/templates/lists/list.html:137 +#: bookwyrm/templates/lists/list.html:152 msgid "No books found" msgstr "Keine Bücher gefunden" @@ -1184,7 +1183,7 @@ msgid "%(username)s's %(year)s Books" msgstr "%(username)ss %(year)s Bücher" #: bookwyrm/templates/import.html:5 bookwyrm/templates/import.html:9 -#: bookwyrm/templates/layout.html:97 +#: bookwyrm/templates/user/shelf/shelf.html:40 msgid "Import Books" msgstr "Bücher importieren" @@ -1271,14 +1270,14 @@ msgstr "Buch" #: bookwyrm/templates/import_status.html:114 #: bookwyrm/templates/snippets/create_status_form.html:13 -#: bookwyrm/templates/user/shelf/shelf.html:78 -#: bookwyrm/templates/user/shelf/shelf.html:98 +#: bookwyrm/templates/user/shelf/shelf.html:79 +#: bookwyrm/templates/user/shelf/shelf.html:99 msgid "Title" msgstr "Titel" #: bookwyrm/templates/import_status.html:117 -#: bookwyrm/templates/user/shelf/shelf.html:79 -#: bookwyrm/templates/user/shelf/shelf.html:101 +#: bookwyrm/templates/user/shelf/shelf.html:80 +#: bookwyrm/templates/user/shelf/shelf.html:102 msgid "Author" msgstr "Autor*in" @@ -1320,15 +1319,21 @@ msgstr "Suche nach Buch oder Benutzer*in" msgid "Main navigation menu" msgstr "Navigationshauptmenü" -#: bookwyrm/templates/layout.html:61 +#: bookwyrm/templates/layout.html:58 msgid "Feed" msgstr "" -#: bookwyrm/templates/layout.html:102 +#: bookwyrm/templates/layout.html:87 +#, fuzzy +#| msgid "Your books" +msgid "Your Books" +msgstr "Deine Bücher" + +#: bookwyrm/templates/layout.html:97 msgid "Settings" msgstr "Einstellungen" -#: bookwyrm/templates/layout.html:111 +#: bookwyrm/templates/layout.html:106 #: bookwyrm/templates/settings/admin_layout.html:31 #: bookwyrm/templates/settings/manage_invite_requests.html:15 #: bookwyrm/templates/settings/manage_invites.html:3 @@ -1336,45 +1341,47 @@ msgstr "Einstellungen" msgid "Invites" msgstr "Einladungen" -#: bookwyrm/templates/layout.html:118 +#: bookwyrm/templates/layout.html:113 msgid "Admin" msgstr "" -#: bookwyrm/templates/layout.html:125 +#: bookwyrm/templates/layout.html:120 msgid "Log out" msgstr "Abmelden" -#: bookwyrm/templates/layout.html:133 bookwyrm/templates/layout.html:134 +#: bookwyrm/templates/layout.html:128 bookwyrm/templates/layout.html:129 #: bookwyrm/templates/notifications.html:6 #: bookwyrm/templates/notifications.html:11 msgid "Notifications" msgstr "Benachrichtigungen" -#: bookwyrm/templates/layout.html:154 bookwyrm/templates/layout.html:158 +#: bookwyrm/templates/layout.html:151 bookwyrm/templates/layout.html:155 #: bookwyrm/templates/login.html:17 #: bookwyrm/templates/snippets/register_form.html:4 msgid "Username:" msgstr "" -#: bookwyrm/templates/layout.html:159 +#: bookwyrm/templates/layout.html:156 msgid "password" msgstr "Passwort" -#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:36 +#: bookwyrm/templates/layout.html:157 bookwyrm/templates/login.html:36 msgid "Forgot your password?" msgstr "Passwort vergessen?" -#: bookwyrm/templates/layout.html:163 bookwyrm/templates/login.html:10 +#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:10 #: bookwyrm/templates/login.html:33 msgid "Log in" msgstr "Anmelden" -#: bookwyrm/templates/layout.html:171 +#: bookwyrm/templates/layout.html:168 msgid "Join" msgstr "" #: bookwyrm/templates/layout.html:206 -msgid "About this server" +#, fuzzy +#| msgid "About this server" +msgid "About this instance" msgstr "Über diesen Server" #: bookwyrm/templates/layout.html:210 @@ -1438,7 +1445,7 @@ msgid "Discard" msgstr "Ablehnen" #: bookwyrm/templates/lists/edit_form.html:5 -#: bookwyrm/templates/lists/list_layout.html:17 +#: bookwyrm/templates/lists/list_layout.html:16 msgid "Edit List" msgstr "Liste bearbeiten" @@ -1487,63 +1494,64 @@ msgstr "Alle können Bücher hinzufügen" msgid "This list is currently empty" msgstr "Diese Liste ist momentan leer" -#: bookwyrm/templates/lists/list.html:65 +#: bookwyrm/templates/lists/list.html:66 #, fuzzy, python-format #| msgid "Direct Messages with %(username)s" msgid "Added by %(username)s" msgstr "Direktnachrichten mit %(username)s" -#: bookwyrm/templates/lists/list.html:77 -#, fuzzy -#| msgid "Started" -msgid "Set" -msgstr "Gestartet" - -#: bookwyrm/templates/lists/list.html:80 +#: bookwyrm/templates/lists/list.html:74 #, fuzzy #| msgid "List curation:" msgid "List position" msgstr "Listenkuratierung:" -#: bookwyrm/templates/lists/list.html:86 +#: bookwyrm/templates/lists/list.html:81 +#, fuzzy +#| msgid "Started" +msgid "Set" +msgstr "Gestartet" + +#: bookwyrm/templates/lists/list.html:89 #: bookwyrm/templates/snippets/shelf_selector.html:26 msgid "Remove" msgstr "Entfernen" -#: bookwyrm/templates/lists/list.html:99 bookwyrm/templates/lists/list.html:111 +#: bookwyrm/templates/lists/list.html:103 +#: bookwyrm/templates/lists/list.html:120 #, fuzzy #| msgid "Your Lists" msgid "Sort List" msgstr "Deine Listen" -#: bookwyrm/templates/lists/list.html:105 +#: bookwyrm/templates/lists/list.html:113 #, fuzzy #| msgid "List curation:" msgid "Direction" msgstr "Listenkuratierung:" -#: bookwyrm/templates/lists/list.html:116 +#: bookwyrm/templates/lists/list.html:127 msgid "Add Books" msgstr "Bücher hinzufügen" -#: bookwyrm/templates/lists/list.html:116 +#: bookwyrm/templates/lists/list.html:129 msgid "Suggest Books" msgstr "Bücher vorschlagen" -#: bookwyrm/templates/lists/list.html:125 +#: bookwyrm/templates/lists/list.html:140 msgid "search" msgstr "suchen" -#: bookwyrm/templates/lists/list.html:131 +#: bookwyrm/templates/lists/list.html:146 msgid "Clear search" msgstr "Suche leeren" -#: bookwyrm/templates/lists/list.html:136 +#: bookwyrm/templates/lists/list.html:151 #, python-format msgid "No books found matching the query \"%(query)s\"" msgstr "Keine passenden Bücher zu \"%(query)s\" gefunden" -#: bookwyrm/templates/lists/list.html:164 +#: bookwyrm/templates/lists/list.html:179 msgid "Suggest" msgstr "Vorschlagen" @@ -1643,7 +1651,7 @@ msgstr "Lösen" #: bookwyrm/templates/moderation/reports.html:6 #, fuzzy, python-format #| msgid "Lists: %(username)s" -msgid "Reports: %(server_name)s" +msgid "Reports: %(instance_name)s" msgstr "Listen: %(username)s" #: bookwyrm/templates/moderation/reports.html:8 @@ -1657,7 +1665,7 @@ msgstr "Aktuelle Importe" #: bookwyrm/templates/moderation/reports.html:14 #, fuzzy, python-format #| msgid "Lists: %(username)s" -msgid "Reports: %(server_name)s" +msgid "Reports: %(instance_name)s" msgstr "Listen: %(username)s" #: bookwyrm/templates/moderation/reports.html:28 @@ -1863,6 +1871,26 @@ msgstr "Profil" msgid "Relationships" msgstr "Beziehungen" +#: bookwyrm/templates/rss/title.html:5 +#: bookwyrm/templates/snippets/status/status_header.html:35 +msgid "rated" +msgstr "" + +#: bookwyrm/templates/rss/title.html:7 +#: bookwyrm/templates/snippets/status/status_header.html:37 +msgid "reviewed" +msgstr "bewertete" + +#: bookwyrm/templates/rss/title.html:9 +#: bookwyrm/templates/snippets/status/status_header.html:39 +msgid "commented on" +msgstr "kommentierte" + +#: bookwyrm/templates/rss/title.html:11 +#: bookwyrm/templates/snippets/status/status_header.html:41 +msgid "quoted" +msgstr "zitierte" + #: bookwyrm/templates/search/book.html:64 #, fuzzy #| msgid "Show results from other catalogues" @@ -1922,7 +1950,9 @@ msgstr "Nutzer*innen verwalten" #: bookwyrm/templates/settings/admin_layout.html:39 #: bookwyrm/templates/settings/federation.html:3 #: bookwyrm/templates/settings/federation.html:5 -msgid "Federated Servers" +#, fuzzy +#| msgid "Federated Servers" +msgid "Federated Instances" msgstr "Föderierende Server" #: bookwyrm/templates/settings/admin_layout.html:44 @@ -1976,6 +2006,7 @@ msgid "Back to list" msgstr "Zurück zu den Meldungen" #: bookwyrm/templates/settings/announcement.html:11 +#: bookwyrm/templates/settings/announcement_form.html:6 #, fuzzy #| msgid "Announcements" msgid "Edit Announcement" @@ -2017,7 +2048,7 @@ msgstr "Geburtsdatum:" msgid "Active:" msgstr "Aktivität" -#: bookwyrm/templates/settings/announcement_form.html:5 +#: bookwyrm/templates/settings/announcement_form.html:8 #: bookwyrm/templates/settings/announcements.html:8 #, fuzzy #| msgid "Announcements" @@ -2076,15 +2107,15 @@ msgstr "Aktivität" #: bookwyrm/templates/settings/server_blocklist.html:3 #: bookwyrm/templates/settings/server_blocklist.html:20 #, fuzzy -#| msgid "Add cover" -msgid "Add server" -msgstr "Cover hinzufügen" +#| msgid "Instance Name:" +msgid "Add instance" +msgstr "Instanzname" #: bookwyrm/templates/settings/edit_server.html:7 #: bookwyrm/templates/settings/server_blocklist.html:7 #, fuzzy #| msgid "Back to reports" -msgid "Back to server list" +msgid "Back to instance list" msgstr "Zurück zu den Meldungen" #: bookwyrm/templates/settings/edit_server.html:16 @@ -2213,8 +2244,10 @@ msgstr "" #: bookwyrm/templates/settings/federation.html:19 #: bookwyrm/templates/user_admin/server_filter.html:5 -msgid "Server name" -msgstr "Servername" +#, fuzzy +#| msgid "Instance Name:" +msgid "Instance name" +msgstr "Instanzname" #: bookwyrm/templates/settings/federation.html:23 #, fuzzy @@ -2353,7 +2386,7 @@ msgid "Import Blocklist" msgstr "Bücher importieren" #: bookwyrm/templates/settings/server_blocklist.html:26 -#: bookwyrm/templates/snippets/goal_progress.html:5 +#: bookwyrm/templates/snippets/goal_progress.html:7 msgid "Success!" msgstr "Erfolg!" @@ -2446,15 +2479,15 @@ msgstr "Cover hinzufügen" msgid "%(title)s by " msgstr "%(title)s von " -#: bookwyrm/templates/snippets/boost_button.html:9 -#: bookwyrm/templates/snippets/boost_button.html:10 +#: bookwyrm/templates/snippets/boost_button.html:20 +#: bookwyrm/templates/snippets/boost_button.html:21 #, fuzzy #| msgid "boosted" msgid "Boost" msgstr "teilt" -#: bookwyrm/templates/snippets/boost_button.html:16 -#: bookwyrm/templates/snippets/boost_button.html:17 +#: bookwyrm/templates/snippets/boost_button.html:33 +#: bookwyrm/templates/snippets/boost_button.html:34 #, fuzzy #| msgid "Un-boost status" msgid "Un-boost" @@ -2554,13 +2587,13 @@ msgstr "Diese Lesedaten löschen?" msgid "You are deleting this readthrough and its %(count)s associated progress updates." msgstr "Du löscht diesen Leseforschritt und %(count)s zugehörige Fortschrittsupdates." -#: bookwyrm/templates/snippets/fav_button.html:9 -#: bookwyrm/templates/snippets/fav_button.html:11 +#: bookwyrm/templates/snippets/fav_button.html:10 +#: bookwyrm/templates/snippets/fav_button.html:12 msgid "Like" msgstr "" -#: bookwyrm/templates/snippets/fav_button.html:17 #: bookwyrm/templates/snippets/fav_button.html:18 +#: bookwyrm/templates/snippets/fav_button.html:19 #, fuzzy #| msgid "Un-like status" msgid "Un-like" @@ -2609,6 +2642,14 @@ msgstr "Annehmen" msgid "No rating" msgstr "Kein Rating" +#: bookwyrm/templates/snippets/form_rate_stars.html:44 +#: bookwyrm/templates/snippets/stars.html:7 +#, python-format +msgid "%(rating)s star" +msgid_plural "%(rating)s stars" +msgstr[0] "" +msgstr[1] "" + #: bookwyrm/templates/snippets/generated_status/goal.html:1 #, python-format msgid "set a goal to read %(counter)s book in %(year)s" @@ -2664,17 +2705,17 @@ msgstr "Posten" msgid "Set goal" msgstr "Ziel setzen" -#: bookwyrm/templates/snippets/goal_progress.html:7 +#: bookwyrm/templates/snippets/goal_progress.html:9 #, python-format msgid "%(percent)s%% complete!" msgstr "%(percent)s%% komplett!" -#: bookwyrm/templates/snippets/goal_progress.html:10 +#: bookwyrm/templates/snippets/goal_progress.html:12 #, python-format msgid "You've read %(read_count)s of %(goal_count)s books." msgstr "Du hast %(read_count)s von %(goal_count)s Büchern gelesen." -#: bookwyrm/templates/snippets/goal_progress.html:12 +#: bookwyrm/templates/snippets/goal_progress.html:14 #, python-format msgid "%(username)s has read %(read_count)s of %(goal_count)s books." msgstr "%(username)s hat %(read_count)s von %(goal_count)s Büchern gelesen." @@ -2787,26 +2828,6 @@ msgstr "Registrieren" msgid "Report" msgstr "Importieren" -#: bookwyrm/templates/snippets/rss_title.html:5 -#: bookwyrm/templates/snippets/status/status_header.html:35 -msgid "rated" -msgstr "" - -#: bookwyrm/templates/snippets/rss_title.html:7 -#: bookwyrm/templates/snippets/status/status_header.html:37 -msgid "reviewed" -msgstr "bewertete" - -#: bookwyrm/templates/snippets/rss_title.html:9 -#: bookwyrm/templates/snippets/status/status_header.html:39 -msgid "commented on" -msgstr "kommentierte" - -#: bookwyrm/templates/snippets/rss_title.html:11 -#: bookwyrm/templates/snippets/status/status_header.html:41 -msgid "quoted" -msgstr "zitierte" - #: bookwyrm/templates/snippets/search_result_text.html:36 msgid "Import book" msgstr "Buch importieren" @@ -2989,7 +3010,7 @@ msgstr "Regal bearbeiten" msgid "Update shelf" msgstr "Regal aktualisieren" -#: bookwyrm/templates/user/shelf/shelf.html:25 bookwyrm/views/shelf.py:51 +#: bookwyrm/templates/user/shelf/shelf.html:25 bookwyrm/views/shelf.py:56 #, fuzzy #| msgid "books" msgid "All books" @@ -2999,30 +3020,30 @@ msgstr "Bücher" msgid "Create shelf" msgstr "Regal erstellen" -#: bookwyrm/templates/user/shelf/shelf.html:61 +#: bookwyrm/templates/user/shelf/shelf.html:62 msgid "Edit shelf" msgstr "Regal bearbeiten" -#: bookwyrm/templates/user/shelf/shelf.html:80 -#: bookwyrm/templates/user/shelf/shelf.html:104 +#: bookwyrm/templates/user/shelf/shelf.html:81 +#: bookwyrm/templates/user/shelf/shelf.html:105 msgid "Shelved" msgstr "Ins Regal gestellt" -#: bookwyrm/templates/user/shelf/shelf.html:81 -#: bookwyrm/templates/user/shelf/shelf.html:108 +#: bookwyrm/templates/user/shelf/shelf.html:82 +#: bookwyrm/templates/user/shelf/shelf.html:109 msgid "Started" msgstr "Gestartet" -#: bookwyrm/templates/user/shelf/shelf.html:82 -#: bookwyrm/templates/user/shelf/shelf.html:111 +#: bookwyrm/templates/user/shelf/shelf.html:83 +#: bookwyrm/templates/user/shelf/shelf.html:112 msgid "Finished" msgstr "Abgeschlossen" -#: bookwyrm/templates/user/shelf/shelf.html:137 +#: bookwyrm/templates/user/shelf/shelf.html:138 msgid "This shelf is empty." msgstr "Dieses Regal ist leer." -#: bookwyrm/templates/user/shelf/shelf.html:143 +#: bookwyrm/templates/user/shelf/shelf.html:144 msgid "Delete shelf" msgstr "Regal löschen" @@ -3084,9 +3105,10 @@ msgid "Back to users" msgstr "Zurück zu den Meldungen" #: bookwyrm/templates/user_admin/user_admin.html:7 -#, python-format -msgid "Users: %(server_name)s" -msgstr "" +#, fuzzy, python-format +#| msgid "Lists: %(username)s" +msgid "Users: %(instance_name)s" +msgstr "Listen: %(username)s" #: bookwyrm/templates/user_admin/user_admin.html:22 #: bookwyrm/templates/user_admin/username_filter.html:5 @@ -3107,9 +3129,9 @@ msgstr "" #: bookwyrm/templates/user_admin/user_admin.html:38 #, fuzzy -#| msgid "Remove" -msgid "Remote server" -msgstr "Entfernen" +#| msgid "Instance Name:" +msgid "Remote instance" +msgstr "Instanzname" #: bookwyrm/templates/user_admin/user_admin.html:47 #, fuzzy @@ -3158,6 +3180,10 @@ msgstr "" msgid "Access level:" msgstr "" +#: bookwyrm/templates/widgets/clearable_file_input_with_warning.html:3 +msgid "File exceeds maximum size: 10MB" +msgstr "" + #: bookwyrm/views/import_data.py:67 #, fuzzy #| msgid "Email address:" @@ -3175,6 +3201,27 @@ msgstr "Dieser Benutzename ist bereits vergeben." msgid "A password reset link sent to %s" msgstr "" +#, fuzzy +#~| msgid "Lists: %(username)s" +#~ msgid "Reports: %(server_name)s" +#~ msgstr "Listen: %(username)s" + +#~ msgid "Federated Servers" +#~ msgstr "Föderierende Server" + +#~ msgid "Server name" +#~ msgstr "Servername" + +#, fuzzy +#~| msgid "Add cover" +#~ msgid "Add server" +#~ msgstr "Cover hinzufügen" + +#, fuzzy +#~| msgid "Remove" +#~ msgid "Remote server" +#~ msgstr "Entfernen" + #, fuzzy #~| msgid "Book" #~ msgid "BookWyrm\\" @@ -3225,12 +3272,12 @@ msgstr "" #~ msgid "Enter a number." #~ msgstr "Seriennummer:" -#, fuzzy, python-format +#, fuzzy #~| msgid "A user with that username already exists." #~ msgid "%(model_name)s with this %(field_labels)s already exists." #~ msgstr "Dieser Benutzename ist bereits vergeben." -#, fuzzy, python-format +#, fuzzy #~| msgid "%(value)s is not a valid remote_id" #~ msgid "Value %(value)r is not a valid choice." #~ msgstr "%(value)s ist keine gültige remote_id" @@ -3240,7 +3287,7 @@ msgstr "" #~ msgid "This field cannot be null." #~ msgstr "Dieses Regal ist leer." -#, fuzzy, python-format +#, fuzzy #~| msgid "A user with that username already exists." #~ msgid "%(model_name)s with this %(field_label)s already exists." #~ msgstr "Dieser Benutzename ist bereits vergeben." @@ -3250,7 +3297,7 @@ msgstr "" #~ msgid "Comma-separated integers" #~ msgstr "Keine aktiven Einladungen" -#, fuzzy, python-format +#, fuzzy #~| msgid "%(value)s is not a valid username" #~ msgid "“%(value)s” value must be a decimal number." #~ msgstr "%(value)s ist kein gültiger Username" @@ -3270,7 +3317,7 @@ msgstr "" #~ msgid "Email address" #~ msgstr "E-Mail Adresse" -#, fuzzy, python-format +#, fuzzy #~| msgid "%(value)s is not a valid username" #~ msgid "“%(value)s” value must be a float." #~ msgstr "%(value)s ist kein gültiger Username" @@ -3300,7 +3347,7 @@ msgstr "" #~ msgid "Positive small integer" #~ msgstr "Keine aktiven Einladungen" -#, fuzzy, python-format +#, fuzzy #~| msgid "%(value)s is not a valid username" #~ msgid "“%(value)s” is not a valid UUID." #~ msgstr "%(value)s ist kein gültiger Username" @@ -3315,12 +3362,12 @@ msgstr "" #~ msgid "One-to-one relationship" #~ msgstr "Beziehungen" -#, fuzzy, python-format +#, fuzzy #~| msgid "Relationships" #~ msgid "%(from)s-%(to)s relationship" #~ msgstr "Beziehungen" -#, fuzzy, python-format +#, fuzzy #~| msgid "Relationships" #~ msgid "%(from)s-%(to)s relationships" #~ msgstr "Beziehungen" @@ -3375,7 +3422,7 @@ msgstr "" #~ msgid "Enter a valid UUID." #~ msgstr "E-Mail Adresse" -#, fuzzy, python-format +#, fuzzy #~| msgid "%(value)s is not a valid username" #~ msgid "“%(pk)s” is not a valid value." #~ msgstr "%(value)s ist kein gültiger Username" @@ -3439,12 +3486,12 @@ msgstr "" #~ msgid "This is not a valid IPv6 address." #~ msgstr "E-Mail Adresse" -#, fuzzy, python-format +#, fuzzy #~| msgid "No books found matching the query \"%(query)s\"" #~ msgid "No %(verbose_name)s found matching the query" #~ msgstr "Keine passenden Bücher zu \"%(query)s\" gefunden" -#, fuzzy, python-format +#, fuzzy #~| msgid "%(value)s is not a valid username" #~ msgid "“%(path)s” does not exist" #~ msgstr "%(value)s ist kein gültiger Username" @@ -3463,11 +3510,9 @@ msgstr "" #~ msgid "Matching Users" #~ msgstr "Passende Nutzer*innen" -#, python-format #~ msgid "Set a reading goal for %(year)s" #~ msgstr "Leseziel für %(year)s setzen" -#, python-format #~ msgid "by %(author)s" #~ msgstr "von %(author)s" @@ -3477,17 +3522,17 @@ msgstr "" #~ msgid "Reactivate user" #~ msgstr "Nutzer:in reaktivieren" -#, fuzzy, python-format +#, fuzzy #~| msgid "Direct Messages with %(username)s" #~ msgid "replied to %(username)s's review" #~ msgstr "Direktnachrichten mit %(username)s" -#, fuzzy, python-format +#, fuzzy #~| msgid "replied to your status" #~ msgid "replied to %(username)s's comment" #~ msgstr "hat auf deinen Status geantwortet" -#, fuzzy, python-format +#, fuzzy #~| msgid "replied to your status" #~ msgid "replied to %(username)s's quote" #~ msgstr "hat auf deinen Status geantwortet" @@ -3498,7 +3543,6 @@ msgstr "" #~ msgid "Add tag" #~ msgstr "Tag hinzufügen" -#, python-format #~ msgid "Books tagged \"%(tag.name)s\"" #~ msgstr "Mit \"%(tag.name)s\" markierte Bücher" @@ -3507,7 +3551,7 @@ msgstr "" #~ msgid "Getting Started" #~ msgstr "Gestartet" -#, fuzzy, python-format +#, fuzzy #~| msgid "No users found for \"%(query)s\"" #~ msgid "No users were found for \"%(query)s\"" #~ msgstr "Keine Nutzer*innen für \"%(query)s\" gefunden" @@ -3515,7 +3559,7 @@ msgstr "" #~ msgid "Your lists" #~ msgstr "Deine Listen" -#, fuzzy, python-format +#, fuzzy #~| msgid "See all %(size)s" #~ msgid "See all %(size)s lists" #~ msgstr "Alle %(size)s anzeigen" @@ -3538,14 +3582,12 @@ msgstr "" #~ msgid "Your Shelves" #~ msgstr "Deine Regale" -#, python-format #~ msgid "%(username)s: Shelves" #~ msgstr "%(username)s: Regale" #~ msgid "Shelves" #~ msgstr "Regale" -#, python-format #~ msgid "See all %(shelf_count)s shelves" #~ msgstr "Alle %(shelf_count)s Regale anzeigen" diff --git a/locale/en_US/LC_MESSAGES/django.po b/locale/en_US/LC_MESSAGES/django.po index 1fa5eb304..8c36a2cb3 100644 --- a/locale/en_US/LC_MESSAGES/django.po +++ b/locale/en_US/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-05-20 14:40-0700\n" +"POT-Creation-Date: 2021-06-06 20:52+0000\n" "PO-Revision-Date: 2021-02-28 17:19-0800\n" "Last-Translator: Mouse Reeve \n" "Language-Team: English \n" @@ -56,12 +56,12 @@ msgid "Book Title" msgstr "" #: bookwyrm/forms.py:301 bookwyrm/templates/snippets/create_status_form.html:34 -#: bookwyrm/templates/user/shelf/shelf.html:84 -#: bookwyrm/templates/user/shelf/shelf.html:115 +#: bookwyrm/templates/user/shelf/shelf.html:85 +#: bookwyrm/templates/user/shelf/shelf.html:116 msgid "Rating" msgstr "" -#: bookwyrm/forms.py:303 bookwyrm/templates/lists/list.html:101 +#: bookwyrm/forms.py:303 bookwyrm/templates/lists/list.html:107 msgid "Sort By" msgstr "" @@ -73,41 +73,41 @@ msgstr "" msgid "Descending" msgstr "" -#: bookwyrm/models/fields.py:24 +#: bookwyrm/models/fields.py:25 #, python-format msgid "%(value)s is not a valid remote_id" msgstr "" -#: bookwyrm/models/fields.py:33 bookwyrm/models/fields.py:42 +#: bookwyrm/models/fields.py:34 bookwyrm/models/fields.py:43 #, python-format msgid "%(value)s is not a valid username" msgstr "" -#: bookwyrm/models/fields.py:165 bookwyrm/templates/layout.html:155 +#: bookwyrm/models/fields.py:166 bookwyrm/templates/layout.html:152 msgid "username" msgstr "" -#: bookwyrm/models/fields.py:170 +#: bookwyrm/models/fields.py:171 msgid "A user with that username already exists." msgstr "" -#: bookwyrm/settings.py:155 +#: bookwyrm/settings.py:156 msgid "English" msgstr "" -#: bookwyrm/settings.py:156 +#: bookwyrm/settings.py:157 msgid "German" msgstr "" -#: bookwyrm/settings.py:157 +#: bookwyrm/settings.py:158 msgid "Spanish" msgstr "" -#: bookwyrm/settings.py:158 +#: bookwyrm/settings.py:159 msgid "French" msgstr "" -#: bookwyrm/settings.py:159 +#: bookwyrm/settings.py:160 msgid "Simplified Chinese" msgstr "" @@ -248,7 +248,7 @@ msgstr "" #: bookwyrm/templates/book/edit_book.html:263 #: bookwyrm/templates/lists/form.html:42 #: bookwyrm/templates/preferences/edit_user.html:70 -#: bookwyrm/templates/settings/announcement_form.html:65 +#: bookwyrm/templates/settings/announcement_form.html:69 #: bookwyrm/templates/settings/edit_server.html:68 #: bookwyrm/templates/settings/federated_server.html:98 #: bookwyrm/templates/settings/site.html:97 @@ -367,7 +367,7 @@ msgstr "" msgid "Places" msgstr "" -#: bookwyrm/templates/book/book.html:276 bookwyrm/templates/layout.html:64 +#: bookwyrm/templates/book/book.html:276 bookwyrm/templates/layout.html:61 #: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 #: bookwyrm/templates/search/layout.html:25 #: bookwyrm/templates/search/layout.html:50 @@ -381,7 +381,7 @@ msgstr "" #: bookwyrm/templates/book/book.html:297 #: bookwyrm/templates/book/cover_modal.html:31 -#: bookwyrm/templates/lists/list.html:164 +#: bookwyrm/templates/lists/list.html:179 msgid "Add" msgstr "" @@ -511,7 +511,7 @@ msgid "John Doe, Jane Smith" msgstr "" #: bookwyrm/templates/book/edit_book.html:183 -#: bookwyrm/templates/user/shelf/shelf.html:77 +#: bookwyrm/templates/user/shelf/shelf.html:78 msgid "Cover" msgstr "" @@ -630,7 +630,7 @@ msgstr "" #: bookwyrm/templates/directory/directory.html:4 #: bookwyrm/templates/directory/directory.html:9 -#: bookwyrm/templates/layout.html:92 +#: bookwyrm/templates/layout.html:64 msgid "Directory" msgstr "" @@ -831,7 +831,7 @@ msgid "Direct Messages with %(username)s" msgstr "" #: bookwyrm/templates/feed/direct_messages.html:10 -#: bookwyrm/templates/layout.html:87 +#: bookwyrm/templates/layout.html:92 msgid "Direct Messages" msgstr "" @@ -887,7 +887,6 @@ msgid "Updates" msgstr "" #: bookwyrm/templates/feed/feed_layout.html:10 -#: bookwyrm/templates/layout.html:58 #: bookwyrm/templates/user/shelf/books_header.html:3 msgid "Your books" msgstr "" @@ -942,7 +941,7 @@ msgid "What are you reading?" msgstr "" #: bookwyrm/templates/get_started/books.html:9 -#: bookwyrm/templates/lists/list.html:120 +#: bookwyrm/templates/lists/list.html:135 msgid "Search for a book" msgstr "" @@ -962,7 +961,7 @@ msgstr "" #: bookwyrm/templates/get_started/users.html:18 #: bookwyrm/templates/get_started/users.html:19 #: bookwyrm/templates/layout.html:37 bookwyrm/templates/layout.html:38 -#: bookwyrm/templates/lists/list.html:124 +#: bookwyrm/templates/lists/list.html:139 #: bookwyrm/templates/search/layout.html:4 #: bookwyrm/templates/search/layout.html:9 msgid "Search" @@ -978,7 +977,7 @@ msgid "Popular on %(site_name)s" msgstr "" #: bookwyrm/templates/get_started/books.html:58 -#: bookwyrm/templates/lists/list.html:137 +#: bookwyrm/templates/lists/list.html:152 msgid "No books found" msgstr "" @@ -1090,7 +1089,7 @@ msgid "%(username)s's %(year)s Books" msgstr "" #: bookwyrm/templates/import.html:5 bookwyrm/templates/import.html:9 -#: bookwyrm/templates/layout.html:97 +#: bookwyrm/templates/user/shelf/shelf.html:40 msgid "Import Books" msgstr "" @@ -1175,14 +1174,14 @@ msgstr "" #: bookwyrm/templates/import_status.html:114 #: bookwyrm/templates/snippets/create_status_form.html:13 -#: bookwyrm/templates/user/shelf/shelf.html:78 -#: bookwyrm/templates/user/shelf/shelf.html:98 +#: bookwyrm/templates/user/shelf/shelf.html:79 +#: bookwyrm/templates/user/shelf/shelf.html:99 msgid "Title" msgstr "" #: bookwyrm/templates/import_status.html:117 -#: bookwyrm/templates/user/shelf/shelf.html:79 -#: bookwyrm/templates/user/shelf/shelf.html:101 +#: bookwyrm/templates/user/shelf/shelf.html:80 +#: bookwyrm/templates/user/shelf/shelf.html:102 msgid "Author" msgstr "" @@ -1224,15 +1223,19 @@ msgstr "" msgid "Main navigation menu" msgstr "" -#: bookwyrm/templates/layout.html:61 +#: bookwyrm/templates/layout.html:58 msgid "Feed" msgstr "" -#: bookwyrm/templates/layout.html:102 +#: bookwyrm/templates/layout.html:87 +msgid "Your Books" +msgstr "" + +#: bookwyrm/templates/layout.html:97 msgid "Settings" msgstr "" -#: bookwyrm/templates/layout.html:111 +#: bookwyrm/templates/layout.html:106 #: bookwyrm/templates/settings/admin_layout.html:31 #: bookwyrm/templates/settings/manage_invite_requests.html:15 #: bookwyrm/templates/settings/manage_invites.html:3 @@ -1240,45 +1243,45 @@ msgstr "" msgid "Invites" msgstr "" -#: bookwyrm/templates/layout.html:118 +#: bookwyrm/templates/layout.html:113 msgid "Admin" msgstr "" -#: bookwyrm/templates/layout.html:125 +#: bookwyrm/templates/layout.html:120 msgid "Log out" msgstr "" -#: bookwyrm/templates/layout.html:133 bookwyrm/templates/layout.html:134 +#: bookwyrm/templates/layout.html:128 bookwyrm/templates/layout.html:129 #: bookwyrm/templates/notifications.html:6 #: bookwyrm/templates/notifications.html:11 msgid "Notifications" msgstr "" -#: bookwyrm/templates/layout.html:154 bookwyrm/templates/layout.html:158 +#: bookwyrm/templates/layout.html:151 bookwyrm/templates/layout.html:155 #: bookwyrm/templates/login.html:17 #: bookwyrm/templates/snippets/register_form.html:4 msgid "Username:" msgstr "" -#: bookwyrm/templates/layout.html:159 +#: bookwyrm/templates/layout.html:156 msgid "password" msgstr "" -#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:36 +#: bookwyrm/templates/layout.html:157 bookwyrm/templates/login.html:36 msgid "Forgot your password?" msgstr "" -#: bookwyrm/templates/layout.html:163 bookwyrm/templates/login.html:10 +#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:10 #: bookwyrm/templates/login.html:33 msgid "Log in" msgstr "" -#: bookwyrm/templates/layout.html:171 +#: bookwyrm/templates/layout.html:168 msgid "Join" msgstr "" #: bookwyrm/templates/layout.html:206 -msgid "About this server" +msgid "About this instance" msgstr "" #: bookwyrm/templates/layout.html:210 @@ -1338,7 +1341,7 @@ msgid "Discard" msgstr "" #: bookwyrm/templates/lists/edit_form.html:5 -#: bookwyrm/templates/lists/list_layout.html:17 +#: bookwyrm/templates/lists/list_layout.html:16 msgid "Edit List" msgstr "" @@ -1385,54 +1388,55 @@ msgstr "" msgid "This list is currently empty" msgstr "" -#: bookwyrm/templates/lists/list.html:65 +#: bookwyrm/templates/lists/list.html:66 #, python-format msgid "Added by %(username)s" msgstr "" -#: bookwyrm/templates/lists/list.html:77 -msgid "Set" -msgstr "" - -#: bookwyrm/templates/lists/list.html:80 +#: bookwyrm/templates/lists/list.html:74 msgid "List position" msgstr "" -#: bookwyrm/templates/lists/list.html:86 +#: bookwyrm/templates/lists/list.html:81 +msgid "Set" +msgstr "" + +#: bookwyrm/templates/lists/list.html:89 #: bookwyrm/templates/snippets/shelf_selector.html:26 msgid "Remove" msgstr "" -#: bookwyrm/templates/lists/list.html:99 bookwyrm/templates/lists/list.html:111 +#: bookwyrm/templates/lists/list.html:103 +#: bookwyrm/templates/lists/list.html:120 msgid "Sort List" msgstr "" -#: bookwyrm/templates/lists/list.html:105 +#: bookwyrm/templates/lists/list.html:113 msgid "Direction" msgstr "" -#: bookwyrm/templates/lists/list.html:116 +#: bookwyrm/templates/lists/list.html:127 msgid "Add Books" msgstr "" -#: bookwyrm/templates/lists/list.html:116 +#: bookwyrm/templates/lists/list.html:129 msgid "Suggest Books" msgstr "" -#: bookwyrm/templates/lists/list.html:125 +#: bookwyrm/templates/lists/list.html:140 msgid "search" msgstr "" -#: bookwyrm/templates/lists/list.html:131 +#: bookwyrm/templates/lists/list.html:146 msgid "Clear search" msgstr "" -#: bookwyrm/templates/lists/list.html:136 +#: bookwyrm/templates/lists/list.html:151 #, python-format msgid "No books found matching the query \"%(query)s\"" msgstr "" -#: bookwyrm/templates/lists/list.html:164 +#: bookwyrm/templates/lists/list.html:179 msgid "Suggest" msgstr "" @@ -1523,7 +1527,7 @@ msgstr "" #: bookwyrm/templates/moderation/reports.html:6 #, python-format -msgid "Reports: %(server_name)s" +msgid "Reports: %(instance_name)s" msgstr "" #: bookwyrm/templates/moderation/reports.html:8 @@ -1534,7 +1538,7 @@ msgstr "" #: bookwyrm/templates/moderation/reports.html:14 #, python-format -msgid "Reports: %(server_name)s" +msgid "Reports: %(instance_name)s" msgstr "" #: bookwyrm/templates/moderation/reports.html:28 @@ -1733,6 +1737,26 @@ msgstr "" msgid "Relationships" msgstr "" +#: bookwyrm/templates/rss/title.html:5 +#: bookwyrm/templates/snippets/status/status_header.html:35 +msgid "rated" +msgstr "" + +#: bookwyrm/templates/rss/title.html:7 +#: bookwyrm/templates/snippets/status/status_header.html:37 +msgid "reviewed" +msgstr "" + +#: bookwyrm/templates/rss/title.html:9 +#: bookwyrm/templates/snippets/status/status_header.html:39 +msgid "commented on" +msgstr "" + +#: bookwyrm/templates/rss/title.html:11 +#: bookwyrm/templates/snippets/status/status_header.html:41 +msgid "quoted" +msgstr "" + #: bookwyrm/templates/search/book.html:64 msgid "Load results from other catalogues" msgstr "" @@ -1783,7 +1807,7 @@ msgstr "" #: bookwyrm/templates/settings/admin_layout.html:39 #: bookwyrm/templates/settings/federation.html:3 #: bookwyrm/templates/settings/federation.html:5 -msgid "Federated Servers" +msgid "Federated Instances" msgstr "" #: bookwyrm/templates/settings/admin_layout.html:44 @@ -1833,6 +1857,7 @@ msgid "Back to list" msgstr "" #: bookwyrm/templates/settings/announcement.html:11 +#: bookwyrm/templates/settings/announcement_form.html:6 msgid "Edit Announcement" msgstr "" @@ -1866,7 +1891,7 @@ msgstr "" msgid "Active:" msgstr "" -#: bookwyrm/templates/settings/announcement_form.html:5 +#: bookwyrm/templates/settings/announcement_form.html:8 #: bookwyrm/templates/settings/announcements.html:8 msgid "Create Announcement" msgstr "" @@ -1910,12 +1935,12 @@ msgstr "" #: bookwyrm/templates/settings/federation.html:10 #: bookwyrm/templates/settings/server_blocklist.html:3 #: bookwyrm/templates/settings/server_blocklist.html:20 -msgid "Add server" +msgid "Add instance" msgstr "" #: bookwyrm/templates/settings/edit_server.html:7 #: bookwyrm/templates/settings/server_blocklist.html:7 -msgid "Back to server list" +msgid "Back to instance list" msgstr "" #: bookwyrm/templates/settings/edit_server.html:16 @@ -2022,7 +2047,7 @@ msgstr "" #: bookwyrm/templates/settings/federation.html:19 #: bookwyrm/templates/user_admin/server_filter.html:5 -msgid "Server name" +msgid "Instance name" msgstr "" #: bookwyrm/templates/settings/federation.html:23 @@ -2144,7 +2169,7 @@ msgid "Import Blocklist" msgstr "" #: bookwyrm/templates/settings/server_blocklist.html:26 -#: bookwyrm/templates/snippets/goal_progress.html:5 +#: bookwyrm/templates/snippets/goal_progress.html:7 msgid "Success!" msgstr "" @@ -2230,13 +2255,13 @@ msgstr "" msgid "%(title)s by " msgstr "" -#: bookwyrm/templates/snippets/boost_button.html:9 -#: bookwyrm/templates/snippets/boost_button.html:10 +#: bookwyrm/templates/snippets/boost_button.html:20 +#: bookwyrm/templates/snippets/boost_button.html:21 msgid "Boost" msgstr "" -#: bookwyrm/templates/snippets/boost_button.html:16 -#: bookwyrm/templates/snippets/boost_button.html:17 +#: bookwyrm/templates/snippets/boost_button.html:33 +#: bookwyrm/templates/snippets/boost_button.html:34 msgid "Un-boost" msgstr "" @@ -2326,13 +2351,13 @@ msgstr "" msgid "You are deleting this readthrough and its %(count)s associated progress updates." msgstr "" -#: bookwyrm/templates/snippets/fav_button.html:9 -#: bookwyrm/templates/snippets/fav_button.html:11 +#: bookwyrm/templates/snippets/fav_button.html:10 +#: bookwyrm/templates/snippets/fav_button.html:12 msgid "Like" msgstr "" -#: bookwyrm/templates/snippets/fav_button.html:17 #: bookwyrm/templates/snippets/fav_button.html:18 +#: bookwyrm/templates/snippets/fav_button.html:19 msgid "Un-like" msgstr "" @@ -2373,6 +2398,14 @@ msgstr "" msgid "No rating" msgstr "" +#: bookwyrm/templates/snippets/form_rate_stars.html:44 +#: bookwyrm/templates/snippets/stars.html:7 +#, python-format +msgid "%(rating)s star" +msgid_plural "%(rating)s stars" +msgstr[0] "" +msgstr[1] "" + #: bookwyrm/templates/snippets/generated_status/goal.html:1 #, python-format msgid "set a goal to read %(counter)s book in %(year)s" @@ -2427,17 +2460,17 @@ msgstr "" msgid "Set goal" msgstr "" -#: bookwyrm/templates/snippets/goal_progress.html:7 +#: bookwyrm/templates/snippets/goal_progress.html:9 #, python-format msgid "%(percent)s%% complete!" msgstr "" -#: bookwyrm/templates/snippets/goal_progress.html:10 +#: bookwyrm/templates/snippets/goal_progress.html:12 #, python-format msgid "You've read %(read_count)s of %(goal_count)s books." msgstr "" -#: bookwyrm/templates/snippets/goal_progress.html:12 +#: bookwyrm/templates/snippets/goal_progress.html:14 #, python-format msgid "%(username)s has read %(read_count)s of %(goal_count)s books." msgstr "" @@ -2546,26 +2579,6 @@ msgstr "" msgid "Report" msgstr "" -#: bookwyrm/templates/snippets/rss_title.html:5 -#: bookwyrm/templates/snippets/status/status_header.html:35 -msgid "rated" -msgstr "" - -#: bookwyrm/templates/snippets/rss_title.html:7 -#: bookwyrm/templates/snippets/status/status_header.html:37 -msgid "reviewed" -msgstr "" - -#: bookwyrm/templates/snippets/rss_title.html:9 -#: bookwyrm/templates/snippets/status/status_header.html:39 -msgid "commented on" -msgstr "" - -#: bookwyrm/templates/snippets/rss_title.html:11 -#: bookwyrm/templates/snippets/status/status_header.html:41 -msgid "quoted" -msgstr "" - #: bookwyrm/templates/snippets/search_result_text.html:36 msgid "Import book" msgstr "" @@ -2735,7 +2748,7 @@ msgstr "" msgid "Update shelf" msgstr "" -#: bookwyrm/templates/user/shelf/shelf.html:25 bookwyrm/views/shelf.py:51 +#: bookwyrm/templates/user/shelf/shelf.html:25 bookwyrm/views/shelf.py:56 msgid "All books" msgstr "" @@ -2743,30 +2756,30 @@ msgstr "" msgid "Create shelf" msgstr "" -#: bookwyrm/templates/user/shelf/shelf.html:61 +#: bookwyrm/templates/user/shelf/shelf.html:62 msgid "Edit shelf" msgstr "" -#: bookwyrm/templates/user/shelf/shelf.html:80 -#: bookwyrm/templates/user/shelf/shelf.html:104 +#: bookwyrm/templates/user/shelf/shelf.html:81 +#: bookwyrm/templates/user/shelf/shelf.html:105 msgid "Shelved" msgstr "" -#: bookwyrm/templates/user/shelf/shelf.html:81 -#: bookwyrm/templates/user/shelf/shelf.html:108 +#: bookwyrm/templates/user/shelf/shelf.html:82 +#: bookwyrm/templates/user/shelf/shelf.html:109 msgid "Started" msgstr "" -#: bookwyrm/templates/user/shelf/shelf.html:82 -#: bookwyrm/templates/user/shelf/shelf.html:111 +#: bookwyrm/templates/user/shelf/shelf.html:83 +#: bookwyrm/templates/user/shelf/shelf.html:112 msgid "Finished" msgstr "" -#: bookwyrm/templates/user/shelf/shelf.html:137 +#: bookwyrm/templates/user/shelf/shelf.html:138 msgid "This shelf is empty." msgstr "" -#: bookwyrm/templates/user/shelf/shelf.html:143 +#: bookwyrm/templates/user/shelf/shelf.html:144 msgid "Delete shelf" msgstr "" @@ -2825,7 +2838,7 @@ msgstr "" #: bookwyrm/templates/user_admin/user_admin.html:7 #, python-format -msgid "Users: %(server_name)s" +msgid "Users: %(instance_name)s" msgstr "" #: bookwyrm/templates/user_admin/user_admin.html:22 @@ -2842,7 +2855,7 @@ msgid "Last Active" msgstr "" #: bookwyrm/templates/user_admin/user_admin.html:38 -msgid "Remote server" +msgid "Remote instance" msgstr "" #: bookwyrm/templates/user_admin/user_admin.html:47 @@ -2886,6 +2899,10 @@ msgstr "" msgid "Access level:" msgstr "" +#: bookwyrm/templates/widgets/clearable_file_input_with_warning.html:3 +msgid "File exceeds maximum size: 10MB" +msgstr "" + #: bookwyrm/views/import_data.py:67 msgid "Not a valid csv file" msgstr "" diff --git a/locale/es/LC_MESSAGES/django.mo b/locale/es/LC_MESSAGES/django.mo index 1377b5092..54adfb570 100644 Binary files a/locale/es/LC_MESSAGES/django.mo and b/locale/es/LC_MESSAGES/django.mo differ diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index 4f60dfb8c..c9a19a4df 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-05-20 14:40-0700\n" +"POT-Creation-Date: 2021-06-06 20:52+0000\n" "PO-Revision-Date: 2021-03-19 11:49+0800\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -56,12 +56,12 @@ msgid "Book Title" msgstr "Título" #: bookwyrm/forms.py:301 bookwyrm/templates/snippets/create_status_form.html:34 -#: bookwyrm/templates/user/shelf/shelf.html:84 -#: bookwyrm/templates/user/shelf/shelf.html:115 +#: bookwyrm/templates/user/shelf/shelf.html:85 +#: bookwyrm/templates/user/shelf/shelf.html:116 msgid "Rating" msgstr "Calificación" -#: bookwyrm/forms.py:303 bookwyrm/templates/lists/list.html:101 +#: bookwyrm/forms.py:303 bookwyrm/templates/lists/list.html:107 msgid "Sort By" msgstr "Ordenar por" @@ -73,41 +73,41 @@ msgstr "Ascendente" msgid "Descending" msgstr "Descendente" -#: bookwyrm/models/fields.py:24 +#: bookwyrm/models/fields.py:25 #, python-format msgid "%(value)s is not a valid remote_id" msgstr "%(value)s no es un remote_id válido" -#: bookwyrm/models/fields.py:33 bookwyrm/models/fields.py:42 +#: bookwyrm/models/fields.py:34 bookwyrm/models/fields.py:43 #, python-format msgid "%(value)s is not a valid username" msgstr "%(value)s no es un usuario válido" -#: bookwyrm/models/fields.py:165 bookwyrm/templates/layout.html:155 +#: bookwyrm/models/fields.py:166 bookwyrm/templates/layout.html:152 msgid "username" msgstr "nombre de usuario" -#: bookwyrm/models/fields.py:170 +#: bookwyrm/models/fields.py:171 msgid "A user with that username already exists." msgstr "Ya existe un usuario con ese nombre." -#: bookwyrm/settings.py:155 +#: bookwyrm/settings.py:156 msgid "English" msgstr "Inglés" -#: bookwyrm/settings.py:156 +#: bookwyrm/settings.py:157 msgid "German" msgstr "Aléman" -#: bookwyrm/settings.py:157 +#: bookwyrm/settings.py:158 msgid "Spanish" msgstr "Español" -#: bookwyrm/settings.py:158 +#: bookwyrm/settings.py:159 msgid "French" msgstr "Francés" -#: bookwyrm/settings.py:159 +#: bookwyrm/settings.py:160 msgid "Simplified Chinese" msgstr "Chino simplificado" @@ -139,15 +139,15 @@ msgstr "Editar Autor/Autora" #: bookwyrm/templates/author/author.html:32 #: bookwyrm/templates/author/edit_author.html:38 msgid "Aliases:" -msgstr "" +msgstr "Aliases:" #: bookwyrm/templates/author/author.html:38 msgid "Born:" -msgstr "" +msgstr "Nacido:" #: bookwyrm/templates/author/author.html:44 msgid "Died:" -msgstr "" +msgstr "Muerto:" #: bookwyrm/templates/author/author.html:51 msgid "Wikipedia" @@ -160,10 +160,8 @@ msgstr "Ver en OpenLibrary" #: bookwyrm/templates/author/author.html:60 #: bookwyrm/templates/book/book.html:81 -#, fuzzy -#| msgid "View on OpenLibrary" msgid "View on Inventaire" -msgstr "Ver en OpenLibrary" +msgstr "Ver en Inventaire" #: bookwyrm/templates/author/author.html:74 #, python-format @@ -205,10 +203,8 @@ msgstr "Nombre:" #: bookwyrm/templates/book/edit_book.html:132 #: bookwyrm/templates/book/edit_book.html:141 #: bookwyrm/templates/book/edit_book.html:178 -#, fuzzy -#| msgid "Separate multiple publishers with commas." msgid "Separate multiple values with commas." -msgstr "Separar varios editores con comas." +msgstr "Separar varios valores con comas." #: bookwyrm/templates/author/edit_author.html:46 msgid "Bio:" @@ -236,10 +232,8 @@ msgstr "Clave OpenLibrary:" #: bookwyrm/templates/author/edit_author.html:79 #: bookwyrm/templates/book/edit_book.html:243 -#, fuzzy -#| msgid "View on OpenLibrary" msgid "Inventaire ID:" -msgstr "Ver en OpenLibrary" +msgstr "ID Inventaire:" #: bookwyrm/templates/author/edit_author.html:84 msgid "Librarything key:" @@ -254,7 +248,7 @@ msgstr "Clave Goodreads:" #: bookwyrm/templates/book/edit_book.html:263 #: bookwyrm/templates/lists/form.html:42 #: bookwyrm/templates/preferences/edit_user.html:70 -#: bookwyrm/templates/settings/announcement_form.html:65 +#: bookwyrm/templates/settings/announcement_form.html:69 #: bookwyrm/templates/settings/edit_server.html:68 #: bookwyrm/templates/settings/federated_server.html:98 #: bookwyrm/templates/settings/site.html:97 @@ -350,28 +344,20 @@ msgid "You don't have any reading activity for this book." msgstr "No tienes ninguna actividad de lectura para este libro." #: bookwyrm/templates/book/book.html:200 -#, fuzzy -#| msgid "Review" msgid "Reviews" -msgstr "Reseña" +msgstr "Reseñas" #: bookwyrm/templates/book/book.html:205 -#, fuzzy -#| msgid "Your shelves" msgid "Your reviews" -msgstr "Tus estantes" +msgstr "Tus reseñas" #: bookwyrm/templates/book/book.html:211 -#, fuzzy -#| msgid "Your Account" msgid "Your comments" -msgstr "Tu cuenta" +msgstr "Tus comentarios" #: bookwyrm/templates/book/book.html:217 -#, fuzzy -#| msgid "Your books" msgid "Your quotes" -msgstr "Tus libros" +msgstr "Tus citas" #: bookwyrm/templates/book/book.html:253 msgid "Subjects" @@ -381,7 +367,7 @@ msgstr "Sujetos" msgid "Places" msgstr "Lugares" -#: bookwyrm/templates/book/book.html:276 bookwyrm/templates/layout.html:64 +#: bookwyrm/templates/book/book.html:276 bookwyrm/templates/layout.html:61 #: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 #: bookwyrm/templates/search/layout.html:25 #: bookwyrm/templates/search/layout.html:50 @@ -395,7 +381,7 @@ msgstr "Agregar a lista" #: bookwyrm/templates/book/book.html:297 #: bookwyrm/templates/book/cover_modal.html:31 -#: bookwyrm/templates/lists/list.html:164 +#: bookwyrm/templates/lists/list.html:179 msgid "Add" msgstr "Agregar" @@ -492,10 +478,8 @@ msgid "Series number:" msgstr "Número de serie:" #: bookwyrm/templates/book/edit_book.html:130 -#, fuzzy -#| msgid "Language:" msgid "Languages:" -msgstr "Idioma:" +msgstr "Idiomas:" #: bookwyrm/templates/book/edit_book.html:139 msgid "Publisher:" @@ -527,7 +511,7 @@ msgid "John Doe, Jane Smith" msgstr "Juan Nadie, Natalia Natalia" #: bookwyrm/templates/book/edit_book.html:183 -#: bookwyrm/templates/user/shelf/shelf.html:77 +#: bookwyrm/templates/user/shelf/shelf.html:78 msgid "Cover" msgstr "Portada:" @@ -557,10 +541,8 @@ msgid "ISBN 10:" msgstr "ISBN 10:" #: bookwyrm/templates/book/edit_book.html:238 -#, fuzzy -#| msgid "Openlibrary key:" msgid "Openlibrary ID:" -msgstr "Clave OpenLibrary:" +msgstr "ID OpenLibrary:" #: bookwyrm/templates/book/editions.html:4 #, python-format @@ -648,7 +630,7 @@ msgstr "Comunidad federalizada" #: bookwyrm/templates/directory/directory.html:4 #: bookwyrm/templates/directory/directory.html:9 -#: bookwyrm/templates/layout.html:92 +#: bookwyrm/templates/layout.html:64 msgid "Directory" msgstr "Directorio" @@ -849,7 +831,7 @@ msgid "Direct Messages with %(username)s" msgstr "Mensajes directos con %(username)s" #: bookwyrm/templates/feed/direct_messages.html:10 -#: bookwyrm/templates/layout.html:87 +#: bookwyrm/templates/layout.html:92 msgid "Direct Messages" msgstr "Mensajes directos" @@ -905,7 +887,6 @@ msgid "Updates" msgstr "Actualizaciones" #: bookwyrm/templates/feed/feed_layout.html:10 -#: bookwyrm/templates/layout.html:58 #: bookwyrm/templates/user/shelf/books_header.html:3 msgid "Your books" msgstr "Tus libros" @@ -960,7 +941,7 @@ msgid "What are you reading?" msgstr "¿Qué estás leyendo?" #: bookwyrm/templates/get_started/books.html:9 -#: bookwyrm/templates/lists/list.html:120 +#: bookwyrm/templates/lists/list.html:135 msgid "Search for a book" msgstr "Buscar libros" @@ -980,7 +961,7 @@ msgstr "Puedes agregar libros cuando comiences a usar %(site_name)s." #: bookwyrm/templates/get_started/users.html:18 #: bookwyrm/templates/get_started/users.html:19 #: bookwyrm/templates/layout.html:37 bookwyrm/templates/layout.html:38 -#: bookwyrm/templates/lists/list.html:124 +#: bookwyrm/templates/lists/list.html:139 #: bookwyrm/templates/search/layout.html:4 #: bookwyrm/templates/search/layout.html:9 msgid "Search" @@ -996,7 +977,7 @@ msgid "Popular on %(site_name)s" msgstr "Popular en %(site_name)s" #: bookwyrm/templates/get_started/books.html:58 -#: bookwyrm/templates/lists/list.html:137 +#: bookwyrm/templates/lists/list.html:152 msgid "No books found" msgstr "No se encontró ningún libro" @@ -1108,7 +1089,7 @@ msgid "%(username)s's %(year)s Books" msgstr "Los libros de %(username)s para %(year)s" #: bookwyrm/templates/import.html:5 bookwyrm/templates/import.html:9 -#: bookwyrm/templates/layout.html:97 +#: bookwyrm/templates/user/shelf/shelf.html:40 msgid "Import Books" msgstr "Importar libros" @@ -1193,14 +1174,14 @@ msgstr "Libro" #: bookwyrm/templates/import_status.html:114 #: bookwyrm/templates/snippets/create_status_form.html:13 -#: bookwyrm/templates/user/shelf/shelf.html:78 -#: bookwyrm/templates/user/shelf/shelf.html:98 +#: bookwyrm/templates/user/shelf/shelf.html:79 +#: bookwyrm/templates/user/shelf/shelf.html:99 msgid "Title" msgstr "Título" #: bookwyrm/templates/import_status.html:117 -#: bookwyrm/templates/user/shelf/shelf.html:79 -#: bookwyrm/templates/user/shelf/shelf.html:101 +#: bookwyrm/templates/user/shelf/shelf.html:80 +#: bookwyrm/templates/user/shelf/shelf.html:102 msgid "Author" msgstr "Autor/Autora" @@ -1242,15 +1223,19 @@ msgstr "Buscar un libro o un usuario" msgid "Main navigation menu" msgstr "Menú de navigación central" -#: bookwyrm/templates/layout.html:61 +#: bookwyrm/templates/layout.html:58 msgid "Feed" msgstr "Actividad" -#: bookwyrm/templates/layout.html:102 +#: bookwyrm/templates/layout.html:87 +msgid "Your Books" +msgstr "Tus libros" + +#: bookwyrm/templates/layout.html:97 msgid "Settings" msgstr "Configuración" -#: bookwyrm/templates/layout.html:111 +#: bookwyrm/templates/layout.html:106 #: bookwyrm/templates/settings/admin_layout.html:31 #: bookwyrm/templates/settings/manage_invite_requests.html:15 #: bookwyrm/templates/settings/manage_invites.html:3 @@ -1258,45 +1243,47 @@ msgstr "Configuración" msgid "Invites" msgstr "Invitaciones" -#: bookwyrm/templates/layout.html:118 +#: bookwyrm/templates/layout.html:113 msgid "Admin" msgstr "Admin" -#: bookwyrm/templates/layout.html:125 +#: bookwyrm/templates/layout.html:120 msgid "Log out" msgstr "Cerrar sesión" -#: bookwyrm/templates/layout.html:133 bookwyrm/templates/layout.html:134 +#: bookwyrm/templates/layout.html:128 bookwyrm/templates/layout.html:129 #: bookwyrm/templates/notifications.html:6 #: bookwyrm/templates/notifications.html:11 msgid "Notifications" msgstr "Notificaciones" -#: bookwyrm/templates/layout.html:154 bookwyrm/templates/layout.html:158 +#: bookwyrm/templates/layout.html:151 bookwyrm/templates/layout.html:155 #: bookwyrm/templates/login.html:17 #: bookwyrm/templates/snippets/register_form.html:4 msgid "Username:" msgstr "Nombre de usuario:" -#: bookwyrm/templates/layout.html:159 +#: bookwyrm/templates/layout.html:156 msgid "password" msgstr "contraseña" -#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:36 +#: bookwyrm/templates/layout.html:157 bookwyrm/templates/login.html:36 msgid "Forgot your password?" msgstr "¿Olvidaste tu contraseña?" -#: bookwyrm/templates/layout.html:163 bookwyrm/templates/login.html:10 +#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:10 #: bookwyrm/templates/login.html:33 msgid "Log in" msgstr "Iniciar sesión" -#: bookwyrm/templates/layout.html:171 +#: bookwyrm/templates/layout.html:168 msgid "Join" msgstr "Unirse" #: bookwyrm/templates/layout.html:206 -msgid "About this server" +#, fuzzy +#| msgid "About this server" +msgid "About this instance" msgstr "Sobre este servidor" #: bookwyrm/templates/layout.html:210 @@ -1304,8 +1291,6 @@ msgid "Contact site admin" msgstr "Contactarse con administradores del sitio" #: bookwyrm/templates/layout.html:214 -#, fuzzy -#| msgid "Django Documentation" msgid "Documentation" msgstr "Documentación de Django" @@ -1358,7 +1343,7 @@ msgid "Discard" msgstr "Desechar" #: bookwyrm/templates/lists/edit_form.html:5 -#: bookwyrm/templates/lists/list_layout.html:17 +#: bookwyrm/templates/lists/list_layout.html:16 msgid "Edit List" msgstr "Editar lista" @@ -1395,66 +1380,65 @@ msgstr "Cualquer usuario puede agregar libros a esta lista" #: bookwyrm/templates/lists/list.html:20 msgid "You successfully suggested a book for this list!" -msgstr "" +msgstr "¡Has sugerido un libro para esta lista exitosamente!" #: bookwyrm/templates/lists/list.html:22 -#, fuzzy -#| msgid "Anyone can add books to this list" msgid "You successfully added a book to this list!" -msgstr "Cualquer usuario puede agregar libros a esta lista" +msgstr "¡Has agregado un libro a esta lista exitosamente!" #: bookwyrm/templates/lists/list.html:28 msgid "This list is currently empty" msgstr "Esta lista está vacia" -#: bookwyrm/templates/lists/list.html:65 +#: bookwyrm/templates/lists/list.html:66 #, python-format msgid "Added by %(username)s" msgstr "Agregado por %(username)s" -#: bookwyrm/templates/lists/list.html:77 -msgid "Set" -msgstr "Establecido" - -#: bookwyrm/templates/lists/list.html:80 +#: bookwyrm/templates/lists/list.html:74 msgid "List position" msgstr "Posición" -#: bookwyrm/templates/lists/list.html:86 +#: bookwyrm/templates/lists/list.html:81 +msgid "Set" +msgstr "Establecido" + +#: bookwyrm/templates/lists/list.html:89 #: bookwyrm/templates/snippets/shelf_selector.html:26 msgid "Remove" msgstr "Quitar" -#: bookwyrm/templates/lists/list.html:99 bookwyrm/templates/lists/list.html:111 +#: bookwyrm/templates/lists/list.html:103 +#: bookwyrm/templates/lists/list.html:120 msgid "Sort List" msgstr "Ordena la lista" -#: bookwyrm/templates/lists/list.html:105 +#: bookwyrm/templates/lists/list.html:113 msgid "Direction" msgstr "Dirección" -#: bookwyrm/templates/lists/list.html:116 +#: bookwyrm/templates/lists/list.html:127 msgid "Add Books" msgstr "Agregar libros" -#: bookwyrm/templates/lists/list.html:116 +#: bookwyrm/templates/lists/list.html:129 msgid "Suggest Books" msgstr "Sugerir libros" -#: bookwyrm/templates/lists/list.html:125 +#: bookwyrm/templates/lists/list.html:140 msgid "search" msgstr "buscar" -#: bookwyrm/templates/lists/list.html:131 +#: bookwyrm/templates/lists/list.html:146 msgid "Clear search" msgstr "Borrar búsqueda" -#: bookwyrm/templates/lists/list.html:136 +#: bookwyrm/templates/lists/list.html:151 #, python-format msgid "No books found matching the query \"%(query)s\"" msgstr "No se encontró ningún libro correspondiente a la búsqueda: \"%(query)s\"" -#: bookwyrm/templates/lists/list.html:164 +#: bookwyrm/templates/lists/list.html:179 msgid "Suggest" msgstr "Sugerir" @@ -1545,8 +1529,8 @@ msgstr "Resolver" #: bookwyrm/templates/moderation/reports.html:6 #, python-format -msgid "Reports: %(server_name)s" -msgstr "Informes: %(server_name)s" +msgid "Reports: %(instance_name)s" +msgstr "Informes: %(instance_name)s" #: bookwyrm/templates/moderation/reports.html:8 #: bookwyrm/templates/moderation/reports.html:17 @@ -1556,8 +1540,8 @@ msgstr "Informes" #: bookwyrm/templates/moderation/reports.html:14 #, python-format -msgid "Reports: %(server_name)s" -msgstr "Informes: %(server_name)s" +msgid "Reports: %(instance_name)s" +msgstr "Informes: %(instance_name)s" #: bookwyrm/templates/moderation/reports.html:28 msgid "Resolved" @@ -1573,13 +1557,11 @@ msgstr "Borrar notificaciones" #: bookwyrm/templates/notifications.html:25 msgid "All" -msgstr "" +msgstr "Todas" #: bookwyrm/templates/notifications.html:29 -#, fuzzy -#| msgid "More options" msgid "Mentions" -msgstr "Más opciones" +msgstr "Menciones" #: bookwyrm/templates/notifications.html:70 #, python-format @@ -1680,10 +1662,9 @@ msgid " suggested adding %(book_title)s t msgstr " sugirió agregar %(book_title)s a tu lista \"%(list_name)s\"" #: bookwyrm/templates/notifications.html:128 -#, fuzzy, python-format -#| msgid "Your import completed." +#, python-format msgid "Your import completed." -msgstr "Tu importación ha terminado." +msgstr "Tu importación ha terminado." #: bookwyrm/templates/notifications.html:131 #, python-format @@ -1758,31 +1739,45 @@ msgstr "Perfil" msgid "Relationships" msgstr "Relaciones" +#: bookwyrm/templates/rss/title.html:5 +#: bookwyrm/templates/snippets/status/status_header.html:35 +msgid "rated" +msgstr "calificó" + +#: bookwyrm/templates/rss/title.html:7 +#: bookwyrm/templates/snippets/status/status_header.html:37 +msgid "reviewed" +msgstr "reseñó" + +#: bookwyrm/templates/rss/title.html:9 +#: bookwyrm/templates/snippets/status/status_header.html:39 +msgid "commented on" +msgstr "comentó en" + +#: bookwyrm/templates/rss/title.html:11 +#: bookwyrm/templates/snippets/status/status_header.html:41 +msgid "quoted" +msgstr "citó" + #: bookwyrm/templates/search/book.html:64 -#, fuzzy -#| msgid "Show results from other catalogues" msgid "Load results from other catalogues" -msgstr "Mostrar resultados de otros catálogos" +msgstr "Cargar resultados de otros catálogos" #: bookwyrm/templates/search/book.html:68 msgid "Manually add book" -msgstr "" +msgstr "Agregar libro a mano" #: bookwyrm/templates/search/book.html:73 msgid "Log in to import or add books." -msgstr "" +msgstr "Iniciar una sesión para importar o agregar libros" #: bookwyrm/templates/search/layout.html:16 -#, fuzzy -#| msgid "Search for a user" msgid "Search query" -msgstr "Buscar un usuario" +msgstr "Búsqueda" #: bookwyrm/templates/search/layout.html:19 -#, fuzzy -#| msgid "Search" msgid "Search type" -msgstr "Buscar" +msgstr "Tipo de búsqueda" #: bookwyrm/templates/search/layout.html:21 #: bookwyrm/templates/search/layout.html:42 @@ -1799,10 +1794,9 @@ msgid "Users" msgstr "Usuarios" #: bookwyrm/templates/search/layout.html:58 -#, fuzzy, python-format -#| msgid "No lists found for \"%(query)s\"" +#, python-format msgid "No results found for \"%(query)s\"" -msgstr "No se encontró ningúna lista correspondiente a \"%(query)s\"" +msgstr "No se encontró ningún resultado correspondiente a \"%(query)s\"" #: bookwyrm/templates/settings/admin_layout.html:4 msgid "Administration" @@ -1815,7 +1809,9 @@ msgstr "Administrar usuarios" #: bookwyrm/templates/settings/admin_layout.html:39 #: bookwyrm/templates/settings/federation.html:3 #: bookwyrm/templates/settings/federation.html:5 -msgid "Federated Servers" +#, fuzzy +#| msgid "Federated Servers" +msgid "Federated Instances" msgstr "Servidores federalizados" #: bookwyrm/templates/settings/admin_layout.html:44 @@ -1856,23 +1852,18 @@ msgstr "Registración" #: bookwyrm/templates/settings/announcement.html:3 #: bookwyrm/templates/settings/announcement.html:6 -#, fuzzy -#| msgid "Announcements" msgid "Announcement" -msgstr "Anuncios" +msgstr "Anuncio" #: bookwyrm/templates/settings/announcement.html:7 #: bookwyrm/templates/settings/federated_server.html:13 -#, fuzzy -#| msgid "Back to server list" msgid "Back to list" msgstr "Volver a la lista de servidores" #: bookwyrm/templates/settings/announcement.html:11 -#, fuzzy -#| msgid "Announcements" +#: bookwyrm/templates/settings/announcement_form.html:6 msgid "Edit Announcement" -msgstr "Anuncios" +msgstr "Editar anuncio" #: bookwyrm/templates/settings/announcement.html:20 #: bookwyrm/templates/snippets/delete_readthrough_modal.html:15 @@ -1882,64 +1873,48 @@ msgstr "Eliminar" #: bookwyrm/templates/settings/announcement.html:35 msgid "Visible:" -msgstr "" +msgstr "Visible:" #: bookwyrm/templates/settings/announcement.html:38 msgid "True" -msgstr "" +msgstr "Verdadero" #: bookwyrm/templates/settings/announcement.html:40 msgid "False" -msgstr "" +msgstr "Falso" #: bookwyrm/templates/settings/announcement.html:47 -#, fuzzy -#| msgid "Birth date:" msgid "Start date:" -msgstr "Fecha de nacimiento:" +msgstr "Fecha de inicio:" #: bookwyrm/templates/settings/announcement.html:54 -#, fuzzy -#| msgid "Birth date:" msgid "End date:" -msgstr "Fecha de nacimiento:" +msgstr "Fecha final:" #: bookwyrm/templates/settings/announcement.html:60 -#, fuzzy -#| msgid "Active" msgid "Active:" -msgstr "Activ@" +msgstr "Activ@:" -#: bookwyrm/templates/settings/announcement_form.html:5 +#: bookwyrm/templates/settings/announcement_form.html:8 #: bookwyrm/templates/settings/announcements.html:8 -#, fuzzy -#| msgid "Announcements" msgid "Create Announcement" -msgstr "Anuncios" +msgstr "Crear anuncio" #: bookwyrm/templates/settings/announcements.html:22 -#, fuzzy -#| msgid "Date Added" msgid "Date added" msgstr "Fecha agregada" #: bookwyrm/templates/settings/announcements.html:26 -#, fuzzy -#| msgid "reviewed" msgid "Preview" -msgstr "reseñó" +msgstr "Vista preliminar" #: bookwyrm/templates/settings/announcements.html:30 -#, fuzzy -#| msgid "Started" msgid "Start date" -msgstr "Empezado" +msgstr "Fecha de inicio" #: bookwyrm/templates/settings/announcements.html:34 -#, fuzzy -#| msgid "Edit read dates" msgid "End date" -msgstr "Editar fechas de lectura" +msgstr "Fecha final" #: bookwyrm/templates/settings/announcements.html:38 #: bookwyrm/templates/settings/federation.html:30 @@ -1950,16 +1925,12 @@ msgid "Status" msgstr "Status" #: bookwyrm/templates/settings/announcements.html:48 -#, fuzzy -#| msgid "Inactive" msgid "active" -msgstr "Inactiv@" +msgstr "activo" #: bookwyrm/templates/settings/announcements.html:48 -#, fuzzy -#| msgid "Inactive" msgid "inactive" -msgstr "Inactiv@" +msgstr "inactivo" #: bookwyrm/templates/settings/edit_server.html:3 #: bookwyrm/templates/settings/edit_server.html:6 @@ -1968,12 +1939,16 @@ msgstr "Inactiv@" #: bookwyrm/templates/settings/federation.html:10 #: bookwyrm/templates/settings/server_blocklist.html:3 #: bookwyrm/templates/settings/server_blocklist.html:20 -msgid "Add server" -msgstr "Agregar servidor" +#, fuzzy +#| msgid "View instance" +msgid "Add instance" +msgstr "Ver instancia" #: bookwyrm/templates/settings/edit_server.html:7 #: bookwyrm/templates/settings/server_blocklist.html:7 -msgid "Back to server list" +#, fuzzy +#| msgid "Back to server list" +msgid "Back to instance list" msgstr "Volver a la lista de servidores" #: bookwyrm/templates/settings/edit_server.html:16 @@ -2080,8 +2055,10 @@ msgstr "Todos los usuarios en esta instancia serán re-activados." #: bookwyrm/templates/settings/federation.html:19 #: bookwyrm/templates/user_admin/server_filter.html:5 -msgid "Server name" -msgstr "Nombre de servidor" +#, fuzzy +#| msgid "Instance Name:" +msgid "Instance name" +msgstr "Nombre de instancia:" #: bookwyrm/templates/settings/federation.html:23 msgid "Date federated" @@ -2202,7 +2179,7 @@ msgid "Import Blocklist" msgstr "Importar lista de bloqueo" #: bookwyrm/templates/settings/server_blocklist.html:26 -#: bookwyrm/templates/snippets/goal_progress.html:5 +#: bookwyrm/templates/snippets/goal_progress.html:7 msgid "Success!" msgstr "¡Meta logrado!" @@ -2260,7 +2237,7 @@ msgstr "Correo electrónico de administradorx:" #: bookwyrm/templates/settings/site.html:73 msgid "Additional info:" -msgstr "" +msgstr "Más informacion:" #: bookwyrm/templates/settings/site.html:83 msgid "Allow registration:" @@ -2275,10 +2252,9 @@ msgid "Registration closed text:" msgstr "Texto de registración cerrada:" #: bookwyrm/templates/snippets/announcement.html:31 -#, fuzzy, python-format -#| msgid "Added by %(username)s" +#, python-format msgid "Posted by %(username)s" -msgstr "Agregado por %(username)s" +msgstr "Publicado por %(username)s" #: bookwyrm/templates/snippets/book_cover.html:31 msgid "No cover" @@ -2289,19 +2265,15 @@ msgstr "Sin portada" msgid "%(title)s by " msgstr "%(title)s por " -#: bookwyrm/templates/snippets/boost_button.html:9 -#: bookwyrm/templates/snippets/boost_button.html:10 -#, fuzzy -#| msgid "boosted" +#: bookwyrm/templates/snippets/boost_button.html:20 +#: bookwyrm/templates/snippets/boost_button.html:21 msgid "Boost" -msgstr "respaldó" +msgstr "Respaldar" -#: bookwyrm/templates/snippets/boost_button.html:16 -#: bookwyrm/templates/snippets/boost_button.html:17 -#, fuzzy -#| msgid "Un-boost status" +#: bookwyrm/templates/snippets/boost_button.html:33 +#: bookwyrm/templates/snippets/boost_button.html:34 msgid "Un-boost" -msgstr "Status de des-respaldo" +msgstr "Des-respaldar" #: bookwyrm/templates/snippets/content_warning_field.html:3 msgid "Spoiler alert:" @@ -2339,10 +2311,8 @@ msgid "Reply" msgstr "Respuesta" #: bookwyrm/templates/snippets/create_status_form.html:56 -#, fuzzy -#| msgid "Footer Content" msgid "Content" -msgstr "Contenido del pie de página" +msgstr "Contenido" #: bookwyrm/templates/snippets/create_status_form.html:80 #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:16 @@ -2391,17 +2361,15 @@ msgstr "¿Eliminar estas fechas de lectura?" msgid "You are deleting this readthrough and its %(count)s associated progress updates." msgstr "Estás eliminando esta lectura y sus %(count)s actualizaciones de progreso asociados." -#: bookwyrm/templates/snippets/fav_button.html:9 -#: bookwyrm/templates/snippets/fav_button.html:11 +#: bookwyrm/templates/snippets/fav_button.html:10 +#: bookwyrm/templates/snippets/fav_button.html:12 msgid "Like" -msgstr "" +msgstr "Me gusta" -#: bookwyrm/templates/snippets/fav_button.html:17 #: bookwyrm/templates/snippets/fav_button.html:18 -#, fuzzy -#| msgid "Un-like status" +#: bookwyrm/templates/snippets/fav_button.html:19 msgid "Un-like" -msgstr "Quitar me gusta de status" +msgstr "Quitar me gusta" #: bookwyrm/templates/snippets/filters_panel/filters_panel.html:7 msgid "Show filters" @@ -2440,6 +2408,14 @@ msgstr "Aceptar" msgid "No rating" msgstr "No calificación" +#: bookwyrm/templates/snippets/form_rate_stars.html:44 +#: bookwyrm/templates/snippets/stars.html:7 +#, python-format +msgid "%(rating)s star" +msgid_plural "%(rating)s stars" +msgstr[0] "%(rating)s estrella" +msgstr[1] "%(rating)s estrellas" + #: bookwyrm/templates/snippets/generated_status/goal.html:1 #, python-format msgid "set a goal to read %(counter)s book in %(year)s" @@ -2494,17 +2470,17 @@ msgstr "Compartir con tu feed" msgid "Set goal" msgstr "Establecer meta" -#: bookwyrm/templates/snippets/goal_progress.html:7 +#: bookwyrm/templates/snippets/goal_progress.html:9 #, python-format msgid "%(percent)s%% complete!" msgstr "%(percent)s%% terminado!" -#: bookwyrm/templates/snippets/goal_progress.html:10 +#: bookwyrm/templates/snippets/goal_progress.html:12 #, python-format msgid "You've read %(read_count)s of %(goal_count)s books." msgstr "Has leído %(read_count)s de %(goal_count)s libros." -#: bookwyrm/templates/snippets/goal_progress.html:12 +#: bookwyrm/templates/snippets/goal_progress.html:14 #, python-format msgid "%(username)s has read %(read_count)s of %(goal_count)s books." msgstr "%(username)s ha leído %(read_count)s de %(goal_count)s libros." @@ -2614,26 +2590,6 @@ msgstr "Inscribirse" msgid "Report" msgstr "Reportar" -#: bookwyrm/templates/snippets/rss_title.html:5 -#: bookwyrm/templates/snippets/status/status_header.html:35 -msgid "rated" -msgstr "calificó" - -#: bookwyrm/templates/snippets/rss_title.html:7 -#: bookwyrm/templates/snippets/status/status_header.html:37 -msgid "reviewed" -msgstr "reseñó" - -#: bookwyrm/templates/snippets/rss_title.html:9 -#: bookwyrm/templates/snippets/status/status_header.html:39 -msgid "commented on" -msgstr "comentó en" - -#: bookwyrm/templates/snippets/rss_title.html:11 -#: bookwyrm/templates/snippets/status/status_header.html:41 -msgid "quoted" -msgstr "citó" - #: bookwyrm/templates/snippets/search_result_text.html:36 msgid "Import book" msgstr "Importar libro" @@ -2706,7 +2662,7 @@ msgstr "Eliminar status" #: bookwyrm/templates/snippets/status/layout.html:51 #: bookwyrm/templates/snippets/status/layout.html:52 msgid "Boost status" -msgstr "Status de respaldo" +msgstr "Respaldar status" #: bookwyrm/templates/snippets/status/layout.html:55 #: bookwyrm/templates/snippets/status/layout.html:56 @@ -2803,7 +2759,7 @@ msgstr "Editar estante" msgid "Update shelf" msgstr "Actualizar estante" -#: bookwyrm/templates/user/shelf/shelf.html:25 bookwyrm/views/shelf.py:51 +#: bookwyrm/templates/user/shelf/shelf.html:25 bookwyrm/views/shelf.py:56 msgid "All books" msgstr "Todos los libros" @@ -2811,30 +2767,30 @@ msgstr "Todos los libros" msgid "Create shelf" msgstr "Crear estante" -#: bookwyrm/templates/user/shelf/shelf.html:61 +#: bookwyrm/templates/user/shelf/shelf.html:62 msgid "Edit shelf" msgstr "Editar estante" -#: bookwyrm/templates/user/shelf/shelf.html:80 -#: bookwyrm/templates/user/shelf/shelf.html:104 +#: bookwyrm/templates/user/shelf/shelf.html:81 +#: bookwyrm/templates/user/shelf/shelf.html:105 msgid "Shelved" msgstr "Archivado" -#: bookwyrm/templates/user/shelf/shelf.html:81 -#: bookwyrm/templates/user/shelf/shelf.html:108 +#: bookwyrm/templates/user/shelf/shelf.html:82 +#: bookwyrm/templates/user/shelf/shelf.html:109 msgid "Started" msgstr "Empezado" -#: bookwyrm/templates/user/shelf/shelf.html:82 -#: bookwyrm/templates/user/shelf/shelf.html:111 +#: bookwyrm/templates/user/shelf/shelf.html:83 +#: bookwyrm/templates/user/shelf/shelf.html:112 msgid "Finished" msgstr "Terminado" -#: bookwyrm/templates/user/shelf/shelf.html:137 +#: bookwyrm/templates/user/shelf/shelf.html:138 msgid "This shelf is empty." msgstr "Este estante está vacio." -#: bookwyrm/templates/user/shelf/shelf.html:143 +#: bookwyrm/templates/user/shelf/shelf.html:144 msgid "Delete shelf" msgstr "Eliminar estante" @@ -2881,13 +2837,11 @@ msgid "%(counter)s following" msgstr "%(counter)s siguiendo" #: bookwyrm/templates/user/user_preview.html:26 -#, fuzzy, python-format -#| msgid "%(mutuals)s follower you follow" -#| msgid_plural "%(mutuals)s followers you follow" +#, python-format msgid "%(mutuals_display)s follower you follow" msgid_plural "%(mutuals_display)s followers you follow" -msgstr[0] "%(mutuals)s seguidor que sigues" -msgstr[1] "%(mutuals)s seguidores que sigues" +msgstr[0] "%(mutuals_display)s seguidor que sigues" +msgstr[1] "%(mutuals_display)s seguidores que sigues" #: bookwyrm/templates/user_admin/user.html:9 msgid "Back to users" @@ -2895,8 +2849,8 @@ msgstr "Volver a usuarios" #: bookwyrm/templates/user_admin/user_admin.html:7 #, python-format -msgid "Users: %(server_name)s" -msgstr "Usuarios %(server_name)s" +msgid "Users: %(instance_name)s" +msgstr "Usuarios %(instance_name)s" #: bookwyrm/templates/user_admin/user_admin.html:22 #: bookwyrm/templates/user_admin/username_filter.html:5 @@ -2912,8 +2866,10 @@ msgid "Last Active" msgstr "Actividad reciente" #: bookwyrm/templates/user_admin/user_admin.html:38 -msgid "Remote server" -msgstr "Quitar servidor" +#, fuzzy +#| msgid "View instance" +msgid "Remote instance" +msgstr "Ver instancia" #: bookwyrm/templates/user_admin/user_admin.html:47 msgid "Active" @@ -2956,11 +2912,13 @@ msgstr "Des-suspender usuario" msgid "Access level:" msgstr "Nivel de acceso:" +#: bookwyrm/templates/widgets/clearable_file_input_with_warning.html:3 +msgid "File exceeds maximum size: 10MB" +msgstr "" + #: bookwyrm/views/import_data.py:67 -#, fuzzy -#| msgid "Enter a valid value." msgid "Not a valid csv file" -msgstr "Ingrese un valor válido." +msgstr "No un archivo csv válido" #: bookwyrm/views/password.py:32 msgid "No user with that email address was found." @@ -2971,6 +2929,18 @@ msgstr "No se pudo encontrar un usuario con esa dirección de correo electrónic msgid "A password reset link sent to %s" msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" +#~ msgid "Federated Servers" +#~ msgstr "Servidores federalizados" + +#~ msgid "Server name" +#~ msgstr "Nombre de servidor" + +#~ msgid "Add server" +#~ msgstr "Agregar servidor" + +#~ msgid "Remote server" +#~ msgstr "Quitar servidor" + #, fuzzy #~| msgid "BookWyrm users" #~ msgid "BookWyrm\\" @@ -2981,7 +2951,6 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "Show" #~ msgstr "Mostrar más" -#, python-format #~ msgid "ambiguous option: %(option)s could match %(matches)s" #~ msgstr "opción ambiguo: %(option)s pudiera coincidir con %(matches)s" @@ -3033,25 +3002,20 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "Enter only digits separated by commas." #~ msgstr "Ingrese solo digitos separados por comas." -#, python-format #~ msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." #~ msgstr "Asegura que este valor es %(limit_value)s (es %(show_value)s)." -#, python-format #~ msgid "Ensure this value is less than or equal to %(limit_value)s." #~ msgstr "Asegura que este valor es menor que o iguala a %(limit_value)s." -#, python-format #~ msgid "Ensure this value is greater than or equal to %(limit_value)s." #~ msgstr "Asegura que este valor es más que o que iguala a %(limit_value)s." -#, python-format #~ msgid "Ensure this value has at least %(limit_value)d character (it has %(show_value)d)." #~ msgid_plural "Ensure this value has at least %(limit_value)d characters (it has %(show_value)d)." #~ msgstr[0] "Verifica que este valor tiene por lo menos %(limit_value)d carácter. (Tiene %(show_value)d).)" #~ msgstr[1] "Verifica que este valor tiene por lo menos %(limit_value)d caracteres. (Tiene %(show_value)d).)" -#, python-format #~ msgid "Ensure this value has at most %(limit_value)d character (it has %(show_value)d)." #~ msgid_plural "Ensure this value has at most %(limit_value)d characters (it has %(show_value)d)." #~ msgstr[0] "Verifica que este valor tiene a lo sumo %(limit_value)d carácter. (Tiene %(show_value)d).)" @@ -3060,26 +3024,22 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "Enter a number." #~ msgstr "Ingrese un número." -#, python-format #~ msgid "Ensure that there are no more than %(max)s digit in total." #~ msgid_plural "Ensure that there are no more than %(max)s digits in total." #~ msgstr[0] "Verifica que no hay más que %(max)s digito en total." #~ msgstr[1] "Verifica que no hay más que %(max)s digitos en total." # is -#, python-format #~ msgid "Ensure that there are no more than %(max)s decimal place." #~ msgid_plural "Ensure that there are no more than %(max)s decimal places." #~ msgstr[0] "Verifica que no hay más que %(max)s cifra decimal." #~ msgstr[1] "Verifica que no hay más que %(max)s cifras decimales." -#, python-format #~ msgid "Ensure that there are no more than %(max)s digit before the decimal point." #~ msgid_plural "Ensure that there are no more than %(max)s digits before the decimal point." #~ msgstr[0] "Verifica que no hay más que %(max)s digito antes de la coma decimal." #~ msgstr[1] "Verifica que no hay más que %(max)s digitos antes de la coma decimal." -#, python-format #~ msgid "File extension “%(extension)s” is not allowed. Allowed extensions are: %(allowed_extensions)s." #~ msgstr "No se permite la extensión de archivo “%(extension)s”. Extensiones permitidas son: %(allowed_extensions)s." @@ -3089,11 +3049,9 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "and" #~ msgstr "y" -#, python-format #~ msgid "%(model_name)s with this %(field_labels)s already exists." #~ msgstr "Ya existe %(model_name)s con este %(field_labels)s." -#, python-format #~ msgid "Value %(value)r is not a valid choice." #~ msgstr "El valor %(value)s no es una opción válida." @@ -3103,67 +3061,56 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "This field cannot be blank." #~ msgstr "Este campo no puede ser vacio." -#, fuzzy, python-format +#, fuzzy #~| msgid "%(model_name)s with this %(field_label)s already exists." #~ msgid "%(model_name)s with this %(field_label)s already exists." #~ msgstr "Ya existe %(model_name)s con este %(field_labels)s." -#, python-format #~ msgid "%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." #~ msgstr "%(field_label)s deben ser unicos por %(date_field_label)s %(lookup_type)s." -#, python-format #~ msgid "Field of type: %(field_type)s" #~ msgstr "Campo de tipo: %(field_type)s" -#, python-format #~ msgid "“%(value)s” value must be either True or False." #~ msgstr "“%(value)s” valor debe ser o verdadero o falso." -#, python-format #~ msgid "“%(value)s” value must be either True, False, or None." #~ msgstr "%(value)s” valor debe ser o True, False, o None." #~ msgid "Boolean (Either True or False)" #~ msgstr "Booleano (O True O False)" -#, python-format #~ msgid "String (up to %(max_length)s)" #~ msgstr "Cadena (máximo de %(max_length)s caracteres)" #~ msgid "Comma-separated integers" #~ msgstr "Enteros separados por comas" -#, python-format #~ msgid "“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD format." #~ msgstr "“%(value)s” valor tiene un formato de fecha inválido. Hay que estar de formato YYYY-MM-DD." -#, python-format #~ msgid "“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid date." #~ msgstr "“%(value)s” valor tiene el formato correcto (YYYY-MM-DD) pero la fecha es invalida." #~ msgid "Date (without time)" #~ msgstr "Fecha (sin la hora)" -#, python-format #~ msgid "“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format." #~ msgstr "“%(value)s” valor tiene un formato invalido. Debe estar en formato YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]." -#, python-format #~ msgid "“%(value)s” value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) but it is an invalid date/time." #~ msgstr "“%(value)s” valor tiene el formato correcto (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) pero es una fecha/hora invalida." #~ msgid "Date (with time)" #~ msgstr "Fecha (con la hora)" -#, python-format #~ msgid "“%(value)s” value must be a decimal number." #~ msgstr "El valor de “%(value)s” debe ser un número decimal." #~ msgid "Decimal number" #~ msgstr "Número decimal" -#, python-format #~ msgid "“%(value)s” value has an invalid format. It must be in [DD] [[HH:]MM:]ss[.uuuuuu] format." #~ msgstr "“%(value)s” valor tiene un formato invalido. Debe estar en formato [DD] [[HH:]MM:]ss[.uuuuuu]." @@ -3176,14 +3123,12 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "File path" #~ msgstr "Ruta de archivo" -#, python-format #~ msgid "“%(value)s” value must be a float." #~ msgstr "%(value)s no es un usuario válido" #~ msgid "Floating point number" #~ msgstr "Número de coma flotante" -#, python-format #~ msgid "“%(value)s” value must be an integer." #~ msgstr "“%(value)s” valor debe ser un entero." @@ -3199,7 +3144,6 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "IP address" #~ msgstr "Dirección IP" -#, python-format #~ msgid "“%(value)s” value must be either None, True or False." #~ msgstr "Valor “%(value)s” debe ser o None, True, o False." @@ -3212,7 +3156,6 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "Positive small integer" #~ msgstr "Entero positivo pequeño " -#, python-format #~ msgid "Slug (up to %(max_length)s)" #~ msgstr "Slug (máximo de %(max_length)s)" @@ -3222,11 +3165,9 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "Text" #~ msgstr "Texto" -#, python-format #~ msgid "“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] format." #~ msgstr "“%(value)s” valor tiene un formato invalido. Debe estar en formato HH:MM[:ss[.uuuuuu]]." -#, python-format #~ msgid "“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an invalid time." #~ msgstr "“%(value)s” valor tiene el formato correcto (HH:MM[:ss[.uuuuuu]]) pero es una hora invalida." @@ -3239,7 +3180,6 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "Raw binary data" #~ msgstr "Datos binarios sin procesar" -#, python-format #~ msgid "“%(value)s” is not a valid UUID." #~ msgstr "%(value)s no es una UUID válida." @@ -3252,7 +3192,6 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "Image" #~ msgstr "Imágen" -#, python-format #~ msgid "%(model)s instance with %(field)s %(value)r does not exist." #~ msgstr "%(model)s instancia con %(field)s %(value)r no existe." @@ -3262,11 +3201,9 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "One-to-one relationship" #~ msgstr "Relación uno-a-uno" -#, python-format #~ msgid "%(from)s-%(to)s relationship" #~ msgstr "relación %(from)s-%(to)s" -#, python-format #~ msgid "%(from)s-%(to)s relationships" #~ msgstr "relaciones %(from)s-%(to)s" @@ -3291,7 +3228,6 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "Enter a valid duration." #~ msgstr "Ingrese una duración válida." -#, python-brace-format #~ msgid "The number of days must be between {min_days} and {max_days}." #~ msgstr "El número de dias debe ser entre {min_days} y {max_days}." @@ -3304,7 +3240,6 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "The submitted file is empty." #~ msgstr "El archivo enviado está vacio." -#, python-format #~ msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." #~ msgid_plural "Ensure this filename has at most %(max)d characters (it has %(length)d)." #~ msgstr[0] "Verifica que este nombre de archivo no tiene más que %(max)d carácter. (Tiene %(length)d)." @@ -3316,7 +3251,6 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "Upload a valid image. The file you uploaded was either not an image or a corrupted image." #~ msgstr "Subir una imagen válida. El archivo que subiste o no fue imagen o fue corrupto." -#, python-format #~ msgid "Select a valid choice. %(value)s is not one of the available choices." #~ msgstr "Selecciona una opción válida. %(value)s no es una de las opciones disponibles." @@ -3332,20 +3266,17 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid ":" #~ msgstr ":" -#, python-format #~ msgid "(Hidden field %(name)s) %(error)s" #~ msgstr "(Campo oculto %(name)s) %(error)s" #~ msgid "ManagementForm data is missing or has been tampered with" #~ msgstr "Datos de ManagementForm está ausento o ha sido corrompido" -#, python-format #~ msgid "Please submit %d or fewer forms." #~ msgid_plural "Please submit %d or fewer forms." #~ msgstr[0] "Por favor, enviar %d o menos formularios." #~ msgstr[1] "Por favor, enviar %d o menos formularios." -#, python-format #~ msgid "Please submit %d or more forms." #~ msgid_plural "Please submit %d or more forms." #~ msgstr[0] "Por favor, enviar %d o más formularios." @@ -3358,15 +3289,12 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" # if verb # msgstr "Pedido" # if noun -#, python-format #~ msgid "Please correct the duplicate data for %(field)s." #~ msgstr "Por favor corrige los datos duplicados en %(field)s." -#, python-format #~ msgid "Please correct the duplicate data for %(field)s, which must be unique." #~ msgstr "Por favor corrige los datos duplicados en %(field)s, los cuales deben ser unicos." -#, python-format #~ msgid "Please correct the duplicate data for %(field_name)s which must be unique for the %(lookup)s in %(date_field)s." #~ msgstr "Por favor corrige los datos duplicados en %(field_name)s los cuales deben ser unicos por el %(lookup)s en %(date_field)s." @@ -3379,11 +3307,9 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "Select a valid choice. That choice is not one of the available choices." #~ msgstr "Selecciona una opción válida. Esa opción no es una de las opciones disponibles." -#, python-format #~ msgid "“%(pk)s” is not a valid value." #~ msgstr "“%(pk)s” no es un valor válido." -#, python-format #~ msgid "%(datetime)s couldn’t be interpreted in time zone %(current_timezone)s; it may be ambiguous or it may not exist." #~ msgstr "%(datetime)s no se pudo interpretar en la zona horaria %(current_timezone)s; puede ser ambiguo o puede que no exista." @@ -3408,29 +3334,23 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "yes,no,maybe" #~ msgstr "sí,no,quizás" -#, python-format #~ msgid "%(size)d byte" #~ msgid_plural "%(size)d bytes" #~ msgstr[0] "%(size)d byte" #~ msgstr[1] "%(size)d bytes" -#, python-format #~ msgid "%s KB" #~ msgstr "%s KB" -#, python-format #~ msgid "%s MB" #~ msgstr "%s MB" -#, python-format #~ msgid "%s GB" #~ msgstr "%s GB" -#, python-format #~ msgid "%s TB" #~ msgstr "%s TB" -#, python-format #~ msgid "%s PB" #~ msgstr "%s PB" @@ -3665,7 +3585,6 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "This is not a valid IPv6 address." #~ msgstr "Esta no es una dirección IPv6 válida." -#, python-format #~ msgctxt "String to return when truncating text" #~ msgid "%(truncated_text)s…" #~ msgstr "%(truncated_text)s…" @@ -3676,37 +3595,31 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid ", " #~ msgstr ", " -#, python-format #~ msgid "%d year" #~ msgid_plural "%d years" #~ msgstr[0] "%d año" #~ msgstr[1] "%d años" -#, python-format #~ msgid "%d month" #~ msgid_plural "%d months" #~ msgstr[0] "%d mes" #~ msgstr[1] "%d meses" -#, python-format #~ msgid "%d week" #~ msgid_plural "%d weeks" #~ msgstr[0] "%d semana" #~ msgstr[1] "%d semanas" -#, python-format #~ msgid "%d day" #~ msgid_plural "%d days" #~ msgstr[0] "%d día" #~ msgstr[1] "%d días" -#, python-format #~ msgid "%d hour" #~ msgid_plural "%d hours" #~ msgstr[0] "%d hora" #~ msgstr[1] "%d horas" -#, python-format #~ msgid "%d minute" #~ msgid_plural "%d minutes" #~ msgstr[0] "%d minuto" @@ -3757,55 +3670,45 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "No week specified" #~ msgstr "Ninguna semana fue especificado" -#, python-format #~ msgid "No %(verbose_name_plural)s available" #~ msgstr "No %(verbose_name_plural)s disponible" -#, python-format #~ msgid "Future %(verbose_name_plural)s not available because %(class_name)s.allow_future is False." #~ msgstr "%(verbose_name_plural)s del futuro no está disponible porque %(class_name)s.allow_future es False." -#, python-format #~ msgid "Invalid date string “%(datestr)s” given format “%(format)s”" #~ msgstr "Cadena de fecha invalida “%(datestr)s” dado el formato “%(format)s”" -#, python-format #~ msgid "No %(verbose_name)s found matching the query" #~ msgstr "No se encontró ningún %(verbose_name)s correspondiente a la búsqueda" #~ msgid "Page is not “last”, nor can it be converted to an int." #~ msgstr "Página no es “last”, ni puede ser convertido en un int." -#, python-format #~ msgid "Invalid page (%(page_number)s): %(message)s" #~ msgstr "Página invalida (%(page_number)s): %(message)s" -#, python-format #~ msgid "Empty list and “%(class_name)s.allow_empty” is False." #~ msgstr "Lista vacia y “%(class_name)s.allow_empty” es False." #~ msgid "Directory indexes are not allowed here." #~ msgstr "Indices directorios no se permiten aquí." -#, python-format #~ msgid "“%(path)s” does not exist" #~ msgstr "“%(path)s” no existe" -#, python-format #~ msgid "Index of %(directory)s" #~ msgstr "Indice de %(directory)s" #~ msgid "Django: the Web framework for perfectionists with deadlines." #~ msgstr "Django: el estructura Web para perfeccionistas con fechas límites." -#, python-format #~ msgid "View release notes for Django %(version)s" #~ msgstr "Ver notas de lanzamiento por Django %(version)s" #~ msgid "The install worked successfully! Congratulations!" #~ msgstr "¡La instalación fue exitoso! ¡Felicidades!" -#, python-format #~ msgid "You are seeing this page because DEBUG=True is in your settings file and you have not configured any URLs." #~ msgstr "Estás viendo esta pagina porque DEBUG=True está en tu archivo de configuración y no has configurado ningún URL." @@ -3827,15 +3730,12 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "Connect, get help, or contribute" #~ msgstr "Conectarse, encontrar ayuda, o contribuir" -#, python-format #~ msgid "Attempting to connect to qpid with SASL mechanism %s" #~ msgstr "Intentando conectar con qpid con mecanismo SASL %s" -#, python-format #~ msgid "Connected to qpid with SASL mechanism %s" #~ msgstr "Conectado con qpid con mecanismo SASL %s" -#, python-format #~ msgid "Unable to connect to qpid with SASL mechanism %s" #~ msgstr "No se pudo conectar con qpid con mecanismo SASL %s" @@ -3848,7 +3748,6 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "1 hour ago" #~ msgstr "Hace 1 hora" -#, python-format #~ msgid "%(time)s" #~ msgstr "%(time)s" @@ -3857,45 +3756,36 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" # TODO cc @mouse this could be grammatically incorrect if the time said 1 o'clock # a working clock is broken twice a day! -#, python-format #~ msgid "yesterday at %(time)s" #~ msgstr "ayer a las %(time)s" -#, python-format #~ msgid "%(weekday)s" #~ msgstr "%(weekday)s" # TODO cc @mouse this could be grammatically incorrect if the time said 1 o'clock # a working clock is broken twice a day! -#, python-format #~ msgid "%(weekday)s at %(time)s" #~ msgstr "%(weekday)s a las %(time)s" -#, python-format #~ msgid "%(month_name)s %(day)s" #~ msgstr "%(day)s %(month_name)s" # TODO cc @mouse this could be grammatically incorrect if the time said 1 o'clock # a working clock is broken twice a day! -#, python-format #~ msgid "%(month_name)s %(day)s at %(time)s" #~ msgstr "%(day)s %(month_name)s a las %(time)s" -#, python-format #~ msgid "%(month_name)s %(day)s, %(year)s" #~ msgstr "%(day)s %(month_name)s, %(year)s" # TODO cc @mouse this could be grammatically incorrect if the time said 1 o'clock # a working clock is broken twice a day! -#, python-format #~ msgid "%(month_name)s %(day)s, %(year)s at %(time)s" #~ msgstr "%(day)s %(month_name)s, %(year)s a las %(time)s" -#, python-format #~ msgid "%(weekday)s, %(month_name)s %(day)s" #~ msgstr "%(weekday)s, %(day)s %(month_name)s" -#, python-format #~ msgid "%(commas)s and %(last)s" #~ msgstr "%(commas)s y %(last)s" @@ -3924,29 +3814,18 @@ msgstr "Un enlace para reestablecer tu contraseña se enviará a %s" #~ msgid "Matching Users" #~ msgstr "Usuarios correspondientes" -#, python-format #~ msgid "Set a reading goal for %(year)s" #~ msgstr "Establecer una meta de lectura para %(year)s" -#, python-format #~ msgid "by %(author)s" #~ msgstr "por %(author)s" -#, python-format -#~ msgid "%(rating)s star" -#~ msgid_plural "%(rating)s stars" -#~ msgstr[0] "%(rating)s estrella" -#~ msgstr[1] "%(rating)s estrellas" - -#, python-format #~ msgid "replied to %(username)s's review" #~ msgstr "respondió a la reseña de %(username)s " -#, python-format #~ msgid "replied to %(username)s's comment" #~ msgstr "respondió al comentario de %(username)s " -#, python-format #~ msgid "replied to %(username)s's quote" #~ msgstr "respondió a la cita de %(username)s " diff --git a/locale/fr_FR/LC_MESSAGES/django.mo b/locale/fr_FR/LC_MESSAGES/django.mo index 8d377f420..3d622ac66 100644 Binary files a/locale/fr_FR/LC_MESSAGES/django.mo and b/locale/fr_FR/LC_MESSAGES/django.mo differ diff --git a/locale/fr_FR/LC_MESSAGES/django.po b/locale/fr_FR/LC_MESSAGES/django.po index 577614544..d29afc1cf 100644 --- a/locale/fr_FR/LC_MESSAGES/django.po +++ b/locale/fr_FR/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.1.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-05-20 14:40-0700\n" +"POT-Creation-Date: 2021-06-09 16:46+0000\n" "PO-Revision-Date: 2021-04-05 12:44+0100\n" "Last-Translator: Fabien Basmaison \n" "Language-Team: Mouse Reeve \n" @@ -49,71 +49,65 @@ msgstr "Sans limite" #: bookwyrm/forms.py:299 msgid "List Order" -msgstr "" +msgstr "Ordre de la liste" #: bookwyrm/forms.py:300 -#, fuzzy -#| msgid "Title" msgid "Book Title" -msgstr "Titre" +msgstr "Titre du livre" #: bookwyrm/forms.py:301 bookwyrm/templates/snippets/create_status_form.html:34 -#: bookwyrm/templates/user/shelf/shelf.html:84 -#: bookwyrm/templates/user/shelf/shelf.html:115 +#: bookwyrm/templates/user/shelf/shelf.html:85 +#: bookwyrm/templates/user/shelf/shelf.html:116 msgid "Rating" msgstr "Note" -#: bookwyrm/forms.py:303 bookwyrm/templates/lists/list.html:101 +#: bookwyrm/forms.py:303 bookwyrm/templates/lists/list.html:107 msgid "Sort By" -msgstr "" +msgstr "Trier par" #: bookwyrm/forms.py:307 -#, fuzzy -#| msgid "Sorted ascending" msgid "Ascending" -msgstr "Trié par ordre croissant" +msgstr "Ordre croissant" #: bookwyrm/forms.py:308 -#, fuzzy -#| msgid "Sorted ascending" msgid "Descending" -msgstr "Trié par ordre croissant" +msgstr "Ordre décroissant" -#: bookwyrm/models/fields.py:24 +#: bookwyrm/models/fields.py:25 #, python-format msgid "%(value)s is not a valid remote_id" msgstr "%(value)s n’est pas une remote_id valide." -#: bookwyrm/models/fields.py:33 bookwyrm/models/fields.py:42 +#: bookwyrm/models/fields.py:34 bookwyrm/models/fields.py:43 #, python-format msgid "%(value)s is not a valid username" msgstr "%(value)s n’est pas un nom de compte valide." -#: bookwyrm/models/fields.py:165 bookwyrm/templates/layout.html:155 +#: bookwyrm/models/fields.py:166 bookwyrm/templates/layout.html:152 msgid "username" msgstr "nom du compte :" -#: bookwyrm/models/fields.py:170 +#: bookwyrm/models/fields.py:171 msgid "A user with that username already exists." msgstr "Ce nom est déjà associé à un compte." -#: bookwyrm/settings.py:155 +#: bookwyrm/settings.py:156 msgid "English" msgstr "English" -#: bookwyrm/settings.py:156 +#: bookwyrm/settings.py:157 msgid "German" msgstr "Deutsch" -#: bookwyrm/settings.py:157 +#: bookwyrm/settings.py:158 msgid "Spanish" msgstr "Español" -#: bookwyrm/settings.py:158 +#: bookwyrm/settings.py:159 msgid "French" msgstr "Français" -#: bookwyrm/settings.py:159 +#: bookwyrm/settings.py:160 msgid "Simplified Chinese" msgstr "简化字" @@ -142,36 +136,42 @@ msgstr "Une erreur s’est produite ; désolé !" msgid "Edit Author" msgstr "Modifier l’auteur ou autrice" -#: bookwyrm/templates/author/author.html:32 -#: bookwyrm/templates/author/edit_author.html:38 +#: bookwyrm/templates/author/author.html:34 +#: bookwyrm/templates/author/edit_author.html:41 msgid "Aliases:" -msgstr "" +msgstr "Pseudonymes :" -#: bookwyrm/templates/author/author.html:38 +#: bookwyrm/templates/author/author.html:45 msgid "Born:" -msgstr "" +msgstr "Naissance :" -#: bookwyrm/templates/author/author.html:44 +#: bookwyrm/templates/author/author.html:52 msgid "Died:" -msgstr "" +msgstr "Décès :" -#: bookwyrm/templates/author/author.html:51 +#: bookwyrm/templates/author/author.html:61 msgid "Wikipedia" msgstr "Wikipedia" -#: bookwyrm/templates/author/author.html:55 -#: bookwyrm/templates/book/book.html:78 +#: bookwyrm/templates/author/author.html:69 +#: bookwyrm/templates/book/book.html:87 msgid "View on OpenLibrary" msgstr "Voir sur OpenLibrary" -#: bookwyrm/templates/author/author.html:60 -#: bookwyrm/templates/book/book.html:81 -#, fuzzy -#| msgid "View on OpenLibrary" +#: bookwyrm/templates/author/author.html:77 +#: bookwyrm/templates/book/book.html:90 msgid "View on Inventaire" -msgstr "Voir sur OpenLibrary" +msgstr "Voir sur Inventaire" -#: bookwyrm/templates/author/author.html:74 +#: bookwyrm/templates/author/author.html:85 +msgid "View on LibraryThing" +msgstr "Voir sur LibraryThing" + +#: bookwyrm/templates/author/author.html:93 +msgid "View on Goodreads" +msgstr "Voir sur Goodreads" + +#: bookwyrm/templates/author/author.html:108 #, python-format msgid "Books by %(name)s" msgstr "Livres par %(name)s" @@ -181,89 +181,85 @@ msgid "Edit Author:" msgstr "Modifier l’auteur ou l’autrice :" #: bookwyrm/templates/author/edit_author.html:13 -#: bookwyrm/templates/book/edit_book.html:18 +#: bookwyrm/templates/book/edit_book.html:19 msgid "Added:" -msgstr "AJouté :" +msgstr "Ajouté :" #: bookwyrm/templates/author/edit_author.html:14 -#: bookwyrm/templates/book/edit_book.html:19 +#: bookwyrm/templates/book/edit_book.html:24 msgid "Updated:" msgstr "Mis à jour :" #: bookwyrm/templates/author/edit_author.html:15 -#: bookwyrm/templates/book/edit_book.html:20 +#: bookwyrm/templates/book/edit_book.html:30 msgid "Last edited by:" msgstr "Dernière modification par :" #: bookwyrm/templates/author/edit_author.html:31 -#: bookwyrm/templates/book/edit_book.html:90 +#: bookwyrm/templates/book/edit_book.html:117 msgid "Metadata" msgstr "Métadonnées" -#: bookwyrm/templates/author/edit_author.html:32 +#: bookwyrm/templates/author/edit_author.html:33 #: bookwyrm/templates/lists/form.html:8 #: bookwyrm/templates/user/shelf/create_shelf_form.html:13 #: bookwyrm/templates/user/shelf/edit_shelf_form.html:14 msgid "Name:" msgstr "Nom :" -#: bookwyrm/templates/author/edit_author.html:40 -#: bookwyrm/templates/book/edit_book.html:132 -#: bookwyrm/templates/book/edit_book.html:141 -#: bookwyrm/templates/book/edit_book.html:178 -#, fuzzy -#| msgid "Separate multiple publishers with commas." +#: bookwyrm/templates/author/edit_author.html:43 +#: bookwyrm/templates/book/edit_book.html:162 +#: bookwyrm/templates/book/edit_book.html:171 +#: bookwyrm/templates/book/edit_book.html:214 msgid "Separate multiple values with commas." -msgstr "Séparez plusieurs éditeurs par une virgule." +msgstr "Séparez plusieurs valeurs par une virgule." -#: bookwyrm/templates/author/edit_author.html:46 +#: bookwyrm/templates/author/edit_author.html:50 msgid "Bio:" msgstr "Bio :" -#: bookwyrm/templates/author/edit_author.html:51 +#: bookwyrm/templates/author/edit_author.html:57 msgid "Wikipedia link:" msgstr "Wikipedia :" -#: bookwyrm/templates/author/edit_author.html:57 +#: bookwyrm/templates/author/edit_author.html:63 msgid "Birth date:" msgstr "Date de naissance :" -#: bookwyrm/templates/author/edit_author.html:65 +#: bookwyrm/templates/author/edit_author.html:71 msgid "Death date:" msgstr "Date de décès :" -#: bookwyrm/templates/author/edit_author.html:73 +#: bookwyrm/templates/author/edit_author.html:79 msgid "Author Identifiers" msgstr "Identifiants de l’auteur ou autrice" -#: bookwyrm/templates/author/edit_author.html:74 +#: bookwyrm/templates/author/edit_author.html:81 msgid "Openlibrary key:" msgstr "Clé Openlibrary :" -#: bookwyrm/templates/author/edit_author.html:79 -#: bookwyrm/templates/book/edit_book.html:243 -#, fuzzy -#| msgid "View on OpenLibrary" +#: bookwyrm/templates/author/edit_author.html:89 +#: bookwyrm/templates/book/edit_book.html:293 msgid "Inventaire ID:" -msgstr "Voir sur OpenLibrary" +msgstr "Identifiant Inventaire :" -#: bookwyrm/templates/author/edit_author.html:84 +#: bookwyrm/templates/author/edit_author.html:97 msgid "Librarything key:" msgstr "Clé Librarything :" -#: bookwyrm/templates/author/edit_author.html:89 +#: bookwyrm/templates/author/edit_author.html:105 msgid "Goodreads key:" msgstr "Clé Goodreads :" -#: bookwyrm/templates/author/edit_author.html:98 -#: bookwyrm/templates/book/book.html:124 -#: bookwyrm/templates/book/edit_book.html:263 +#: bookwyrm/templates/author/edit_author.html:116 +#: bookwyrm/templates/book/book.html:133 +#: bookwyrm/templates/book/edit_book.html:321 #: bookwyrm/templates/lists/form.html:42 #: bookwyrm/templates/preferences/edit_user.html:70 -#: bookwyrm/templates/settings/announcement_form.html:65 +#: bookwyrm/templates/settings/announcement_form.html:69 #: bookwyrm/templates/settings/edit_server.html:68 #: bookwyrm/templates/settings/federated_server.html:98 -#: bookwyrm/templates/settings/site.html:97 +#: bookwyrm/templates/settings/site.html:101 #: bookwyrm/templates/snippets/readthrough.html:77 #: bookwyrm/templates/snippets/shelve_button/finish_reading_modal.html:42 #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:42 @@ -272,10 +268,10 @@ msgstr "Clé Goodreads :" msgid "Save" msgstr "Enregistrer" -#: bookwyrm/templates/author/edit_author.html:99 -#: bookwyrm/templates/book/book.html:125 bookwyrm/templates/book/book.html:174 +#: bookwyrm/templates/author/edit_author.html:117 +#: bookwyrm/templates/book/book.html:134 bookwyrm/templates/book/book.html:183 #: bookwyrm/templates/book/cover_modal.html:32 -#: bookwyrm/templates/book/edit_book.html:264 +#: bookwyrm/templates/book/edit_book.html:322 #: bookwyrm/templates/moderation/report_modal.html:34 #: bookwyrm/templates/settings/federated_server.html:99 #: bookwyrm/templates/snippets/delete_readthrough_modal.html:17 @@ -288,106 +284,98 @@ msgstr "Enregistrer" msgid "Cancel" msgstr "Annuler" -#: bookwyrm/templates/book/book.html:31 +#: bookwyrm/templates/book/book.html:40 #: bookwyrm/templates/discover/large-book.html:25 #: bookwyrm/templates/discover/small-book.html:19 msgid "by" msgstr "par" -#: bookwyrm/templates/book/book.html:39 bookwyrm/templates/book/book.html:40 +#: bookwyrm/templates/book/book.html:48 bookwyrm/templates/book/book.html:49 msgid "Edit Book" msgstr "Modifier le livre" -#: bookwyrm/templates/book/book.html:57 +#: bookwyrm/templates/book/book.html:66 #: bookwyrm/templates/book/cover_modal.html:5 msgid "Add cover" msgstr "Ajouter une couverture" -#: bookwyrm/templates/book/book.html:61 +#: bookwyrm/templates/book/book.html:70 msgid "Failed to load cover" msgstr "La couverture n’a pu être chargée" -#: bookwyrm/templates/book/book.html:101 +#: bookwyrm/templates/book/book.html:110 #, python-format msgid "(%(review_count)s review)" msgid_plural "(%(review_count)s reviews)" msgstr[0] "(%(review_count)s critique)" msgstr[1] "(%(review_count)s critiques)" -#: bookwyrm/templates/book/book.html:113 +#: bookwyrm/templates/book/book.html:122 msgid "Add Description" msgstr "Ajouter une description" -#: bookwyrm/templates/book/book.html:120 -#: bookwyrm/templates/book/edit_book.html:108 +#: bookwyrm/templates/book/book.html:129 +#: bookwyrm/templates/book/edit_book.html:136 #: bookwyrm/templates/lists/form.html:12 msgid "Description:" msgstr "Description :" -#: bookwyrm/templates/book/book.html:134 +#: bookwyrm/templates/book/book.html:143 #, python-format msgid "%(count)s editions" msgstr "%(count)s éditions" -#: bookwyrm/templates/book/book.html:142 +#: bookwyrm/templates/book/book.html:151 #, python-format msgid "This edition is on your %(shelf_name)s shelf." msgstr "Cette édition est sur votre étagère %(shelf_name)s." -#: bookwyrm/templates/book/book.html:148 +#: bookwyrm/templates/book/book.html:157 #, python-format msgid "A different edition of this book is on your %(shelf_name)s shelf." msgstr "Une édition différente de ce livre existe sur votre étagère %(shelf_name)s." -#: bookwyrm/templates/book/book.html:159 +#: bookwyrm/templates/book/book.html:168 msgid "Your reading activity" msgstr "Votre activité de lecture" -#: bookwyrm/templates/book/book.html:162 +#: bookwyrm/templates/book/book.html:171 msgid "Add read dates" msgstr "Ajouter des dates de lecture" -#: bookwyrm/templates/book/book.html:171 +#: bookwyrm/templates/book/book.html:180 msgid "Create" msgstr "Créer" -#: bookwyrm/templates/book/book.html:181 +#: bookwyrm/templates/book/book.html:190 msgid "You don't have any reading activity for this book." msgstr "Vous n’avez aucune activité de lecture pour ce livre" -#: bookwyrm/templates/book/book.html:200 -#, fuzzy -#| msgid "Review" +#: bookwyrm/templates/book/book.html:209 msgid "Reviews" -msgstr "Critique" +msgstr "Critiques" -#: bookwyrm/templates/book/book.html:205 -#, fuzzy -#| msgid "Your shelves" +#: bookwyrm/templates/book/book.html:214 msgid "Your reviews" -msgstr "Vos étagères" +msgstr "Vos critiques" -#: bookwyrm/templates/book/book.html:211 -#, fuzzy -#| msgid "Your Account" +#: bookwyrm/templates/book/book.html:220 msgid "Your comments" -msgstr "Votre compte" +msgstr "Vos commentaires" -#: bookwyrm/templates/book/book.html:217 -#, fuzzy -#| msgid "Your books" +#: bookwyrm/templates/book/book.html:226 msgid "Your quotes" -msgstr "Vos livres" +msgstr "Vos citations" -#: bookwyrm/templates/book/book.html:253 +#: bookwyrm/templates/book/book.html:262 msgid "Subjects" msgstr "Sujets" -#: bookwyrm/templates/book/book.html:265 +#: bookwyrm/templates/book/book.html:274 msgid "Places" msgstr "Lieux" -#: bookwyrm/templates/book/book.html:276 bookwyrm/templates/layout.html:64 +#: bookwyrm/templates/book/book.html:285 bookwyrm/templates/layout.html:61 #: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 #: bookwyrm/templates/search/layout.html:25 #: bookwyrm/templates/search/layout.html:50 @@ -395,13 +383,13 @@ msgstr "Lieux" msgid "Lists" msgstr "Listes" -#: bookwyrm/templates/book/book.html:287 +#: bookwyrm/templates/book/book.html:296 msgid "Add to list" msgstr "Ajouter à la liste" -#: bookwyrm/templates/book/book.html:297 +#: bookwyrm/templates/book/book.html:306 #: bookwyrm/templates/book/cover_modal.html:31 -#: bookwyrm/templates/lists/list.html:164 +#: bookwyrm/templates/lists/list.html:179 msgid "Add" msgstr "Ajouter" @@ -410,22 +398,22 @@ msgid "ISBN:" msgstr "ISBN :" #: bookwyrm/templates/book/book_identifiers.html:14 -#: bookwyrm/templates/book/edit_book.html:248 +#: bookwyrm/templates/book/edit_book.html:301 msgid "OCLC Number:" msgstr "Numéro OCLC :" #: bookwyrm/templates/book/book_identifiers.html:21 -#: bookwyrm/templates/book/edit_book.html:253 +#: bookwyrm/templates/book/edit_book.html:309 msgid "ASIN:" msgstr "ASIN :" #: bookwyrm/templates/book/cover_modal.html:17 -#: bookwyrm/templates/book/edit_book.html:192 +#: bookwyrm/templates/book/edit_book.html:229 msgid "Upload cover:" msgstr "Charger une couverture :" #: bookwyrm/templates/book/cover_modal.html:23 -#: bookwyrm/templates/book/edit_book.html:198 +#: bookwyrm/templates/book/edit_book.html:235 msgid "Load cover from url:" msgstr "Charger la couverture depuis une URL :" @@ -440,133 +428,134 @@ msgstr "Modifier « %(book_title)s »" msgid "Add Book" msgstr "Ajouter un livre" -#: bookwyrm/templates/book/edit_book.html:40 +#: bookwyrm/templates/book/edit_book.html:54 msgid "Confirm Book Info" msgstr "Confirmer les informations de ce livre" -#: bookwyrm/templates/book/edit_book.html:47 +#: bookwyrm/templates/book/edit_book.html:62 #, python-format msgid "Is \"%(name)s\" an existing author?" -msgstr "Est‑ce que l’auteur ou l’autrice « %(name)s » existe déjà ?" +msgstr "Est‑ce que l’auteur/autrice « %(name)s » existe déjà ?" -#: bookwyrm/templates/book/edit_book.html:52 +#: bookwyrm/templates/book/edit_book.html:71 #, python-format msgid "Author of %(book_title)s" -msgstr "Commencer « %(book_title)s »" +msgstr "Auteur/autrice de %(book_title)s" -#: bookwyrm/templates/book/edit_book.html:55 +#: bookwyrm/templates/book/edit_book.html:75 msgid "This is a new author" msgstr "Il s’agit d’un nouvel auteur ou d’une nouvelle autrice." -#: bookwyrm/templates/book/edit_book.html:61 +#: bookwyrm/templates/book/edit_book.html:82 #, python-format msgid "Creating a new author: %(name)s" -msgstr "Création d’un nouvel auteur ou d’une nouvelle autrice : %(name)s" +msgstr "Création d’un nouvel auteur/autrice : %(name)s" -#: bookwyrm/templates/book/edit_book.html:67 +#: bookwyrm/templates/book/edit_book.html:89 msgid "Is this an edition of an existing work?" msgstr "Est‑ce l’édition d’un ouvrage existant ?" -#: bookwyrm/templates/book/edit_book.html:71 +#: bookwyrm/templates/book/edit_book.html:97 msgid "This is a new work" msgstr "Il s’agit d’un nouvel ouvrage." -#: bookwyrm/templates/book/edit_book.html:77 +#: bookwyrm/templates/book/edit_book.html:104 #: bookwyrm/templates/password_reset.html:30 msgid "Confirm" msgstr "Confirmer" -#: bookwyrm/templates/book/edit_book.html:79 +#: bookwyrm/templates/book/edit_book.html:106 #: bookwyrm/templates/feed/status.html:8 msgid "Back" msgstr "Retour" -#: bookwyrm/templates/book/edit_book.html:93 +#: bookwyrm/templates/book/edit_book.html:120 msgid "Title:" msgstr "Titre :" -#: bookwyrm/templates/book/edit_book.html:101 +#: bookwyrm/templates/book/edit_book.html:128 msgid "Subtitle:" msgstr "Sous‑titre :" -#: bookwyrm/templates/book/edit_book.html:114 +#: bookwyrm/templates/book/edit_book.html:144 msgid "Series:" msgstr "Série :" -#: bookwyrm/templates/book/edit_book.html:122 +#: bookwyrm/templates/book/edit_book.html:152 msgid "Series number:" msgstr "Numéro dans la série :" -#: bookwyrm/templates/book/edit_book.html:130 -#, fuzzy -#| msgid "Language:" +#: bookwyrm/templates/book/edit_book.html:160 msgid "Languages:" -msgstr "Langue :" +msgstr "Langues :" -#: bookwyrm/templates/book/edit_book.html:139 +#: bookwyrm/templates/book/edit_book.html:169 msgid "Publisher:" msgstr "Éditeur :" -#: bookwyrm/templates/book/edit_book.html:148 +#: bookwyrm/templates/book/edit_book.html:178 msgid "First published date:" msgstr "Première date de publication :" -#: bookwyrm/templates/book/edit_book.html:156 +#: bookwyrm/templates/book/edit_book.html:186 msgid "Published date:" msgstr "Date de publication :" -#: bookwyrm/templates/book/edit_book.html:165 +#: bookwyrm/templates/book/edit_book.html:195 msgid "Authors" msgstr "Auteurs ou autrices" -#: bookwyrm/templates/book/edit_book.html:171 +#: bookwyrm/templates/book/edit_book.html:202 #, python-format -msgid "Remove %(name)s" -msgstr "Supprimer %(name)s" +msgid "Remove %(name)s" +msgstr "Retirer %(name)s" -#: bookwyrm/templates/book/edit_book.html:176 +#: bookwyrm/templates/book/edit_book.html:205 +#, python-format +msgid "Author page for %(name)s" +msgstr "Page de %(name)s" + +#: bookwyrm/templates/book/edit_book.html:212 msgid "Add Authors:" msgstr "Ajouter des auteurs ou autrices :" -#: bookwyrm/templates/book/edit_book.html:177 +#: bookwyrm/templates/book/edit_book.html:213 msgid "John Doe, Jane Smith" msgstr "Claude Dupont, Dominique Durand" -#: bookwyrm/templates/book/edit_book.html:183 -#: bookwyrm/templates/user/shelf/shelf.html:77 +#: bookwyrm/templates/book/edit_book.html:220 +#: bookwyrm/templates/user/shelf/shelf.html:78 msgid "Cover" msgstr "Couverture" -#: bookwyrm/templates/book/edit_book.html:211 +#: bookwyrm/templates/book/edit_book.html:248 msgid "Physical Properties" msgstr "Propriétés physiques" -#: bookwyrm/templates/book/edit_book.html:212 +#: bookwyrm/templates/book/edit_book.html:250 #: bookwyrm/templates/book/format_filter.html:5 msgid "Format:" msgstr "Format :" -#: bookwyrm/templates/book/edit_book.html:220 +#: bookwyrm/templates/book/edit_book.html:258 msgid "Pages:" msgstr "Pages :" -#: bookwyrm/templates/book/edit_book.html:227 +#: bookwyrm/templates/book/edit_book.html:267 msgid "Book Identifiers" msgstr "Identifiants du livre" -#: bookwyrm/templates/book/edit_book.html:228 +#: bookwyrm/templates/book/edit_book.html:269 msgid "ISBN 13:" msgstr "ISBN 13 :" -#: bookwyrm/templates/book/edit_book.html:233 +#: bookwyrm/templates/book/edit_book.html:277 msgid "ISBN 10:" msgstr "ISBN 10 :" -#: bookwyrm/templates/book/edit_book.html:238 -#, fuzzy -#| msgid "Openlibrary key:" +#: bookwyrm/templates/book/edit_book.html:285 msgid "Openlibrary ID:" -msgstr "Clé Openlibrary :" +msgstr "Identifiant Openlibrary :" #: bookwyrm/templates/book/editions.html:4 #, python-format @@ -605,7 +594,7 @@ msgstr "%(pages)s pages" #: bookwyrm/templates/book/publisher_info.html:38 #, python-format msgid "%(languages)s language" -msgstr "%(languages)s langues" +msgstr "Langue : %(languages)s" #: bookwyrm/templates/book/publisher_info.html:64 #, python-format @@ -654,7 +643,7 @@ msgstr "Communauté fédérée" #: bookwyrm/templates/directory/directory.html:4 #: bookwyrm/templates/directory/directory.html:9 -#: bookwyrm/templates/layout.html:92 +#: bookwyrm/templates/layout.html:64 msgid "Directory" msgstr "Répertoire" @@ -671,7 +660,7 @@ msgstr "Vous pouvez décider de ne plus y figurer à n’importe quel moment dep #: bookwyrm/templates/snippets/announcement.html:34 #: bookwyrm/templates/snippets/goal_card.html:22 msgid "Dismiss message" -msgstr "Rejeter le message" +msgstr "Fermer le message" #: bookwyrm/templates/directory/sort_filter.html:5 msgid "Order by" @@ -688,8 +677,8 @@ msgstr "Actif récemment" #: bookwyrm/templates/directory/user_card.html:33 msgid "follower you follow" msgid_plural "followers you follow" -msgstr[0] "compte auquel vous êtes abonné(e)" -msgstr[1] "comptes auxquels vous êtes abonné(e)" +msgstr[0] "compte que vous suivez" +msgstr[1] "comptes que vous suivez" #: bookwyrm/templates/directory/user_card.html:40 msgid "book on your shelves" @@ -855,7 +844,7 @@ msgid "Direct Messages with %(username)s" msgstr "Messages directs avec %(username)s" #: bookwyrm/templates/feed/direct_messages.html:10 -#: bookwyrm/templates/layout.html:87 +#: bookwyrm/templates/layout.html:92 msgid "Direct Messages" msgstr "Messages directs" @@ -911,7 +900,6 @@ msgid "Updates" msgstr "Mises à jour" #: bookwyrm/templates/feed/feed_layout.html:10 -#: bookwyrm/templates/layout.html:58 #: bookwyrm/templates/user/shelf/books_header.html:3 msgid "Your books" msgstr "Vos livres" @@ -928,7 +916,7 @@ msgstr "À lire" #: bookwyrm/templates/feed/feed_layout.html:24 #: bookwyrm/templates/user/shelf/shelf.html:29 msgid "Currently Reading" -msgstr "En train de lire" +msgstr "Lectures en cours" #: bookwyrm/templates/feed/feed_layout.html:25 #: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:13 @@ -946,8 +934,8 @@ msgstr "Défi lecture pour %(year)s" #, python-format msgid "%(mutuals)s follower you follow" msgid_plural "%(mutuals)s followers you follow" -msgstr[0] "%(mutuals)s abonnement auxquel vous êtes abonné(e)" -msgstr[1] "%(mutuals)s abonnements auxquels vous êtes abonné(e)" +msgstr[0] "%(mutuals)s abonné(e) que vous suivez" +msgstr[1] "%(mutuals)s abonné(e)s que vous suivez" #: bookwyrm/templates/feed/suggested_users.html:19 #, python-format @@ -966,7 +954,7 @@ msgid "What are you reading?" msgstr "Que lisez‑vous ?" #: bookwyrm/templates/get_started/books.html:9 -#: bookwyrm/templates/lists/list.html:120 +#: bookwyrm/templates/lists/list.html:135 msgid "Search for a book" msgstr "Chercher un livre" @@ -986,7 +974,7 @@ msgstr "Vous pourrez ajouter des livres lorsque vous commencerez à utiliser %(s #: bookwyrm/templates/get_started/users.html:18 #: bookwyrm/templates/get_started/users.html:19 #: bookwyrm/templates/layout.html:37 bookwyrm/templates/layout.html:38 -#: bookwyrm/templates/lists/list.html:124 +#: bookwyrm/templates/lists/list.html:139 #: bookwyrm/templates/search/layout.html:4 #: bookwyrm/templates/search/layout.html:9 msgid "Search" @@ -1002,7 +990,7 @@ msgid "Popular on %(site_name)s" msgstr "Populaire sur %(site_name)s" #: bookwyrm/templates/get_started/books.html:58 -#: bookwyrm/templates/lists/list.html:137 +#: bookwyrm/templates/lists/list.html:152 msgid "No books found" msgstr "Aucun livre trouvé" @@ -1014,7 +1002,7 @@ msgstr "Enregistrer & continuer" #: bookwyrm/templates/get_started/layout.html:14 #, python-format msgid "Welcome to %(site_name)s!" -msgstr "Bienvenu(e) sur %(site_name)s !" +msgstr "Bienvenue sur %(site_name)s !" #: bookwyrm/templates/get_started/layout.html:16 msgid "These are some first steps to get you started." @@ -1114,7 +1102,7 @@ msgid "%(username)s's %(year)s Books" msgstr "Livres de %(username)s en %(year)s" #: bookwyrm/templates/import.html:5 bookwyrm/templates/import.html:9 -#: bookwyrm/templates/layout.html:97 +#: bookwyrm/templates/user/shelf/shelf.html:40 msgid "Import Books" msgstr "Importer des livres" @@ -1152,65 +1140,70 @@ msgstr "Aucune importation récente" msgid "Import Status" msgstr "Statut de l’importation" -#: bookwyrm/templates/import_status.html:12 +#: bookwyrm/templates/import_status.html:13 msgid "Import started:" -msgstr "Importation en cours :" +msgstr "Début de l’importation :" -#: bookwyrm/templates/import_status.html:16 +#: bookwyrm/templates/import_status.html:18 msgid "Import completed:" -msgstr "Importation terminé :" +msgstr "Fin de l’importation :" -#: bookwyrm/templates/import_status.html:19 +#: bookwyrm/templates/import_status.html:23 msgid "TASK FAILED" msgstr "la tâche a échoué" -#: bookwyrm/templates/import_status.html:25 +#: bookwyrm/templates/import_status.html:30 msgid "Import still in progress." msgstr "L’importation est toujours en cours" -#: bookwyrm/templates/import_status.html:27 +#: bookwyrm/templates/import_status.html:32 msgid "(Hit reload to update!)" -msgstr "(Rechargez la page pour mettre à jour !" +msgstr "(Rechargez la page pour la mettre à jour !)" -#: bookwyrm/templates/import_status.html:34 +#: bookwyrm/templates/import_status.html:39 msgid "Failed to load" -msgstr "Items non importés" +msgstr "Éléments non importés" -#: bookwyrm/templates/import_status.html:43 +#: bookwyrm/templates/import_status.html:48 #, python-format msgid "Jump to the bottom of the list to select the %(failed_count)s items which failed to import." msgstr "Sauter en bas de liste pour sélectionner les %(failed_count)s items n’ayant pu être importés." -#: bookwyrm/templates/import_status.html:78 +#: bookwyrm/templates/import_status.html:60 +#, python-format +msgid "Line %(index)s: %(title)s by %(author)s" +msgstr "Ligne %(index)s : %(title)s par %(author)s" + +#: bookwyrm/templates/import_status.html:80 msgid "Select all" msgstr "Tout sélectionner" -#: bookwyrm/templates/import_status.html:81 +#: bookwyrm/templates/import_status.html:83 msgid "Retry items" -msgstr "Essayer d’importer les items sélectionnés de nouveau" +msgstr "Réessayer l’importation de ces éléments" -#: bookwyrm/templates/import_status.html:107 +#: bookwyrm/templates/import_status.html:109 msgid "Successfully imported" msgstr "Importation réussie" -#: bookwyrm/templates/import_status.html:111 +#: bookwyrm/templates/import_status.html:113 msgid "Book" msgstr "Livre" -#: bookwyrm/templates/import_status.html:114 +#: bookwyrm/templates/import_status.html:116 #: bookwyrm/templates/snippets/create_status_form.html:13 -#: bookwyrm/templates/user/shelf/shelf.html:78 -#: bookwyrm/templates/user/shelf/shelf.html:98 +#: bookwyrm/templates/user/shelf/shelf.html:79 +#: bookwyrm/templates/user/shelf/shelf.html:99 msgid "Title" msgstr "Titre" -#: bookwyrm/templates/import_status.html:117 -#: bookwyrm/templates/user/shelf/shelf.html:79 -#: bookwyrm/templates/user/shelf/shelf.html:101 +#: bookwyrm/templates/import_status.html:119 +#: bookwyrm/templates/user/shelf/shelf.html:80 +#: bookwyrm/templates/user/shelf/shelf.html:102 msgid "Author" -msgstr "Auteur ou autrice" +msgstr "Auteur/autrice" -#: bookwyrm/templates/import_status.html:140 +#: bookwyrm/templates/import_status.html:142 msgid "Imported" msgstr "Importé" @@ -1248,15 +1241,19 @@ msgstr "Chercher un livre ou un compte" msgid "Main navigation menu" msgstr "Menu de navigation principal " -#: bookwyrm/templates/layout.html:61 +#: bookwyrm/templates/layout.html:58 msgid "Feed" msgstr "Fil d’actualité" -#: bookwyrm/templates/layout.html:102 +#: bookwyrm/templates/layout.html:87 +msgid "Your Books" +msgstr "Vos Livres" + +#: bookwyrm/templates/layout.html:97 msgid "Settings" msgstr "Paramètres" -#: bookwyrm/templates/layout.html:111 +#: bookwyrm/templates/layout.html:106 #: bookwyrm/templates/settings/admin_layout.html:31 #: bookwyrm/templates/settings/manage_invite_requests.html:15 #: bookwyrm/templates/settings/manage_invites.html:3 @@ -1264,56 +1261,54 @@ msgstr "Paramètres" msgid "Invites" msgstr "Invitations" -#: bookwyrm/templates/layout.html:118 +#: bookwyrm/templates/layout.html:113 msgid "Admin" msgstr "Admin" -#: bookwyrm/templates/layout.html:125 +#: bookwyrm/templates/layout.html:120 msgid "Log out" msgstr "Se déconnecter" -#: bookwyrm/templates/layout.html:133 bookwyrm/templates/layout.html:134 +#: bookwyrm/templates/layout.html:128 bookwyrm/templates/layout.html:129 #: bookwyrm/templates/notifications.html:6 #: bookwyrm/templates/notifications.html:11 msgid "Notifications" msgstr "Notifications" -#: bookwyrm/templates/layout.html:154 bookwyrm/templates/layout.html:158 +#: bookwyrm/templates/layout.html:151 bookwyrm/templates/layout.html:155 #: bookwyrm/templates/login.html:17 #: bookwyrm/templates/snippets/register_form.html:4 msgid "Username:" msgstr "Nom du compte :" -#: bookwyrm/templates/layout.html:159 +#: bookwyrm/templates/layout.html:156 msgid "password" msgstr "Mot de passe" -#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:36 +#: bookwyrm/templates/layout.html:157 bookwyrm/templates/login.html:36 msgid "Forgot your password?" msgstr "Mot de passe oublié ?" -#: bookwyrm/templates/layout.html:163 bookwyrm/templates/login.html:10 +#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:10 #: bookwyrm/templates/login.html:33 msgid "Log in" msgstr "Se connecter" -#: bookwyrm/templates/layout.html:171 +#: bookwyrm/templates/layout.html:168 msgid "Join" msgstr "Rejoindre" #: bookwyrm/templates/layout.html:206 -msgid "About this server" -msgstr "À propos de ce serveur" +msgid "About this instance" +msgstr "À propos de cette instance" #: bookwyrm/templates/layout.html:210 msgid "Contact site admin" msgstr "Contacter l’administrateur du site" #: bookwyrm/templates/layout.html:214 -#, fuzzy -#| msgid "List curation:" msgid "Documentation" -msgstr "Modération de la liste :" +msgstr "Documentation" #: bookwyrm/templates/layout.html:221 #, python-format @@ -1364,7 +1359,7 @@ msgid "Discard" msgstr "Rejeter" #: bookwyrm/templates/lists/edit_form.html:5 -#: bookwyrm/templates/lists/list_layout.html:17 +#: bookwyrm/templates/lists/list_layout.html:16 msgid "Edit List" msgstr "Modifier la liste" @@ -1378,7 +1373,7 @@ msgstr "Fermée" #: bookwyrm/templates/lists/form.html:22 msgid "Only you can add and remove books to this list" -msgstr "Vous seul(e) pouvez ajouter ou retirer des livres dans cette liste" +msgstr "Vous seulement pouvez ajouter ou retirer des livres dans cette liste" #: bookwyrm/templates/lists/form.html:26 msgid "Curated" @@ -1401,74 +1396,65 @@ msgstr "N’importe qui peut suggérer des livres" #: bookwyrm/templates/lists/list.html:20 msgid "You successfully suggested a book for this list!" -msgstr "" +msgstr "Vous avez suggéré un livre à cette liste !" #: bookwyrm/templates/lists/list.html:22 -#, fuzzy -#| msgid "Anyone can add books to this list" msgid "You successfully added a book to this list!" -msgstr "N’importe qui peut suggérer des livres" +msgstr "Vous avez ajouté un livre à cette liste !" #: bookwyrm/templates/lists/list.html:28 msgid "This list is currently empty" -msgstr "Cette liste est vide actuellement" +msgstr "Cette liste est actuellement vide" -#: bookwyrm/templates/lists/list.html:65 +#: bookwyrm/templates/lists/list.html:66 #, python-format msgid "Added by %(username)s" -msgstr "Ajoutée par %(username)s" +msgstr "Ajouté par %(username)s" -#: bookwyrm/templates/lists/list.html:77 -#, fuzzy -#| msgid "Sent" -msgid "Set" -msgstr "Envoyé(e)s" - -#: bookwyrm/templates/lists/list.html:80 -#, fuzzy -#| msgid "List curation:" +#: bookwyrm/templates/lists/list.html:74 msgid "List position" -msgstr "Modération de la liste :" +msgstr "Position" -#: bookwyrm/templates/lists/list.html:86 +#: bookwyrm/templates/lists/list.html:81 +msgid "Set" +msgstr "Appliquer" + +#: bookwyrm/templates/lists/list.html:89 #: bookwyrm/templates/snippets/shelf_selector.html:26 msgid "Remove" -msgstr "Supprimer" +msgstr "Retirer" -#: bookwyrm/templates/lists/list.html:99 bookwyrm/templates/lists/list.html:111 -#, fuzzy -#| msgid "Your Lists" +#: bookwyrm/templates/lists/list.html:103 +#: bookwyrm/templates/lists/list.html:120 msgid "Sort List" -msgstr "Vos listes" +msgstr "Trier la liste" -#: bookwyrm/templates/lists/list.html:105 -#, fuzzy -#| msgid "Directory" +#: bookwyrm/templates/lists/list.html:113 msgid "Direction" -msgstr "Répertoire" +msgstr "Direction" -#: bookwyrm/templates/lists/list.html:116 +#: bookwyrm/templates/lists/list.html:127 msgid "Add Books" msgstr "Ajouter des livres" -#: bookwyrm/templates/lists/list.html:116 +#: bookwyrm/templates/lists/list.html:129 msgid "Suggest Books" msgstr "Suggérer des livres" -#: bookwyrm/templates/lists/list.html:125 +#: bookwyrm/templates/lists/list.html:140 msgid "search" -msgstr "Chercher" +msgstr "chercher" -#: bookwyrm/templates/lists/list.html:131 +#: bookwyrm/templates/lists/list.html:146 msgid "Clear search" msgstr "Vider la requête" -#: bookwyrm/templates/lists/list.html:136 +#: bookwyrm/templates/lists/list.html:151 #, python-format msgid "No books found matching the query \"%(query)s\"" msgstr "Aucun livre trouvé pour la requête « %(query)s »" -#: bookwyrm/templates/lists/list.html:164 +#: bookwyrm/templates/lists/list.html:179 msgid "Suggest" msgstr "Suggérer" @@ -1523,10 +1509,8 @@ msgid "No statuses reported" msgstr "Aucun statut signalé" #: bookwyrm/templates/moderation/report.html:53 -#, fuzzy -#| msgid "Statuses has been deleted" msgid "Status has been deleted" -msgstr "Les statuts ont été supprimés" +msgstr "Le statut a été supprimé" #: bookwyrm/templates/moderation/report_modal.html:6 #, python-format @@ -1561,8 +1545,8 @@ msgstr "Résoudre" #: bookwyrm/templates/moderation/reports.html:6 #, python-format -msgid "Reports: %(server_name)s" -msgstr "Signalements : %(server_name)s" +msgid "Reports: %(instance_name)s" +msgstr "Signalements : %(instance_name)s" #: bookwyrm/templates/moderation/reports.html:8 #: bookwyrm/templates/moderation/reports.html:17 @@ -1572,8 +1556,8 @@ msgstr "Signalements" #: bookwyrm/templates/moderation/reports.html:14 #, python-format -msgid "Reports: %(server_name)s" -msgstr "Signalements: %(server_name)s" +msgid "Reports: %(instance_name)s" +msgstr "Signalements : %(instance_name)s" #: bookwyrm/templates/moderation/reports.html:28 msgid "Resolved" @@ -1589,13 +1573,11 @@ msgstr "Supprimer les notifications" #: bookwyrm/templates/notifications.html:25 msgid "All" -msgstr "" +msgstr "Toutes" #: bookwyrm/templates/notifications.html:29 -#, fuzzy -#| msgid "More options" msgid "Mentions" -msgstr "Plus d’options" +msgstr "Mentions" #: bookwyrm/templates/notifications.html:70 #, python-format @@ -1659,7 +1641,7 @@ msgstr "a répondu à votre %(book_title)s t msgstr " a suggégré l’ajout de %(book_title)s à votre liste « %(list_name)s »" #: bookwyrm/templates/notifications.html:128 -#, fuzzy, python-format -#| msgid "Your import completed." +#, python-format msgid "Your import completed." -msgstr "Votre importation est terminée." +msgstr "Votre importation est terminée." #: bookwyrm/templates/notifications.html:131 #, python-format @@ -1774,31 +1755,45 @@ msgstr "Profil" msgid "Relationships" msgstr "Relations" +#: bookwyrm/templates/rss/title.html:5 +#: bookwyrm/templates/snippets/status/status_header.html:35 +msgid "rated" +msgstr "a noté" + +#: bookwyrm/templates/rss/title.html:7 +#: bookwyrm/templates/snippets/status/status_header.html:37 +msgid "reviewed" +msgstr "a écrit une critique de" + +#: bookwyrm/templates/rss/title.html:9 +#: bookwyrm/templates/snippets/status/status_header.html:39 +msgid "commented on" +msgstr "a commenté" + +#: bookwyrm/templates/rss/title.html:11 +#: bookwyrm/templates/snippets/status/status_header.html:41 +msgid "quoted" +msgstr "a cité" + #: bookwyrm/templates/search/book.html:64 -#, fuzzy -#| msgid "Show results from other catalogues" msgid "Load results from other catalogues" -msgstr "Montrer les résultats d’autres catalogues" +msgstr "Charger les résultats d’autres catalogues" #: bookwyrm/templates/search/book.html:68 msgid "Manually add book" -msgstr "" +msgstr "Ajouter un livre manuellement" #: bookwyrm/templates/search/book.html:73 msgid "Log in to import or add books." -msgstr "" +msgstr "Authentifiez-vous pour importer ou ajouter des livres." #: bookwyrm/templates/search/layout.html:16 -#, fuzzy -#| msgid "Search for a user" msgid "Search query" -msgstr "Chercher un compte" +msgstr "Requête" #: bookwyrm/templates/search/layout.html:19 -#, fuzzy -#| msgid "Search" msgid "Search type" -msgstr "Chercher" +msgstr "Type de recherche" #: bookwyrm/templates/search/layout.html:21 #: bookwyrm/templates/search/layout.html:42 @@ -1815,10 +1810,9 @@ msgid "Users" msgstr "Comptes" #: bookwyrm/templates/search/layout.html:58 -#, fuzzy, python-format -#| msgid "No lists found for \"%(query)s\"" +#, python-format msgid "No results found for \"%(query)s\"" -msgstr "Aucune liste trouvée pour « %(query)s »" +msgstr "Aucun résultat pour « %(query)s »" #: bookwyrm/templates/settings/admin_layout.html:4 msgid "Administration" @@ -1831,8 +1825,8 @@ msgstr "Gérer les comptes" #: bookwyrm/templates/settings/admin_layout.html:39 #: bookwyrm/templates/settings/federation.html:3 #: bookwyrm/templates/settings/federation.html:5 -msgid "Federated Servers" -msgstr "Serveurs fédérés" +msgid "Federated Instances" +msgstr "Instances fédérées" #: bookwyrm/templates/settings/admin_layout.html:44 msgid "Instance Settings" @@ -1868,27 +1862,22 @@ msgstr "Contenu du pied de page" #: bookwyrm/templates/settings/admin_layout.html:58 #: bookwyrm/templates/settings/site.html:81 msgid "Registration" -msgstr "Enregistrement" +msgstr "Inscription" #: bookwyrm/templates/settings/announcement.html:3 #: bookwyrm/templates/settings/announcement.html:6 -#, fuzzy -#| msgid "Announcements" msgid "Announcement" -msgstr "Annonces" +msgstr "Annonce" #: bookwyrm/templates/settings/announcement.html:7 #: bookwyrm/templates/settings/federated_server.html:13 -#, fuzzy -#| msgid "Back to server list" msgid "Back to list" -msgstr "Retour à la liste des serveurs" +msgstr "Retour à la liste" #: bookwyrm/templates/settings/announcement.html:11 -#, fuzzy -#| msgid "Announcements" +#: bookwyrm/templates/settings/announcement_form.html:6 msgid "Edit Announcement" -msgstr "Annonces" +msgstr "Modifier l’annonce" #: bookwyrm/templates/settings/announcement.html:20 #: bookwyrm/templates/snippets/delete_readthrough_modal.html:15 @@ -1898,64 +1887,48 @@ msgstr "Supprimer" #: bookwyrm/templates/settings/announcement.html:35 msgid "Visible:" -msgstr "" +msgstr "Visible :" #: bookwyrm/templates/settings/announcement.html:38 msgid "True" -msgstr "" +msgstr "Vrai" #: bookwyrm/templates/settings/announcement.html:40 msgid "False" -msgstr "" +msgstr "Faux" #: bookwyrm/templates/settings/announcement.html:47 -#, fuzzy -#| msgid "Birth date:" msgid "Start date:" -msgstr "Date de naissance :" +msgstr "Date de début :" #: bookwyrm/templates/settings/announcement.html:54 -#, fuzzy -#| msgid "Birth date:" msgid "End date:" -msgstr "Date de naissance :" +msgstr "Date de fin :" #: bookwyrm/templates/settings/announcement.html:60 -#, fuzzy -#| msgid "Active" msgid "Active:" -msgstr "Actif" +msgstr "Active :" -#: bookwyrm/templates/settings/announcement_form.html:5 +#: bookwyrm/templates/settings/announcement_form.html:8 #: bookwyrm/templates/settings/announcements.html:8 -#, fuzzy -#| msgid "Announcements" msgid "Create Announcement" -msgstr "Annonces" +msgstr "Ajouter une annonce" #: bookwyrm/templates/settings/announcements.html:22 -#, fuzzy -#| msgid "Date Added" msgid "Date added" msgstr "Date d’ajout" #: bookwyrm/templates/settings/announcements.html:26 -#, fuzzy -#| msgid "reviewed" msgid "Preview" -msgstr "a écrit une critique de" +msgstr "Aperçu" #: bookwyrm/templates/settings/announcements.html:30 -#, fuzzy -#| msgid "Started" msgid "Start date" -msgstr "Commencé" +msgstr "Date de début" #: bookwyrm/templates/settings/announcements.html:34 -#, fuzzy -#| msgid "Edit read dates" msgid "End date" -msgstr "Modifier les date de lecture" +msgstr "Date de fin" #: bookwyrm/templates/settings/announcements.html:38 #: bookwyrm/templates/settings/federation.html:30 @@ -1966,16 +1939,12 @@ msgid "Status" msgstr "Statut" #: bookwyrm/templates/settings/announcements.html:48 -#, fuzzy -#| msgid "Inactive" msgid "active" -msgstr "Inactif" +msgstr "active" #: bookwyrm/templates/settings/announcements.html:48 -#, fuzzy -#| msgid "Inactive" msgid "inactive" -msgstr "Inactif" +msgstr "inactive" #: bookwyrm/templates/settings/edit_server.html:3 #: bookwyrm/templates/settings/edit_server.html:6 @@ -1984,28 +1953,22 @@ msgstr "Inactif" #: bookwyrm/templates/settings/federation.html:10 #: bookwyrm/templates/settings/server_blocklist.html:3 #: bookwyrm/templates/settings/server_blocklist.html:20 -#, fuzzy -#| msgid "Add cover" -msgid "Add server" -msgstr "Ajouter une couverture" +msgid "Add instance" +msgstr "Ajouter une instance" #: bookwyrm/templates/settings/edit_server.html:7 #: bookwyrm/templates/settings/server_blocklist.html:7 -msgid "Back to server list" -msgstr "Retour à la liste des serveurs" +msgid "Back to instance list" +msgstr "Retour à la liste des instances" #: bookwyrm/templates/settings/edit_server.html:16 #: bookwyrm/templates/settings/server_blocklist.html:16 -#, fuzzy -#| msgid "Import book" msgid "Import block list" -msgstr "Importer le livre" +msgstr "Importer une liste de blocage" #: bookwyrm/templates/settings/edit_server.html:30 -#, fuzzy -#| msgid "Instance Name:" msgid "Instance:" -msgstr "Nom de l’instance :" +msgstr "Instance :" #: bookwyrm/templates/settings/edit_server.html:37 #: bookwyrm/templates/settings/federated_server.html:31 @@ -2015,10 +1978,8 @@ msgstr "Statut :" #: bookwyrm/templates/settings/edit_server.html:41 #: bookwyrm/templates/settings/federated_server.html:10 -#, fuzzy -#| msgid "Block" msgid "Blocked" -msgstr "Bloquer" +msgstr "Bloqué" #: bookwyrm/templates/settings/edit_server.html:48 #: bookwyrm/templates/settings/federated_server.html:23 @@ -2034,7 +1995,7 @@ msgstr "Description :" #: bookwyrm/templates/settings/edit_server.html:64 msgid "Notes:" -msgstr "" +msgstr "Remarques :" #: bookwyrm/templates/settings/federated_server.html:19 msgid "Details" @@ -2073,13 +2034,11 @@ msgstr "Bloqués par nous :" #: bookwyrm/templates/settings/federated_server.html:82 #: bookwyrm/templates/user_admin/user_info.html:39 msgid "Notes" -msgstr "" +msgstr "Remarques" #: bookwyrm/templates/settings/federated_server.html:85 -#, fuzzy -#| msgid "Edit Book" msgid "Edit" -msgstr "Modifier le livre" +msgstr "Modifier" #: bookwyrm/templates/settings/federated_server.html:105 #: bookwyrm/templates/user_admin/user_moderation_actions.html:3 @@ -2093,7 +2052,7 @@ msgstr "Bloquer" #: bookwyrm/templates/settings/federated_server.html:110 msgid "All users from this instance will be deactivated." -msgstr "" +msgstr "Tous les comptes de cette instance seront désactivés." #: bookwyrm/templates/settings/federated_server.html:115 #: bookwyrm/templates/snippets/block_button.html:10 @@ -2102,12 +2061,12 @@ msgstr "Débloquer" #: bookwyrm/templates/settings/federated_server.html:116 msgid "All users from this instance will be re-activated." -msgstr "" +msgstr "Tous les comptes de cette instance seront réactivés." #: bookwyrm/templates/settings/federation.html:19 #: bookwyrm/templates/user_admin/server_filter.html:5 -msgid "Server name" -msgstr "Nom du serveur" +msgid "Instance name" +msgstr "Nom de l’instance" #: bookwyrm/templates/settings/federation.html:23 msgid "Date federated" @@ -2122,7 +2081,7 @@ msgstr "Logiciel" #: bookwyrm/templates/settings/manage_invite_requests.html:25 #: bookwyrm/templates/settings/manage_invites.html:11 msgid "Invite Requests" -msgstr "Invitations" +msgstr "Demandes d’invitation" #: bookwyrm/templates/settings/manage_invite_requests.html:23 msgid "Ignored Invite Requests" @@ -2224,25 +2183,21 @@ msgid "No active invites" msgstr "Aucune invitation active" #: bookwyrm/templates/settings/server_blocklist.html:6 -#, fuzzy -#| msgid "Import Books" msgid "Import Blocklist" -msgstr "Importer des livres" +msgstr "Importer une liste de blocage" #: bookwyrm/templates/settings/server_blocklist.html:26 -#: bookwyrm/templates/snippets/goal_progress.html:5 +#: bookwyrm/templates/snippets/goal_progress.html:7 msgid "Success!" msgstr "Bravo !" #: bookwyrm/templates/settings/server_blocklist.html:30 -#, fuzzy -#| msgid "Successfully imported" msgid "Successfully blocked:" -msgstr "Importation réussie" +msgstr "Blocage réussi :" #: bookwyrm/templates/settings/server_blocklist.html:32 msgid "Failed:" -msgstr "" +msgstr "Échec :" #: bookwyrm/templates/settings/site.html:15 msgid "Instance Name:" @@ -2290,48 +2245,43 @@ msgstr "Email de l’administrateur :" #: bookwyrm/templates/settings/site.html:73 msgid "Additional info:" -msgstr "" +msgstr "Infos supplémentaires :" -#: bookwyrm/templates/settings/site.html:83 -msgid "Allow registration:" -msgstr "Autoriser l’enregistrement :" - -#: bookwyrm/templates/settings/site.html:87 -msgid "Allow invite requests:" -msgstr "Autoriser les demandes d’invitation :" +#: bookwyrm/templates/settings/site.html:85 +msgid "Allow registration" +msgstr "Autoriser les inscriptions" #: bookwyrm/templates/settings/site.html:91 +msgid "Allow invite requests" +msgstr "Autoriser les demandes d’invitation" + +#: bookwyrm/templates/settings/site.html:95 msgid "Registration closed text:" -msgstr "Texte affiché lorsque les enregistrements sont clos :" +msgstr "Texte affiché lorsque les inscriptions sont closes :" #: bookwyrm/templates/snippets/announcement.html:31 -#, fuzzy, python-format -#| msgid "Added by %(username)s" +#, python-format msgid "Posted by %(username)s" -msgstr "Ajoutée par %(username)s" +msgstr "Publiée par %(username)s" #: bookwyrm/templates/snippets/book_cover.html:31 msgid "No cover" -msgstr "Aucune couverture" +msgstr "Pas de couverture" #: bookwyrm/templates/snippets/book_titleby.html:4 #, python-format msgid "%(title)s by " msgstr "%(title)s par " -#: bookwyrm/templates/snippets/boost_button.html:9 -#: bookwyrm/templates/snippets/boost_button.html:10 -#, fuzzy -#| msgid "boosted" +#: bookwyrm/templates/snippets/boost_button.html:20 +#: bookwyrm/templates/snippets/boost_button.html:21 msgid "Boost" -msgstr "partagé" +msgstr "Partager" -#: bookwyrm/templates/snippets/boost_button.html:16 -#: bookwyrm/templates/snippets/boost_button.html:17 -#, fuzzy -#| msgid "Un-boost status" +#: bookwyrm/templates/snippets/boost_button.html:33 +#: bookwyrm/templates/snippets/boost_button.html:34 msgid "Un-boost" -msgstr "Annuler le partage du statut" +msgstr "Annuler le partage" #: bookwyrm/templates/snippets/content_warning_field.html:3 msgid "Spoiler alert:" @@ -2369,10 +2319,8 @@ msgid "Reply" msgstr "Répondre" #: bookwyrm/templates/snippets/create_status_form.html:56 -#, fuzzy -#| msgid "Footer Content" msgid "Content" -msgstr "Contenu du pied de page" +msgstr "Contenu" #: bookwyrm/templates/snippets/create_status_form.html:80 #: bookwyrm/templates/snippets/shelve_button/progress_update_modal.html:16 @@ -2421,17 +2369,15 @@ msgstr "Supprimer ces dates de lecture ?" msgid "You are deleting this readthrough and its %(count)s associated progress updates." msgstr "Vous avez supprimé ce résumé et ses %(count)s progressions associées." -#: bookwyrm/templates/snippets/fav_button.html:9 -#: bookwyrm/templates/snippets/fav_button.html:11 +#: bookwyrm/templates/snippets/fav_button.html:10 +#: bookwyrm/templates/snippets/fav_button.html:12 msgid "Like" -msgstr "" +msgstr "Ajouter aux favoris" -#: bookwyrm/templates/snippets/fav_button.html:17 #: bookwyrm/templates/snippets/fav_button.html:18 -#, fuzzy -#| msgid "Un-like status" +#: bookwyrm/templates/snippets/fav_button.html:19 msgid "Un-like" -msgstr "Retirer le statut des favoris" +msgstr "Retirer des favoris" #: bookwyrm/templates/snippets/filters_panel/filters_panel.html:7 msgid "Show filters" @@ -2470,6 +2416,14 @@ msgstr "Accepter" msgid "No rating" msgstr "Aucune note" +#: bookwyrm/templates/snippets/form_rate_stars.html:44 +#: bookwyrm/templates/snippets/stars.html:7 +#, python-format +msgid "%(rating)s star" +msgid_plural "%(rating)s stars" +msgstr[0] "%(rating)s étoile" +msgstr[1] "%(rating)s étoiles" + #: bookwyrm/templates/snippets/generated_status/goal.html:1 #, python-format msgid "set a goal to read %(counter)s book in %(year)s" @@ -2524,17 +2478,17 @@ msgstr "Publier sur le fil d’actualité" msgid "Set goal" msgstr "Valider ce défi" -#: bookwyrm/templates/snippets/goal_progress.html:7 +#: bookwyrm/templates/snippets/goal_progress.html:9 #, python-format msgid "%(percent)s%% complete!" msgstr "%(percent)s%% terminé !" -#: bookwyrm/templates/snippets/goal_progress.html:10 +#: bookwyrm/templates/snippets/goal_progress.html:12 #, python-format msgid "You've read %(read_count)s of %(goal_count)s books." msgstr "Vous avez lu %(read_count)s sur %(goal_count)s livres." -#: bookwyrm/templates/snippets/goal_progress.html:12 +#: bookwyrm/templates/snippets/goal_progress.html:14 #, python-format msgid "%(username)s has read %(read_count)s of %(goal_count)s books." msgstr "%(username)s a lu %(read_count)s sur %(goal_count)s livres." @@ -2581,7 +2535,7 @@ msgstr "Confidentialité du statut" #: bookwyrm/templates/user/relationships/followers.html:6 #: bookwyrm/templates/user/relationships/layout.html:11 msgid "Followers" -msgstr "Abonnements" +msgstr "Abonné(e)s" #: bookwyrm/templates/snippets/rate_action.html:4 msgid "Leave a rating" @@ -2643,26 +2597,6 @@ msgstr "S’enregistrer" msgid "Report" msgstr "Signaler" -#: bookwyrm/templates/snippets/rss_title.html:5 -#: bookwyrm/templates/snippets/status/status_header.html:35 -msgid "rated" -msgstr "a noté" - -#: bookwyrm/templates/snippets/rss_title.html:7 -#: bookwyrm/templates/snippets/status/status_header.html:37 -msgid "reviewed" -msgstr "a écrit une critique de" - -#: bookwyrm/templates/snippets/rss_title.html:9 -#: bookwyrm/templates/snippets/status/status_header.html:39 -msgid "commented on" -msgstr "a commenté" - -#: bookwyrm/templates/snippets/rss_title.html:11 -#: bookwyrm/templates/snippets/status/status_header.html:41 -msgid "quoted" -msgstr "a cité" - #: bookwyrm/templates/snippets/search_result_text.html:36 msgid "Import book" msgstr "Importer le livre" @@ -2711,7 +2645,7 @@ msgstr "Commencer « %(book_title)s »" #: bookwyrm/templates/snippets/shelve_button/want_to_read_modal.html:5 #, python-format msgid "Want to Read \"%(book_title)s\"" -msgstr "A envie de lire « %(book_title)s »" +msgstr "Ajouter « %(book_title)s » aux envies de lecture" #: bookwyrm/templates/snippets/status/content_status.html:71 #: bookwyrm/templates/snippets/trimmed_text.html:15 @@ -2744,7 +2678,7 @@ msgstr "Ajouter le statut aux favoris" #: bookwyrm/templates/snippets/status/status.html:10 msgid "boosted" -msgstr "partagé" +msgstr "a partagé" #: bookwyrm/templates/snippets/status/status_header.html:45 #, python-format @@ -2807,12 +2741,12 @@ msgstr "%(username)s n’a pas d’abonné(e)" #: bookwyrm/templates/user/relationships/following.html:6 #: bookwyrm/templates/user/relationships/layout.html:15 msgid "Following" -msgstr "Abonné(e) à" +msgstr "Comptes suivis" #: bookwyrm/templates/user/relationships/following.html:12 #, python-format msgid "%(username)s isn't following any users" -msgstr "%(username)s n’est abonné(e) à personne" +msgstr "%(username)s ne suit personne" #: bookwyrm/templates/user/shelf/books_header.html:5 #, python-format @@ -2822,7 +2756,7 @@ msgstr "Livres de %(username)s" #: bookwyrm/templates/user/shelf/create_shelf_form.html:5 #: bookwyrm/templates/user/shelf/create_shelf_form.html:22 msgid "Create Shelf" -msgstr "Créer l’étagère" +msgstr "Créer une étagère" #: bookwyrm/templates/user/shelf/edit_shelf_form.html:5 msgid "Edit Shelf" @@ -2832,38 +2766,38 @@ msgstr "Modifier l’étagère" msgid "Update shelf" msgstr "Mettre l’étagère à jour" -#: bookwyrm/templates/user/shelf/shelf.html:25 bookwyrm/views/shelf.py:51 +#: bookwyrm/templates/user/shelf/shelf.html:25 bookwyrm/views/shelf.py:56 msgid "All books" msgstr "Tous les livres" #: bookwyrm/templates/user/shelf/shelf.html:38 msgid "Create shelf" -msgstr "Créer l’étagère" +msgstr "Créer une étagère" -#: bookwyrm/templates/user/shelf/shelf.html:61 +#: bookwyrm/templates/user/shelf/shelf.html:62 msgid "Edit shelf" msgstr "Modifier l’étagère" -#: bookwyrm/templates/user/shelf/shelf.html:80 -#: bookwyrm/templates/user/shelf/shelf.html:104 -msgid "Shelved" -msgstr "Ajouté à une étagère" - #: bookwyrm/templates/user/shelf/shelf.html:81 -#: bookwyrm/templates/user/shelf/shelf.html:108 +#: bookwyrm/templates/user/shelf/shelf.html:105 +msgid "Shelved" +msgstr "Date d’ajout" + +#: bookwyrm/templates/user/shelf/shelf.html:82 +#: bookwyrm/templates/user/shelf/shelf.html:109 msgid "Started" msgstr "Commencé" -#: bookwyrm/templates/user/shelf/shelf.html:82 -#: bookwyrm/templates/user/shelf/shelf.html:111 +#: bookwyrm/templates/user/shelf/shelf.html:83 +#: bookwyrm/templates/user/shelf/shelf.html:112 msgid "Finished" msgstr "Terminé" -#: bookwyrm/templates/user/shelf/shelf.html:137 +#: bookwyrm/templates/user/shelf/shelf.html:138 msgid "This shelf is empty." msgstr "Cette étagère est vide" -#: bookwyrm/templates/user/shelf/shelf.html:143 +#: bookwyrm/templates/user/shelf/shelf.html:144 msgid "Delete shelf" msgstr "Supprimer l’étagère" @@ -2895,14 +2829,14 @@ msgstr "Aucune activité pour l’instant !" #: bookwyrm/templates/user/user_preview.html:15 #, python-format msgid "Joined %(date)s" -msgstr "Enregistré(e) %(date)s" +msgstr "A rejoint ce serveur %(date)s" #: bookwyrm/templates/user/user_preview.html:19 #, python-format msgid "%(counter)s follower" msgid_plural "%(counter)s followers" -msgstr[0] "%(counter)s abonnement" -msgstr[1] "%(counter)s abonnements" +msgstr[0] "%(counter)s abonné(e)" +msgstr[1] "%(counter)s abonné(e)s" #: bookwyrm/templates/user/user_preview.html:20 #, python-format @@ -2910,24 +2844,20 @@ msgid "%(counter)s following" msgstr "%(counter)s abonnements" #: bookwyrm/templates/user/user_preview.html:26 -#, fuzzy, python-format -#| msgid "%(mutuals)s follower you follow" -#| msgid_plural "%(mutuals)s followers you follow" +#, python-format msgid "%(mutuals_display)s follower you follow" msgid_plural "%(mutuals_display)s followers you follow" -msgstr[0] "%(mutuals)s abonnement auxquel vous êtes abonné(e)" -msgstr[1] "%(mutuals)s abonnements auxquels vous êtes abonné(e)" +msgstr[0] "%(mutuals_display)s abonné(e) que vous suivez" +msgstr[1] "%(mutuals_display)s abonné(e)s que vous suivez" #: bookwyrm/templates/user_admin/user.html:9 -#, fuzzy -#| msgid "Back to reports" msgid "Back to users" -msgstr "Retour aux signalements" +msgstr "Retour aux comptes" #: bookwyrm/templates/user_admin/user_admin.html:7 #, python-format -msgid "Users: %(server_name)s" -msgstr "Comptes : %(server_name)s" +msgid "Users: %(instance_name)s" +msgstr "Comptes : %(instance_name)s" #: bookwyrm/templates/user_admin/user_admin.html:22 #: bookwyrm/templates/user_admin/username_filter.html:5 @@ -2943,8 +2873,8 @@ msgid "Last Active" msgstr "Dernière activité" #: bookwyrm/templates/user_admin/user_admin.html:38 -msgid "Remote server" -msgstr "Serveur distant" +msgid "Remote instance" +msgstr "Instance distante" #: bookwyrm/templates/user_admin/user_admin.html:47 msgid "Active" @@ -2960,42 +2890,45 @@ msgid "Not set" msgstr "Non défini" #: bookwyrm/templates/user_admin/user_info.html:5 -#, fuzzy -#| msgid "Details" msgid "User details" -msgstr "Détails" +msgstr "Détails du compte" #: bookwyrm/templates/user_admin/user_info.html:14 msgid "View user profile" msgstr "Voir le profil" #: bookwyrm/templates/user_admin/user_info.html:20 -#, fuzzy -#| msgid "Instance Settings" msgid "Instance details" -msgstr "Paramètres de l’instance" +msgstr "Détails de l’instance" #: bookwyrm/templates/user_admin/user_info.html:46 msgid "View instance" -msgstr "" +msgstr "Voir l’instance" #: bookwyrm/templates/user_admin/user_moderation_actions.html:11 msgid "Suspend user" -msgstr "" +msgstr "Suspendre le compte" #: bookwyrm/templates/user_admin/user_moderation_actions.html:13 msgid "Un-suspend user" -msgstr "" +msgstr "Rétablir le compte" #: bookwyrm/templates/user_admin/user_moderation_actions.html:21 msgid "Access level:" -msgstr "" +msgstr "Niveau d’accès :" + +#: bookwyrm/templates/widgets/clearable_file_input_with_warning.html:22 +msgid "File exceeds maximum size: 10MB" +msgstr "Ce fichier dépasse la taille limite : 10 Mo" + +#: bookwyrm/templatetags/utilities.py:30 +#, python-format +msgid "%(title)s: %(subtitle)s" +msgstr "%(title)s (%(subtitle)s)" #: bookwyrm/views/import_data.py:67 -#, fuzzy -#| msgid "Email address:" msgid "Not a valid csv file" -msgstr "Adresse email :" +msgstr "Fichier CSV non valide" #: bookwyrm/views/password.py:32 msgid "No user with that email address was found." @@ -3056,12 +2989,12 @@ msgstr "Un lien de réinitialisation a été envoyé à %s." #~ msgid "Enter a number." #~ msgstr "Numéro dans la série :" -#, fuzzy, python-format +#, fuzzy #~| msgid "A user with this email already exists." #~ msgid "%(model_name)s with this %(field_labels)s already exists." #~ msgstr "Cet email est déjà associé à un compte." -#, fuzzy, python-format +#, fuzzy #~| msgid "%(value)s is not a valid remote_id" #~ msgid "Value %(value)r is not a valid choice." #~ msgstr "%(value)s n’est pas une remote_id valide." @@ -3071,7 +3004,7 @@ msgstr "Un lien de réinitialisation a été envoyé à %s." #~ msgid "This field cannot be null." #~ msgstr "Cette étagère est vide" -#, fuzzy, python-format +#, fuzzy #~| msgid "A user with this email already exists." #~ msgid "%(model_name)s with this %(field_label)s already exists." #~ msgstr "Cet email est déjà associé à un compte." @@ -3121,7 +3054,7 @@ msgstr "Un lien de réinitialisation a été envoyé à %s." #~ msgid "Positive small integer" #~ msgstr "Aucune invitation active" -#, fuzzy, python-format +#, fuzzy #~| msgid "%(value)s is not a valid username" #~ msgid "“%(value)s” is not a valid UUID." #~ msgstr "%(value)s n’est pas un nom de compte valide." @@ -3136,12 +3069,12 @@ msgstr "Un lien de réinitialisation a été envoyé à %s." #~ msgid "One-to-one relationship" #~ msgstr "Relations" -#, fuzzy, python-format +#, fuzzy #~| msgid "Relationships" #~ msgid "%(from)s-%(to)s relationship" #~ msgstr "Relations" -#, fuzzy, python-format +#, fuzzy #~| msgid "Relationships" #~ msgid "%(from)s-%(to)s relationships" #~ msgstr "Relations" @@ -3201,7 +3134,7 @@ msgstr "Un lien de réinitialisation a été envoyé à %s." #~ msgid "Order" #~ msgstr "Trier par" -#, fuzzy, python-format +#, fuzzy #~| msgid "%(value)s is not a valid username" #~ msgid "“%(pk)s” is not a valid value." #~ msgstr "%(value)s n’est pas un nom de compte valide." @@ -3265,7 +3198,7 @@ msgstr "Un lien de réinitialisation a été envoyé à %s." #~ msgid "This is not a valid IPv6 address." #~ msgstr "Adresse email :" -#, fuzzy, python-format +#, fuzzy #~| msgid "No books found matching the query \"%(query)s\"" #~ msgid "No %(verbose_name)s found matching the query" #~ msgstr "Aucun livre trouvé pour la requête « %(query)s »" @@ -3284,11 +3217,9 @@ msgstr "Un lien de réinitialisation a été envoyé à %s." #~ msgid "Matching Users" #~ msgstr "Comptes correspondants" -#, python-format #~ msgid "Set a reading goal for %(year)s" #~ msgstr "Définir un défi lecture pour %(year)s" -#, python-format #~ msgid "by %(author)s" #~ msgstr "par %(author)s" @@ -3298,21 +3229,12 @@ msgstr "Un lien de réinitialisation a été envoyé à %s." #~ msgid "Reactivate user" #~ msgstr "Réactiver le compte" -#, python-format -#~ msgid "%(rating)s star" -#~ msgid_plural "%(rating)s stars" -#~ msgstr[0] "%(rating)s étoile" -#~ msgstr[1] "%(rating)s étoiles" - -#, python-format #~ msgid "replied to %(username)s's review" #~ msgstr "a répondu à la critique de %(username)s" -#, python-format #~ msgid "replied to %(username)s's comment" #~ msgstr "a répondu au commentaire de %(username)s" -#, python-format #~ msgid "replied to %(username)s's quote" #~ msgstr "a répondu à la citation de %(username)s" @@ -3322,7 +3244,6 @@ msgstr "Un lien de réinitialisation a été envoyé à %s." #~ msgid "Add tag" #~ msgstr "Ajouter un tag" -#, python-format #~ msgid "Books tagged \"%(tag.name)s\"" #~ msgstr "Livres tagués « %(tag.name)s »" diff --git a/locale/zh_Hans/LC_MESSAGES/django.mo b/locale/zh_Hans/LC_MESSAGES/django.mo index 17b3df695..5e5b36dcb 100644 Binary files a/locale/zh_Hans/LC_MESSAGES/django.mo and b/locale/zh_Hans/LC_MESSAGES/django.mo differ diff --git a/locale/zh_Hans/LC_MESSAGES/django.po b/locale/zh_Hans/LC_MESSAGES/django.po index 0ec8b1d88..4106db438 100644 --- a/locale/zh_Hans/LC_MESSAGES/django.po +++ b/locale/zh_Hans/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.1.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-05-20 14:40-0700\n" +"POT-Creation-Date: 2021-06-06 20:52+0000\n" "PO-Revision-Date: 2021-03-20 00:56+0000\n" "Last-Translator: Kana \n" "Language-Team: Mouse Reeve \n" @@ -58,12 +58,12 @@ msgid "Book Title" msgstr "标题" #: bookwyrm/forms.py:301 bookwyrm/templates/snippets/create_status_form.html:34 -#: bookwyrm/templates/user/shelf/shelf.html:84 -#: bookwyrm/templates/user/shelf/shelf.html:115 +#: bookwyrm/templates/user/shelf/shelf.html:85 +#: bookwyrm/templates/user/shelf/shelf.html:116 msgid "Rating" msgstr "评价" -#: bookwyrm/forms.py:303 bookwyrm/templates/lists/list.html:101 +#: bookwyrm/forms.py:303 bookwyrm/templates/lists/list.html:107 msgid "Sort By" msgstr "" @@ -79,41 +79,41 @@ msgstr "升序排序" msgid "Descending" msgstr "升序排序" -#: bookwyrm/models/fields.py:24 +#: bookwyrm/models/fields.py:25 #, python-format msgid "%(value)s is not a valid remote_id" msgstr "%(value)s 不是有效的 remote_id" -#: bookwyrm/models/fields.py:33 bookwyrm/models/fields.py:42 +#: bookwyrm/models/fields.py:34 bookwyrm/models/fields.py:43 #, python-format msgid "%(value)s is not a valid username" msgstr "%(value)s 不是有效的用户名" -#: bookwyrm/models/fields.py:165 bookwyrm/templates/layout.html:155 +#: bookwyrm/models/fields.py:166 bookwyrm/templates/layout.html:152 msgid "username" msgstr "用户名" -#: bookwyrm/models/fields.py:170 +#: bookwyrm/models/fields.py:171 msgid "A user with that username already exists." msgstr "已经存在使用该用户名的用户。" -#: bookwyrm/settings.py:155 +#: bookwyrm/settings.py:156 msgid "English" msgstr "English(英语)" -#: bookwyrm/settings.py:156 +#: bookwyrm/settings.py:157 msgid "German" msgstr "Deutsch(德语)" -#: bookwyrm/settings.py:157 +#: bookwyrm/settings.py:158 msgid "Spanish" msgstr "Español(西班牙语)" -#: bookwyrm/settings.py:158 +#: bookwyrm/settings.py:159 msgid "French" msgstr "Français(法语)" -#: bookwyrm/settings.py:159 +#: bookwyrm/settings.py:160 msgid "Simplified Chinese" msgstr "简体中文" @@ -260,7 +260,7 @@ msgstr "Goodreads key:" #: bookwyrm/templates/book/edit_book.html:263 #: bookwyrm/templates/lists/form.html:42 #: bookwyrm/templates/preferences/edit_user.html:70 -#: bookwyrm/templates/settings/announcement_form.html:65 +#: bookwyrm/templates/settings/announcement_form.html:69 #: bookwyrm/templates/settings/edit_server.html:68 #: bookwyrm/templates/settings/federated_server.html:98 #: bookwyrm/templates/settings/site.html:97 @@ -386,7 +386,7 @@ msgstr "主题" msgid "Places" msgstr "地点" -#: bookwyrm/templates/book/book.html:276 bookwyrm/templates/layout.html:64 +#: bookwyrm/templates/book/book.html:276 bookwyrm/templates/layout.html:61 #: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 #: bookwyrm/templates/search/layout.html:25 #: bookwyrm/templates/search/layout.html:50 @@ -400,7 +400,7 @@ msgstr "添加到列表" #: bookwyrm/templates/book/book.html:297 #: bookwyrm/templates/book/cover_modal.html:31 -#: bookwyrm/templates/lists/list.html:164 +#: bookwyrm/templates/lists/list.html:179 msgid "Add" msgstr "添加" @@ -532,7 +532,7 @@ msgid "John Doe, Jane Smith" msgstr "张三, 李四" #: bookwyrm/templates/book/edit_book.html:183 -#: bookwyrm/templates/user/shelf/shelf.html:77 +#: bookwyrm/templates/user/shelf/shelf.html:78 msgid "Cover" msgstr "封面" @@ -655,7 +655,7 @@ msgstr "跨站社区" #: bookwyrm/templates/directory/directory.html:4 #: bookwyrm/templates/directory/directory.html:9 -#: bookwyrm/templates/layout.html:92 +#: bookwyrm/templates/layout.html:64 msgid "Directory" msgstr "目录" @@ -854,7 +854,7 @@ msgid "Direct Messages with %(username)s" msgstr "与 %(username)s 私信" #: bookwyrm/templates/feed/direct_messages.html:10 -#: bookwyrm/templates/layout.html:87 +#: bookwyrm/templates/layout.html:92 msgid "Direct Messages" msgstr "私信" @@ -910,7 +910,6 @@ msgid "Updates" msgstr "更新" #: bookwyrm/templates/feed/feed_layout.html:10 -#: bookwyrm/templates/layout.html:58 #: bookwyrm/templates/user/shelf/books_header.html:3 msgid "Your books" msgstr "你的书目" @@ -963,7 +962,7 @@ msgid "What are you reading?" msgstr "你在阅读什么?" #: bookwyrm/templates/get_started/books.html:9 -#: bookwyrm/templates/lists/list.html:120 +#: bookwyrm/templates/lists/list.html:135 msgid "Search for a book" msgstr "搜索书目" @@ -983,7 +982,7 @@ msgstr "你可以在开始使用 %(site_name)s 后添加书目。" #: bookwyrm/templates/get_started/users.html:18 #: bookwyrm/templates/get_started/users.html:19 #: bookwyrm/templates/layout.html:37 bookwyrm/templates/layout.html:38 -#: bookwyrm/templates/lists/list.html:124 +#: bookwyrm/templates/lists/list.html:139 #: bookwyrm/templates/search/layout.html:4 #: bookwyrm/templates/search/layout.html:9 msgid "Search" @@ -999,7 +998,7 @@ msgid "Popular on %(site_name)s" msgstr "%(site_name)s 上的热门" #: bookwyrm/templates/get_started/books.html:58 -#: bookwyrm/templates/lists/list.html:137 +#: bookwyrm/templates/lists/list.html:152 msgid "No books found" msgstr "没有找到书目" @@ -1111,7 +1110,7 @@ msgid "%(username)s's %(year)s Books" msgstr "%(username)s 在 %(year)s 的书目" #: bookwyrm/templates/import.html:5 bookwyrm/templates/import.html:9 -#: bookwyrm/templates/layout.html:97 +#: bookwyrm/templates/user/shelf/shelf.html:40 msgid "Import Books" msgstr "导入书目" @@ -1196,14 +1195,14 @@ msgstr "书目" #: bookwyrm/templates/import_status.html:114 #: bookwyrm/templates/snippets/create_status_form.html:13 -#: bookwyrm/templates/user/shelf/shelf.html:78 -#: bookwyrm/templates/user/shelf/shelf.html:98 +#: bookwyrm/templates/user/shelf/shelf.html:79 +#: bookwyrm/templates/user/shelf/shelf.html:99 msgid "Title" msgstr "标题" #: bookwyrm/templates/import_status.html:117 -#: bookwyrm/templates/user/shelf/shelf.html:79 -#: bookwyrm/templates/user/shelf/shelf.html:101 +#: bookwyrm/templates/user/shelf/shelf.html:80 +#: bookwyrm/templates/user/shelf/shelf.html:102 msgid "Author" msgstr "作者" @@ -1245,15 +1244,21 @@ msgstr "搜索书目或用户" msgid "Main navigation menu" msgstr "主导航菜单" -#: bookwyrm/templates/layout.html:61 +#: bookwyrm/templates/layout.html:58 msgid "Feed" msgstr "动态" -#: bookwyrm/templates/layout.html:102 +#: bookwyrm/templates/layout.html:87 +#, fuzzy +#| msgid "Your books" +msgid "Your Books" +msgstr "你的书目" + +#: bookwyrm/templates/layout.html:97 msgid "Settings" msgstr "设置" -#: bookwyrm/templates/layout.html:111 +#: bookwyrm/templates/layout.html:106 #: bookwyrm/templates/settings/admin_layout.html:31 #: bookwyrm/templates/settings/manage_invite_requests.html:15 #: bookwyrm/templates/settings/manage_invites.html:3 @@ -1261,45 +1266,47 @@ msgstr "设置" msgid "Invites" msgstr "邀请" -#: bookwyrm/templates/layout.html:118 +#: bookwyrm/templates/layout.html:113 msgid "Admin" msgstr "管理员" -#: bookwyrm/templates/layout.html:125 +#: bookwyrm/templates/layout.html:120 msgid "Log out" msgstr "登出" -#: bookwyrm/templates/layout.html:133 bookwyrm/templates/layout.html:134 +#: bookwyrm/templates/layout.html:128 bookwyrm/templates/layout.html:129 #: bookwyrm/templates/notifications.html:6 #: bookwyrm/templates/notifications.html:11 msgid "Notifications" msgstr "通知" -#: bookwyrm/templates/layout.html:154 bookwyrm/templates/layout.html:158 +#: bookwyrm/templates/layout.html:151 bookwyrm/templates/layout.html:155 #: bookwyrm/templates/login.html:17 #: bookwyrm/templates/snippets/register_form.html:4 msgid "Username:" msgstr "用户名:" -#: bookwyrm/templates/layout.html:159 +#: bookwyrm/templates/layout.html:156 msgid "password" msgstr "密码" -#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:36 +#: bookwyrm/templates/layout.html:157 bookwyrm/templates/login.html:36 msgid "Forgot your password?" msgstr "忘记了密码?" -#: bookwyrm/templates/layout.html:163 bookwyrm/templates/login.html:10 +#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:10 #: bookwyrm/templates/login.html:33 msgid "Log in" msgstr "登录" -#: bookwyrm/templates/layout.html:171 +#: bookwyrm/templates/layout.html:168 msgid "Join" msgstr "加入" #: bookwyrm/templates/layout.html:206 -msgid "About this server" +#, fuzzy +#| msgid "About this server" +msgid "About this instance" msgstr "关于本服务器" #: bookwyrm/templates/layout.html:210 @@ -1361,7 +1368,7 @@ msgid "Discard" msgstr "削除" #: bookwyrm/templates/lists/edit_form.html:5 -#: bookwyrm/templates/lists/list_layout.html:17 +#: bookwyrm/templates/lists/list_layout.html:16 msgid "Edit List" msgstr "编辑列表" @@ -1410,62 +1417,63 @@ msgstr "任何人都可以向此列表中添加书目" msgid "This list is currently empty" msgstr "此列表当前是空的" -#: bookwyrm/templates/lists/list.html:65 +#: bookwyrm/templates/lists/list.html:66 #, python-format msgid "Added by %(username)s" msgstr "由 %(username)s 添加" -#: bookwyrm/templates/lists/list.html:77 -#, fuzzy -#| msgid "Sent" -msgid "Set" -msgstr "已发送" - -#: bookwyrm/templates/lists/list.html:80 +#: bookwyrm/templates/lists/list.html:74 #, fuzzy #| msgid "List curation:" msgid "List position" msgstr "列表策展:" -#: bookwyrm/templates/lists/list.html:86 +#: bookwyrm/templates/lists/list.html:81 +#, fuzzy +#| msgid "Sent" +msgid "Set" +msgstr "已发送" + +#: bookwyrm/templates/lists/list.html:89 #: bookwyrm/templates/snippets/shelf_selector.html:26 msgid "Remove" msgstr "移除" -#: bookwyrm/templates/lists/list.html:99 bookwyrm/templates/lists/list.html:111 +#: bookwyrm/templates/lists/list.html:103 +#: bookwyrm/templates/lists/list.html:120 #, fuzzy #| msgid "Your Lists" msgid "Sort List" msgstr "你的列表" -#: bookwyrm/templates/lists/list.html:105 +#: bookwyrm/templates/lists/list.html:113 #, fuzzy #| msgid "Directory" msgid "Direction" msgstr "目录" -#: bookwyrm/templates/lists/list.html:116 +#: bookwyrm/templates/lists/list.html:127 msgid "Add Books" msgstr "添加书目" -#: bookwyrm/templates/lists/list.html:116 +#: bookwyrm/templates/lists/list.html:129 msgid "Suggest Books" msgstr "推荐书目" -#: bookwyrm/templates/lists/list.html:125 +#: bookwyrm/templates/lists/list.html:140 msgid "search" msgstr "搜索" -#: bookwyrm/templates/lists/list.html:131 +#: bookwyrm/templates/lists/list.html:146 msgid "Clear search" msgstr "清除搜索" -#: bookwyrm/templates/lists/list.html:136 +#: bookwyrm/templates/lists/list.html:151 #, python-format msgid "No books found matching the query \"%(query)s\"" msgstr "没有符合 \"%(query)s\" 请求的书目" -#: bookwyrm/templates/lists/list.html:164 +#: bookwyrm/templates/lists/list.html:179 msgid "Suggest" msgstr "推荐" @@ -1558,8 +1566,8 @@ msgstr "已解决" #: bookwyrm/templates/moderation/reports.html:6 #, python-format -msgid "Reports: %(server_name)s" -msgstr "报告: %(server_name)s" +msgid "Reports: %(instance_name)s" +msgstr "报告: %(instance_name)s" #: bookwyrm/templates/moderation/reports.html:8 #: bookwyrm/templates/moderation/reports.html:17 @@ -1569,8 +1577,8 @@ msgstr "报告" #: bookwyrm/templates/moderation/reports.html:14 #, python-format -msgid "Reports: %(server_name)s" -msgstr "报告: %(server_name)s" +msgid "Reports: %(instance_name)s" +msgstr "报告: %(instance_name)s" #: bookwyrm/templates/moderation/reports.html:28 msgid "Resolved" @@ -1771,6 +1779,26 @@ msgstr "个人资料" msgid "Relationships" msgstr "关系" +#: bookwyrm/templates/rss/title.html:5 +#: bookwyrm/templates/snippets/status/status_header.html:35 +msgid "rated" +msgstr "评价了" + +#: bookwyrm/templates/rss/title.html:7 +#: bookwyrm/templates/snippets/status/status_header.html:37 +msgid "reviewed" +msgstr "写了书评给" + +#: bookwyrm/templates/rss/title.html:9 +#: bookwyrm/templates/snippets/status/status_header.html:39 +msgid "commented on" +msgstr "评论了" + +#: bookwyrm/templates/rss/title.html:11 +#: bookwyrm/templates/snippets/status/status_header.html:41 +msgid "quoted" +msgstr "引用了" + #: bookwyrm/templates/search/book.html:64 #, fuzzy #| msgid "Show results from other catalogues" @@ -1828,7 +1856,9 @@ msgstr "管理用户" #: bookwyrm/templates/settings/admin_layout.html:39 #: bookwyrm/templates/settings/federation.html:3 #: bookwyrm/templates/settings/federation.html:5 -msgid "Federated Servers" +#, fuzzy +#| msgid "Federated Servers" +msgid "Federated Instances" msgstr "互联的服务器" #: bookwyrm/templates/settings/admin_layout.html:44 @@ -1882,6 +1912,7 @@ msgid "Back to list" msgstr "回到服务器列表" #: bookwyrm/templates/settings/announcement.html:11 +#: bookwyrm/templates/settings/announcement_form.html:6 #, fuzzy #| msgid "Announcements" msgid "Edit Announcement" @@ -1923,7 +1954,7 @@ msgstr "出生日期:" msgid "Active:" msgstr "活跃" -#: bookwyrm/templates/settings/announcement_form.html:5 +#: bookwyrm/templates/settings/announcement_form.html:8 #: bookwyrm/templates/settings/announcements.html:8 #, fuzzy #| msgid "Announcements" @@ -1982,13 +2013,15 @@ msgstr "停用" #: bookwyrm/templates/settings/server_blocklist.html:3 #: bookwyrm/templates/settings/server_blocklist.html:20 #, fuzzy -#| msgid "Add cover" -msgid "Add server" -msgstr "添加封面" +#| msgid "Instance Name:" +msgid "Add instance" +msgstr "实例名称" #: bookwyrm/templates/settings/edit_server.html:7 #: bookwyrm/templates/settings/server_blocklist.html:7 -msgid "Back to server list" +#, fuzzy +#| msgid "Back to server list" +msgid "Back to instance list" msgstr "回到服务器列表" #: bookwyrm/templates/settings/edit_server.html:16 @@ -2103,8 +2136,10 @@ msgstr "" #: bookwyrm/templates/settings/federation.html:19 #: bookwyrm/templates/user_admin/server_filter.html:5 -msgid "Server name" -msgstr "服务器名称" +#, fuzzy +#| msgid "Instance Name:" +msgid "Instance name" +msgstr "实例名称" #: bookwyrm/templates/settings/federation.html:23 msgid "Date federated" @@ -2231,7 +2266,7 @@ msgid "Import Blocklist" msgstr "导入书目" #: bookwyrm/templates/settings/server_blocklist.html:26 -#: bookwyrm/templates/snippets/goal_progress.html:5 +#: bookwyrm/templates/snippets/goal_progress.html:7 msgid "Success!" msgstr "成功!" @@ -2320,15 +2355,15 @@ msgstr "没有封面" msgid "%(title)s by " msgstr "%(title)s 来自" -#: bookwyrm/templates/snippets/boost_button.html:9 -#: bookwyrm/templates/snippets/boost_button.html:10 +#: bookwyrm/templates/snippets/boost_button.html:20 +#: bookwyrm/templates/snippets/boost_button.html:21 #, fuzzy #| msgid "boosted" msgid "Boost" msgstr "转发了" -#: bookwyrm/templates/snippets/boost_button.html:16 -#: bookwyrm/templates/snippets/boost_button.html:17 +#: bookwyrm/templates/snippets/boost_button.html:33 +#: bookwyrm/templates/snippets/boost_button.html:34 #, fuzzy #| msgid "Un-boost status" msgid "Un-boost" @@ -2422,13 +2457,13 @@ msgstr "删除这些阅读日期吗?" msgid "You are deleting this readthrough and its %(count)s associated progress updates." msgstr "你正要删除这篇阅读经过以及与之相关的 %(count)s 次进度更新。" -#: bookwyrm/templates/snippets/fav_button.html:9 -#: bookwyrm/templates/snippets/fav_button.html:11 +#: bookwyrm/templates/snippets/fav_button.html:10 +#: bookwyrm/templates/snippets/fav_button.html:12 msgid "Like" msgstr "" -#: bookwyrm/templates/snippets/fav_button.html:17 #: bookwyrm/templates/snippets/fav_button.html:18 +#: bookwyrm/templates/snippets/fav_button.html:19 #, fuzzy #| msgid "Un-like status" msgid "Un-like" @@ -2471,6 +2506,13 @@ msgstr "接受" msgid "No rating" msgstr "没有评价" +#: bookwyrm/templates/snippets/form_rate_stars.html:44 +#: bookwyrm/templates/snippets/stars.html:7 +#, python-format +msgid "%(rating)s star" +msgid_plural "%(rating)s stars" +msgstr[0] "" + #: bookwyrm/templates/snippets/generated_status/goal.html:1 #, python-format msgid "set a goal to read %(counter)s book in %(year)s" @@ -2522,17 +2564,17 @@ msgstr "发布到消息流中" msgid "Set goal" msgstr "设置目标" -#: bookwyrm/templates/snippets/goal_progress.html:7 +#: bookwyrm/templates/snippets/goal_progress.html:9 #, python-format msgid "%(percent)s%% complete!" msgstr "完成了 %(percent)s%% !" -#: bookwyrm/templates/snippets/goal_progress.html:10 +#: bookwyrm/templates/snippets/goal_progress.html:12 #, python-format msgid "You've read %(read_count)s of %(goal_count)s books." msgstr "你已经阅读了 %(goal_count)s 本书中的 %(read_count)s 本。" -#: bookwyrm/templates/snippets/goal_progress.html:12 +#: bookwyrm/templates/snippets/goal_progress.html:14 #, python-format msgid "%(username)s has read %(read_count)s of %(goal_count)s books." msgstr "%(username)s 已经阅读了 %(goal_count)s 本书中的 %(read_count)s 本。" @@ -2641,26 +2683,6 @@ msgstr "注册" msgid "Report" msgstr "报告" -#: bookwyrm/templates/snippets/rss_title.html:5 -#: bookwyrm/templates/snippets/status/status_header.html:35 -msgid "rated" -msgstr "评价了" - -#: bookwyrm/templates/snippets/rss_title.html:7 -#: bookwyrm/templates/snippets/status/status_header.html:37 -msgid "reviewed" -msgstr "写了书评给" - -#: bookwyrm/templates/snippets/rss_title.html:9 -#: bookwyrm/templates/snippets/status/status_header.html:39 -msgid "commented on" -msgstr "评论了" - -#: bookwyrm/templates/snippets/rss_title.html:11 -#: bookwyrm/templates/snippets/status/status_header.html:41 -msgid "quoted" -msgstr "引用了" - #: bookwyrm/templates/snippets/search_result_text.html:36 msgid "Import book" msgstr "导入书目" @@ -2832,7 +2854,7 @@ msgstr "编辑书架" msgid "Update shelf" msgstr "更新书架" -#: bookwyrm/templates/user/shelf/shelf.html:25 bookwyrm/views/shelf.py:51 +#: bookwyrm/templates/user/shelf/shelf.html:25 bookwyrm/views/shelf.py:56 msgid "All books" msgstr "所有书目" @@ -2840,30 +2862,30 @@ msgstr "所有书目" msgid "Create shelf" msgstr "创建书架" -#: bookwyrm/templates/user/shelf/shelf.html:61 +#: bookwyrm/templates/user/shelf/shelf.html:62 msgid "Edit shelf" msgstr "编辑书架" -#: bookwyrm/templates/user/shelf/shelf.html:80 -#: bookwyrm/templates/user/shelf/shelf.html:104 +#: bookwyrm/templates/user/shelf/shelf.html:81 +#: bookwyrm/templates/user/shelf/shelf.html:105 msgid "Shelved" msgstr "上架时间" -#: bookwyrm/templates/user/shelf/shelf.html:81 -#: bookwyrm/templates/user/shelf/shelf.html:108 +#: bookwyrm/templates/user/shelf/shelf.html:82 +#: bookwyrm/templates/user/shelf/shelf.html:109 msgid "Started" msgstr "开始时间" -#: bookwyrm/templates/user/shelf/shelf.html:82 -#: bookwyrm/templates/user/shelf/shelf.html:111 +#: bookwyrm/templates/user/shelf/shelf.html:83 +#: bookwyrm/templates/user/shelf/shelf.html:112 msgid "Finished" msgstr "完成时间" -#: bookwyrm/templates/user/shelf/shelf.html:137 +#: bookwyrm/templates/user/shelf/shelf.html:138 msgid "This shelf is empty." msgstr "此书架是空的。" -#: bookwyrm/templates/user/shelf/shelf.html:143 +#: bookwyrm/templates/user/shelf/shelf.html:144 msgid "Delete shelf" msgstr "删除书架" @@ -2924,8 +2946,8 @@ msgstr "回到报告" #: bookwyrm/templates/user_admin/user_admin.html:7 #, python-format -msgid "Users: %(server_name)s" -msgstr "用户: %(server_name)s" +msgid "Users: %(instance_name)s" +msgstr "用户: %(instance_name)s" #: bookwyrm/templates/user_admin/user_admin.html:22 #: bookwyrm/templates/user_admin/username_filter.html:5 @@ -2941,7 +2963,7 @@ msgid "Last Active" msgstr "最后或缺" #: bookwyrm/templates/user_admin/user_admin.html:38 -msgid "Remote server" +msgid "Remote instance" msgstr "移除服务器" #: bookwyrm/templates/user_admin/user_admin.html:47 @@ -2989,6 +3011,10 @@ msgstr "" msgid "Access level:" msgstr "" +#: bookwyrm/templates/widgets/clearable_file_input_with_warning.html:3 +msgid "File exceeds maximum size: 10MB" +msgstr "" + #: bookwyrm/views/import_data.py:67 #, fuzzy #| msgid "Email address:" @@ -3004,6 +3030,23 @@ msgstr "没有找到使用该邮箱的用户。" msgid "A password reset link sent to %s" msgstr "密码重置连接已发送给 %s" +#~ msgid "Reports: %(server_name)s" +#~ msgstr "报告: %(server_name)s" + +#~ msgid "Federated Servers" +#~ msgstr "互联的服务器" + +#~ msgid "Server name" +#~ msgstr "服务器名称" + +#, fuzzy +#~| msgid "Add cover" +#~ msgid "Add server" +#~ msgstr "添加封面" + +#~ msgid "Remote server" +#~ msgstr "移除服务器" + #, fuzzy #~| msgid "BookWyrm users" #~ msgid "BookWyrm\\" @@ -3054,12 +3097,12 @@ msgstr "密码重置连接已发送给 %s" #~ msgid "Enter a number." #~ msgstr "系列编号:" -#, fuzzy, python-format +#, fuzzy #~| msgid "A user with this email already exists." #~ msgid "%(model_name)s with this %(field_labels)s already exists." #~ msgstr "已经存在使用该邮箱的用户。" -#, fuzzy, python-format +#, fuzzy #~| msgid "%(value)s is not a valid remote_id" #~ msgid "Value %(value)r is not a valid choice." #~ msgstr "%(value)s 不是有效的 remote_id" @@ -3069,7 +3112,7 @@ msgstr "密码重置连接已发送给 %s" #~ msgid "This field cannot be null." #~ msgstr "此书架是空的。" -#, fuzzy, python-format +#, fuzzy #~| msgid "A user with this email already exists." #~ msgid "%(model_name)s with this %(field_label)s already exists." #~ msgstr "已经存在使用该邮箱的用户。" @@ -3119,7 +3162,7 @@ msgstr "密码重置连接已发送给 %s" #~ msgid "Positive small integer" #~ msgstr "无有效的邀请" -#, fuzzy, python-format +#, fuzzy #~| msgid "%(value)s is not a valid username" #~ msgid "“%(value)s” is not a valid UUID." #~ msgstr "%(value)s 不是有效的用户名" @@ -3134,12 +3177,12 @@ msgstr "密码重置连接已发送给 %s" #~ msgid "One-to-one relationship" #~ msgstr "关系" -#, fuzzy, python-format +#, fuzzy #~| msgid "Relationships" #~ msgid "%(from)s-%(to)s relationship" #~ msgstr "关系" -#, fuzzy, python-format +#, fuzzy #~| msgid "Relationships" #~ msgid "%(from)s-%(to)s relationships" #~ msgstr "关系" @@ -3199,7 +3242,7 @@ msgstr "密码重置连接已发送给 %s" #~ msgid "Order" #~ msgstr "排列顺序" -#, fuzzy, python-format +#, fuzzy #~| msgid "%(value)s is not a valid username" #~ msgid "“%(pk)s” is not a valid value." #~ msgstr "%(value)s 不是有效的用户名" @@ -3263,7 +3306,7 @@ msgstr "密码重置连接已发送给 %s" #~ msgid "This is not a valid IPv6 address." #~ msgstr "邮箱地址:" -#, fuzzy, python-format +#, fuzzy #~| msgid "No books found matching the query \"%(query)s\"" #~ msgid "No %(verbose_name)s found matching the query" #~ msgstr "没有符合 \"%(query)s\" 请求的书目" @@ -3282,11 +3325,9 @@ msgstr "密码重置连接已发送给 %s" #~ msgid "Matching Users" #~ msgstr "匹配的用户" -#, python-format #~ msgid "Set a reading goal for %(year)s" #~ msgstr "设定 %(year)s 的阅读目标" -#, python-format #~ msgid "by %(author)s" #~ msgstr "由 %(author)s 所著" @@ -3299,15 +3340,12 @@ msgstr "密码重置连接已发送给 %s" #~ msgid "Date" #~ msgstr "日期" -#, python-format #~ msgid "replied to %(username)s's review" #~ msgstr "回复了 %(username)s书评" -#, python-format #~ msgid "replied to %(username)s's comment" #~ msgstr "恢复了 %(username)s评论" -#, python-format #~ msgid "replied to %(username)s's quote" #~ msgstr "回复了 %(username)s引用" @@ -3317,7 +3355,6 @@ msgstr "密码重置连接已发送给 %s" #~ msgid "Add tag" #~ msgstr "添加标签" -#, python-format #~ msgid "Books tagged \"%(tag.name)s\"" #~ msgstr "标有 \"%(tag.name)s\" 标签的书" diff --git a/nginx/development b/nginx/development index d38982870..05b27c2b1 100644 --- a/nginx/development +++ b/nginx/development @@ -1,3 +1,5 @@ +include /etc/nginx/conf.d/server_config; + upstream web { server web:8000; } diff --git a/nginx/production b/nginx/production index c5d83cbf6..54c84af45 100644 --- a/nginx/production +++ b/nginx/production @@ -1,3 +1,5 @@ +include /etc/nginx/conf.d/server_config; + upstream web { server web:8000; } diff --git a/nginx/server_config b/nginx/server_config new file mode 100644 index 000000000..c9aad8e4a --- /dev/null +++ b/nginx/server_config @@ -0,0 +1 @@ +client_max_body_size 10m; diff --git a/requirements.txt b/requirements.txt index 289d6fe68..6d29982e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ celery==4.4.2 -Django==3.2.0 +colorthief==0.2.1 +Django==3.2.4 django-model-utils==4.0.0 environs==7.2.0 flower==0.9.4 diff --git a/yarn.lock b/yarn.lock index 21730a685..7686fd113 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2042,9 +2042,9 @@ to-regex-range@^5.0.1: is-number "^7.0.0" trim-newlines@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" - integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== trough@^1.0.0: version "1.0.5"
diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index bad1cb6e5..5e66ff799 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -8,16 +8,21 @@ - + + {% if preview_images_enabled is True %} + + {% else %} + {% endif %} - - + {% block opengraph_images %} + {% include 'snippets/opengraph_images.html' %} + {% endblock %} @@ -25,7 +30,7 @@
- {% trans "Server name" as text %} + {% trans "Instance name" as text %} {% include 'snippets/table-sort-header.html' with field="server_name" sort=sort text=text %} diff --git a/bookwyrm/templates/settings/server_blocklist.html b/bookwyrm/templates/settings/server_blocklist.html index 0de49acd7..171cf1339 100644 --- a/bookwyrm/templates/settings/server_blocklist.html +++ b/bookwyrm/templates/settings/server_blocklist.html @@ -1,10 +1,10 @@ {% extends 'settings/admin_layout.html' %} {% load i18n %} -{% block title %}{% trans "Add server" %}{% endblock %} +{% block title %}{% trans "Add instance" %}{% endblock %} {% block header %} {% trans "Import Blocklist" %} -{% trans "Back to server list" %} +{% trans "Back to instance list" %} {% endblock %} {% block panel %} @@ -17,7 +17,7 @@ {% url 'settings-add-federated-server' as url %}
  • - {% trans "Add server" %} + {% trans "Add instance" %}
  • @@ -51,7 +51,7 @@
     [
         {
    -        "instance": "example.server.com",
    +        "instance": "example.instance.com",
             "url": "https://link.to.more/info"
         },
         ...
    diff --git a/bookwyrm/templates/settings/site.html b/bookwyrm/templates/settings/site.html
    index 1b9424527..d36371a41 100644
    --- a/bookwyrm/templates/settings/site.html
    +++ b/bookwyrm/templates/settings/site.html
    @@ -11,23 +11,23 @@
         {% csrf_token %}
         

    {% trans "Instance Info" %}

    -
    +
    {{ site_form.name }}
    -
    +
    {{ site_form.instance_tagline }}
    -
    +
    {{ site_form.instance_description }}
    -
    +
    {{ site_form.code_of_conduct }}
    -
    +
    {{ site_form.privacy_policy }}
    @@ -57,19 +57,19 @@
    - {% trans "Remote server" as text %} + {% trans "Remote instance" as text %} {% include 'snippets/table-sort-header.html' with field="federated_server__server_name" sort=sort text=text %}