diff --git a/.env.dev.example b/.env.dev.example index 5e605d74..538d1611 100644 --- a/.env.dev.example +++ b/.env.dev.example @@ -7,6 +7,9 @@ DEBUG=true DOMAIN=your.domain.here #EMAIL=your@email.here +# Used for deciding which editions to prefer +DEFAULT_LANGUAGE="English" + ## Leave unset to allow all hosts # ALLOWED_HOSTS="localhost,127.0.0.1,[::1]" diff --git a/.env.prod.example b/.env.prod.example index 0013bf9d..ac9fe70f 100644 --- a/.env.prod.example +++ b/.env.prod.example @@ -7,6 +7,9 @@ DEBUG=false DOMAIN=your.domain.here EMAIL=your@email.here +# Used for deciding which editions to prefer +DEFAULT_LANGUAGE="English" + ## Leave unset to allow all hosts # ALLOWED_HOSTS="localhost,127.0.0.1,[::1]" diff --git a/bookwyrm/activitypub/book.py b/bookwyrm/activitypub/book.py index f6ebf913..1599b408 100644 --- a/bookwyrm/activitypub/book.py +++ b/bookwyrm/activitypub/book.py @@ -7,11 +7,22 @@ from .image import Document @dataclass(init=False) -class Book(ActivityObject): +class BookData(ActivityObject): + """shared fields for all book data and authors""" + + openlibraryKey: str = None + inventaireId: str = None + librarythingKey: str = None + goodreadsKey: str = None + bnfId: str = None + lastEditedBy: str = None + + +@dataclass(init=False) +class Book(BookData): """serializes an edition or work, abstract""" title: str - lastEditedBy: str = None sortTitle: str = "" subtitle: str = "" description: str = "" @@ -25,10 +36,6 @@ class Book(ActivityObject): firstPublishedDate: str = "" publishedDate: str = "" - openlibraryKey: str = "" - librarythingKey: str = "" - goodreadsKey: str = "" - cover: Document = None type: str = "Book" @@ -55,23 +62,21 @@ class Work(Book): """work instance of a book object""" lccn: str = "" - defaultEdition: str = "" editions: List[str] = field(default_factory=lambda: []) type: str = "Work" @dataclass(init=False) -class Author(ActivityObject): +class Author(BookData): """author of a book""" name: str - lastEditedBy: str = None + isni: str = None + viafId: str = None + gutenbergId: str = None born: str = None died: str = None aliases: List[str] = field(default_factory=lambda: []) bio: str = "" - openlibraryKey: str = "" - librarythingKey: str = "" - goodreadsKey: str = "" wikipediaLink: str = "" type: str = "Author" diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py index b501c3d6..ea2e92b6 100644 --- a/bookwyrm/activitypub/note.py +++ b/bookwyrm/activitypub/note.py @@ -83,4 +83,5 @@ class Rating(Comment): rating: int content: str = None + name: str = None # not used, but the model inherits from Review type: str = "Rating" diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py index 264b5a38..76718823 100644 --- a/bookwyrm/connectors/abstract_connector.py +++ b/bookwyrm/connectors/abstract_connector.py @@ -44,7 +44,7 @@ class AbstractMinimalConnector(ABC): if min_confidence: params["min_confidence"] = min_confidence - data = get_data( + data = self.get_search_data( "%s%s" % (self.search_url, query), params=params, ) @@ -57,7 +57,7 @@ class AbstractMinimalConnector(ABC): def isbn_search(self, query): """isbn search""" params = {} - data = get_data( + data = self.get_search_data( "%s%s" % (self.isbn_search_url, query), params=params, ) @@ -68,6 +68,10 @@ class AbstractMinimalConnector(ABC): results.append(self.format_isbn_search_result(doc)) return results + def get_search_data(self, remote_id, **kwargs): # pylint: disable=no-self-use + """this allows connectors to override the default behavior""" + return get_data(remote_id, **kwargs) + @abstractmethod def get_or_create_book(self, remote_id): """pull up a book record by whatever means possible""" @@ -112,12 +116,12 @@ class AbstractConnector(AbstractMinimalConnector): remote_id ) or models.Work.find_existing_by_remote_id(remote_id) if existing: - if hasattr(existing, "get_default_editon"): - return existing.get_default_editon() + if hasattr(existing, "default_edition"): + return existing.default_edition return existing # load the json - data = get_data(remote_id) + data = self.get_book_data(remote_id) mapped_data = dict_from_mappings(data, self.book_mappings) if self.is_work_data(data): try: @@ -128,12 +132,12 @@ class AbstractConnector(AbstractMinimalConnector): edition_data = data work_data = mapped_data else: + edition_data = data try: work_data = self.get_work_from_edition_data(data) work_data = dict_from_mappings(work_data, self.book_mappings) except (KeyError, ConnectorException): work_data = mapped_data - edition_data = data if not work_data or not edition_data: raise ConnectorException("Unable to load book data: %s" % remote_id) @@ -150,6 +154,10 @@ class AbstractConnector(AbstractMinimalConnector): load_more_data.delay(self.connector.id, work.id) return edition + def get_book_data(self, remote_id): # pylint: disable=no-self-use + """this allows connectors to override the default behavior""" + return get_data(remote_id) + def create_edition_from_data(self, work, edition_data): """if we already have the work, we're ready""" mapped_data = dict_from_mappings(edition_data, self.book_mappings) @@ -159,10 +167,6 @@ class AbstractConnector(AbstractMinimalConnector): edition.connector = self.connector edition.save() - if not work.default_edition: - work.default_edition = edition - work.save() - for author in self.get_authors_from_data(edition_data): edition.authors.add(author) if not edition.authors.exists() and work.authors.exists(): @@ -176,7 +180,7 @@ class AbstractConnector(AbstractMinimalConnector): if existing: return existing - data = get_data(remote_id) + data = self.get_book_data(remote_id) mapped_data = dict_from_mappings(data, self.author_mappings) try: @@ -273,6 +277,7 @@ class SearchResult: title: str key: str connector: object + view_link: str = None author: str = None year: str = None cover: str = None diff --git a/bookwyrm/connectors/bookwyrm_connector.py b/bookwyrm/connectors/bookwyrm_connector.py index 640a0bca..10a633b2 100644 --- a/bookwyrm/connectors/bookwyrm_connector.py +++ b/bookwyrm/connectors/bookwyrm_connector.py @@ -7,11 +7,7 @@ class Connector(AbstractMinimalConnector): """this is basically just for search""" def get_or_create_book(self, remote_id): - edition = activitypub.resolve_remote_id(remote_id, model=models.Edition) - work = edition.parent_work - work.default_edition = work.get_default_edition() - work.save() - return edition + return activitypub.resolve_remote_id(remote_id, model=models.Edition) def parse_search_data(self, data): return data diff --git a/bookwyrm/connectors/inventaire.py b/bookwyrm/connectors/inventaire.py new file mode 100644 index 00000000..dc27f2c0 --- /dev/null +++ b/bookwyrm/connectors/inventaire.py @@ -0,0 +1,214 @@ +""" inventaire data connector """ +import re + +from bookwyrm import models +from .abstract_connector import AbstractConnector, SearchResult, Mapping +from .abstract_connector import get_data +from .connector_manager import ConnectorException + + +class Connector(AbstractConnector): + """instantiate a connector for OL""" + + def __init__(self, identifier): + super().__init__(identifier) + + get_first = lambda a: a[0] + shared_mappings = [ + Mapping("id", remote_field="uri", formatter=self.get_remote_id), + Mapping("bnfId", remote_field="wdt:P268", formatter=get_first), + Mapping("openlibraryKey", remote_field="wdt:P648", formatter=get_first), + ] + self.book_mappings = [ + Mapping("title", remote_field="wdt:P1476", formatter=get_first), + Mapping("subtitle", remote_field="wdt:P1680", formatter=get_first), + Mapping("inventaireId", remote_field="uri"), + Mapping( + "description", remote_field="sitelinks", formatter=self.get_description + ), + Mapping("cover", remote_field="image", formatter=self.get_cover_url), + Mapping("isbn13", remote_field="wdt:P212", formatter=get_first), + Mapping("isbn10", remote_field="wdt:P957", formatter=get_first), + Mapping("oclcNumber", remote_field="wdt:P5331", formatter=get_first), + Mapping("goodreadsKey", remote_field="wdt:P2969", formatter=get_first), + Mapping("librarythingKey", remote_field="wdt:P1085", formatter=get_first), + Mapping("languages", remote_field="wdt:P407", formatter=self.resolve_keys), + Mapping("publishers", remote_field="wdt:P123", formatter=self.resolve_keys), + Mapping("publishedDate", remote_field="wdt:P577", formatter=get_first), + Mapping("pages", remote_field="wdt:P1104", formatter=get_first), + Mapping( + "subjectPlaces", remote_field="wdt:P840", formatter=self.resolve_keys + ), + Mapping("subjects", remote_field="wdt:P921", formatter=self.resolve_keys), + Mapping("asin", remote_field="wdt:P5749", formatter=get_first), + ] + shared_mappings + # TODO: P136: genre, P674 characters, P950 bne + + self.author_mappings = [ + Mapping("id", remote_field="uri", formatter=self.get_remote_id), + Mapping("name", remote_field="labels", formatter=get_language_code), + Mapping("bio", remote_field="sitelinks", formatter=self.get_description), + Mapping("goodreadsKey", remote_field="wdt:P2963", formatter=get_first), + Mapping("isni", remote_field="wdt:P213", formatter=get_first), + Mapping("viafId", remote_field="wdt:P214", formatter=get_first), + Mapping("gutenberg_id", remote_field="wdt:P1938", formatter=get_first), + Mapping("born", remote_field="wdt:P569", formatter=get_first), + Mapping("died", remote_field="wdt:P570", formatter=get_first), + ] + shared_mappings + + def get_remote_id(self, value): + """convert an id/uri into a url""" + return "{:s}?action=by-uris&uris={:s}".format(self.books_url, value) + + def get_book_data(self, remote_id): + data = get_data(remote_id) + extracted = list(data.get("entities").values()) + try: + data = extracted[0] + except KeyError: + raise ConnectorException("Invalid book data") + # flatten the data so that images, uri, and claims are on the same level + return { + **data.get("claims", {}), + **{k: data.get(k) for k in ["uri", "image", "labels", "sitelinks"]}, + } + + def parse_search_data(self, data): + return data.get("results") + + def format_search_result(self, search_result): + images = search_result.get("image") + cover = ( + "{:s}/img/entities/{:s}".format(self.covers_url, images[0]) + if images + else None + ) + return SearchResult( + title=search_result.get("label"), + key=self.get_remote_id(search_result.get("uri")), + author=search_result.get("description"), + view_link="{:s}/entity/{:s}".format( + self.base_url, search_result.get("uri") + ), + cover=cover, + connector=self, + ) + + def parse_isbn_search_data(self, data): + """got some daaaata""" + results = data.get("entities") + if not results: + return [] + return list(results.values()) + + def format_isbn_search_result(self, search_result): + """totally different format than a regular search result""" + title = search_result.get("claims", {}).get("wdt:P1476", []) + if not title: + return None + return SearchResult( + title=title[0], + key=self.get_remote_id(search_result.get("uri")), + author=search_result.get("description"), + view_link="{:s}/entity/{:s}".format( + self.base_url, search_result.get("uri") + ), + cover=self.get_cover_url(search_result.get("image")), + connector=self, + ) + + def is_work_data(self, data): + return data.get("type") == "work" + + def load_edition_data(self, work_uri): + """get a list of editions for a work""" + url = "{:s}?action=reverse-claims&property=wdt:P629&value={:s}".format( + self.books_url, work_uri + ) + return get_data(url) + + def get_edition_from_work_data(self, data): + data = self.load_edition_data(data.get("uri")) + try: + uri = data["uris"][0] + except KeyError: + raise ConnectorException("Invalid book data") + return self.get_book_data(self.get_remote_id(uri)) + + def get_work_from_edition_data(self, data): + try: + uri = data["claims"]["wdt:P629"] + except KeyError: + raise ConnectorException("Invalid book data") + return self.get_book_data(self.get_remote_id(uri)) + + def get_authors_from_data(self, data): + authors = data.get("wdt:P50", []) + for author in authors: + yield self.get_or_create_author(self.get_remote_id(author)) + + def expand_book_data(self, book): + work = book + # go from the edition to the work, if necessary + if isinstance(book, models.Edition): + work = book.parent_work + + try: + edition_options = self.load_edition_data(work.inventaire_id) + except ConnectorException: + # who knows, man + return + + for edition_uri in edition_options.get("uris"): + remote_id = self.get_remote_id(edition_uri) + try: + data = self.get_book_data(remote_id) + except ConnectorException: + # who, indeed, knows + continue + self.create_edition_from_data(work, data) + + def get_cover_url(self, cover_blob, *_): + """format the relative cover url into an absolute one: + {"url": "/img/entities/e794783f01b9d4f897a1ea9820b96e00d346994f"} + """ + # covers may or may not be a list + if isinstance(cover_blob, list) and len(cover_blob) > 0: + cover_blob = cover_blob[0] + cover_id = cover_blob.get("url") + if not cover_id: + return None + # cover may or may not be an absolute url already + if re.match(r"^http", cover_id): + return cover_id + return "%s%s" % (self.covers_url, cover_id) + + def resolve_keys(self, keys): + """cool, it's "wd:Q3156592" now what the heck does that mean""" + results = [] + for uri in keys: + try: + data = self.get_book_data(self.get_remote_id(uri)) + except ConnectorException: + continue + results.append(get_language_code(data.get("labels"))) + return results + + def get_description(self, links): + """grab an extracted excerpt from wikipedia""" + link = links.get("enwiki") + if not link: + return "" + url = "{:s}/api/data?action=wp-extract&lang=en&title={:s}".format( + self.base_url, link + ) + try: + data = get_data(url) + except ConnectorException: + return "" + return data.get("extract") + + +def get_language_code(options, code="en"): + """when there are a bunch of translation but we need a single field""" + return options.get(code) diff --git a/bookwyrm/connectors/openlibrary.py b/bookwyrm/connectors/openlibrary.py index a7c30b66..69d498b8 100644 --- a/bookwyrm/connectors/openlibrary.py +++ b/bookwyrm/connectors/openlibrary.py @@ -14,8 +14,8 @@ class Connector(AbstractConnector): def __init__(self, identifier): super().__init__(identifier) - get_first = lambda a: a[0] - get_remote_id = lambda a: self.base_url + a + get_first = lambda a, *args: a[0] + get_remote_id = lambda a, *args: self.base_url + a self.book_mappings = [ Mapping("title"), Mapping("id", remote_field="key", formatter=get_remote_id), diff --git a/bookwyrm/connectors/self_connector.py b/bookwyrm/connectors/self_connector.py index 0dc922a5..a8f85834 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, F, Q +from django.db.models import Count, OuterRef, Subquery, F, Q from bookwyrm import models from .abstract_connector import AbstractConnector, SearchResult @@ -47,7 +47,16 @@ class Connector(AbstractConnector): # when there are multiple editions of the same work, pick the default. # it would be odd for this to happen. - results = results.filter(parent_work__default_edition__id=F("id")) or results + + default_editions = models.Edition.objects.filter( + parent_work=OuterRef("parent_work") + ).order_by("-edition_rank") + results = ( + results.annotate( + default_id=Subquery(default_editions.values("id")[:1]) + ).filter(default_id=F("id")) + or results + ) search_results = [] for result in results: @@ -60,6 +69,10 @@ class Connector(AbstractConnector): return search_results def format_search_result(self, search_result): + cover = None + if search_result.cover: + cover = "%s%s" % (self.covers_url, search_result.cover) + return SearchResult( title=search_result.title, key=search_result.remote_id, @@ -68,7 +81,7 @@ class Connector(AbstractConnector): if search_result.published_date else None, connector=self, - cover="%s%s" % (self.covers_url, search_result.cover), + cover=cover, confidence=search_result.rank if hasattr(search_result, "rank") else 1, ) @@ -112,7 +125,15 @@ def search_identifiers(query, *filters): # when there are multiple editions of the same work, pick the default. # it would be odd for this to happen. - return results.filter(parent_work__default_edition__id=F("id")) or results + default_editions = models.Edition.objects.filter( + parent_work=OuterRef("parent_work") + ).order_by("-edition_rank") + return ( + results.annotate(default_id=Subquery(default_editions.values("id")[:1])).filter( + default_id=F("id") + ) + or results + ) def search_title_author(query, min_confidence, *filters): @@ -140,10 +161,10 @@ def search_title_author(query, min_confidence, *filters): for work_id in set(editions_of_work): editions = results.filter(parent_work=work_id) - default = editions.filter(parent_work__default_edition=F("id")) - default_rank = default.first().rank if default.exists() else 0 + default = editions.order_by("-edition_rank").first() + default_rank = default.rank if default else 0 # if mutliple books have the top rank, pick the default edition if default_rank == editions.first().rank: - yield default.first() + yield default else: yield editions.first() diff --git a/bookwyrm/connectors/settings.py b/bookwyrm/connectors/settings.py index f1674cf7..4cc98da7 100644 --- a/bookwyrm/connectors/settings.py +++ b/bookwyrm/connectors/settings.py @@ -1,3 +1,3 @@ """ settings book data connectors """ -CONNECTORS = ["openlibrary", "self_connector", "bookwyrm_connector"] +CONNECTORS = ["openlibrary", "inventaire", "self_connector", "bookwyrm_connector"] diff --git a/bookwyrm/management/commands/initdb.py b/bookwyrm/management/commands/initdb.py index 0c0cc61f..9033249d 100644 --- a/bookwyrm/management/commands/initdb.py +++ b/bookwyrm/management/commands/initdb.py @@ -94,6 +94,18 @@ def init_connectors(): priority=2, ) + Connector.objects.create( + identifier="inventaire.io", + name="Inventaire", + connector_file="inventaire", + base_url="https://inventaire.io", + books_url="https://inventaire.io/api/entities", + covers_url="https://inventaire.io", + search_url="https://inventaire.io/api/search?types=works&types=works&search=", + isbn_search_url="https://inventaire.io/api/entities?action=by-uris&uris=isbn%3A", + priority=3, + ) + Connector.objects.create( identifier="openlibrary.org", name="OpenLibrary", diff --git a/bookwyrm/migrations/0062_auto_20210406_1731.py b/bookwyrm/migrations/0062_auto_20210406_1731.py new file mode 100644 index 00000000..5db176ec --- /dev/null +++ b/bookwyrm/migrations/0062_auto_20210406_1731.py @@ -0,0 +1,30 @@ +# Generated by Django 3.1.6 on 2021-04-06 17:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0061_auto_20210402_1435"), + ] + + operations = [ + migrations.RemoveConstraint( + model_name="connector", + name="connector_file_valid", + ), + migrations.AlterField( + model_name="connector", + name="connector_file", + field=models.CharField( + choices=[ + ("openlibrary", "Openlibrary"), + ("inventaire", "Inventaire"), + ("self_connector", "Self Connector"), + ("bookwyrm_connector", "Bookwyrm Connector"), + ], + max_length=255, + ), + ), + ] diff --git a/bookwyrm/migrations/0063_auto_20210407_0045.py b/bookwyrm/migrations/0063_auto_20210407_0045.py new file mode 100644 index 00000000..cd87dd97 --- /dev/null +++ b/bookwyrm/migrations/0063_auto_20210407_0045.py @@ -0,0 +1,63 @@ +# Generated by Django 3.1.6 on 2021-04-07 00:45 + +import bookwyrm.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0062_auto_20210406_1731"), + ] + + operations = [ + migrations.AddField( + model_name="author", + name="bnf_id", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), + ), + migrations.AddField( + model_name="author", + name="gutenberg_id", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), + ), + migrations.AddField( + model_name="author", + name="inventaire_id", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), + ), + migrations.AddField( + model_name="author", + name="isni", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), + ), + migrations.AddField( + model_name="author", + name="viaf_id", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), + ), + migrations.AddField( + model_name="book", + name="bnf_id", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), + ), + migrations.AddField( + model_name="book", + name="inventaire_id", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), + ), + ] diff --git a/bookwyrm/migrations/0071_merge_0063_auto_20210407_0045_0070_auto_20210423_0121.py b/bookwyrm/migrations/0071_merge_0063_auto_20210407_0045_0070_auto_20210423_0121.py new file mode 100644 index 00000000..b6489b80 --- /dev/null +++ b/bookwyrm/migrations/0071_merge_0063_auto_20210407_0045_0070_auto_20210423_0121.py @@ -0,0 +1,13 @@ +# Generated by Django 3.2 on 2021-04-26 21:32 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0063_auto_20210407_0045"), + ("bookwyrm", "0070_auto_20210423_0121"), + ] + + operations = [] diff --git a/bookwyrm/migrations/0072_remove_work_default_edition.py b/bookwyrm/migrations/0072_remove_work_default_edition.py new file mode 100644 index 00000000..1c05c95e --- /dev/null +++ b/bookwyrm/migrations/0072_remove_work_default_edition.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2 on 2021-04-28 22:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0071_merge_0063_auto_20210407_0045_0070_auto_20210423_0121"), + ] + + operations = [ + migrations.RemoveField( + model_name="work", + name="default_edition", + ), + ] diff --git a/bookwyrm/models/author.py b/bookwyrm/models/author.py index b9a4b146..c4e26c5a 100644 --- a/bookwyrm/models/author.py +++ b/bookwyrm/models/author.py @@ -14,6 +14,15 @@ class Author(BookDataModel): wikipedia_link = fields.CharField( max_length=255, blank=True, null=True, deduplication_field=True ) + isni = fields.CharField( + max_length=255, blank=True, null=True, deduplication_field=True + ) + viaf_id = fields.CharField( + max_length=255, blank=True, null=True, deduplication_field=True + ) + gutenberg_id = fields.CharField( + max_length=255, blank=True, null=True, deduplication_field=True + ) # idk probably other keys would be useful here? born = fields.DateTimeField(blank=True, null=True) died = fields.DateTimeField(blank=True, null=True) diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index dd098e56..869ff04d 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -1,11 +1,11 @@ """ database schema for books and shelves """ import re -from django.db import models, transaction +from django.db import models from model_utils.managers import InheritanceManager from bookwyrm import activitypub -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import DOMAIN, DEFAULT_LANGUAGE from .activitypub_mixin import OrderedCollectionPageMixin, ObjectMixin from .base_model import BookWyrmModel @@ -19,12 +19,18 @@ class BookDataModel(ObjectMixin, BookWyrmModel): openlibrary_key = fields.CharField( max_length=255, blank=True, null=True, deduplication_field=True ) + inventaire_id = fields.CharField( + max_length=255, blank=True, null=True, deduplication_field=True + ) librarything_key = fields.CharField( max_length=255, blank=True, null=True, deduplication_field=True ) goodreads_key = fields.CharField( max_length=255, blank=True, null=True, deduplication_field=True ) + bnf_id = fields.CharField( # Bibliothèque nationale de France + max_length=255, blank=True, null=True, deduplication_field=True + ) last_edited_by = fields.ForeignKey( "User", @@ -137,10 +143,6 @@ class Work(OrderedCollectionPageMixin, Book): lccn = fields.CharField( max_length=255, blank=True, null=True, deduplication_field=True ) - # this has to be nullable but should never be null - default_edition = fields.ForeignKey( - "Edition", on_delete=models.PROTECT, null=True, load_remote=False - ) def save(self, *args, **kwargs): """set some fields on the edition object""" @@ -149,18 +151,10 @@ class Work(OrderedCollectionPageMixin, Book): edition.save() return super().save(*args, **kwargs) - def get_default_edition(self): + @property + def default_edition(self): """in case the default edition is not set""" - return self.default_edition or self.editions.order_by("-edition_rank").first() - - @transaction.atomic() - def reset_default_edition(self): - """sets a new default edition based on computed rank""" - self.default_edition = None - # editions are re-ranked implicitly - self.save() - self.default_edition = self.get_default_edition() - self.save() + return self.editions.order_by("-edition_rank").first() def to_edition_list(self, **kwargs): """an ordered collection of editions""" @@ -214,17 +208,20 @@ class Edition(Book): activity_serializer = activitypub.Edition name_field = "title" - def get_rank(self, ignore_default=False): + def get_rank(self): """calculate how complete the data is on this edition""" - if ( - not ignore_default - and self.parent_work - and self.parent_work.default_edition == self - ): - # default edition has the highest rank - return 20 rank = 0 + # big ups for havinga cover rank += int(bool(self.cover)) * 3 + # is it in the instance's preferred language? + rank += int(bool(DEFAULT_LANGUAGE in self.languages)) + # prefer print editions + if self.physical_format: + rank += int( + bool(self.physical_format.lower() in ["paperback", "hardcover"]) + ) + + # does it have metadata? rank += int(bool(self.isbn_13)) rank += int(bool(self.isbn_10)) rank += int(bool(self.oclc_number)) @@ -242,6 +239,12 @@ class Edition(Book): if self.isbn_10 and not self.isbn_13: self.isbn_13 = isbn_10_to_13(self.isbn_10) + # normalize isbn format + if self.isbn_10: + self.isbn_10 = re.sub(r"[^0-9X]", "", self.isbn_10) + if self.isbn_13: + self.isbn_13 = re.sub(r"[^0-9X]", "", self.isbn_13) + # set rank self.edition_rank = self.get_rank() diff --git a/bookwyrm/models/connector.py b/bookwyrm/models/connector.py index 6043fc02..625cdbed 100644 --- a/bookwyrm/models/connector.py +++ b/bookwyrm/models/connector.py @@ -31,16 +31,6 @@ class Connector(BookWyrmModel): # when to reset the query count back to 0 (ie, after 1 day) query_count_expiry = models.DateTimeField(auto_now_add=True, blank=True) - class Meta: - """check that there's code to actually use this connector""" - - constraints = [ - models.CheckConstraint( - check=models.Q(connector_file__in=ConnectorFiles), - name="connector_file_valid", - ) - ] - def __str__(self): return "{} ({})".format( self.identifier, diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index b679e2d4..1bc3c587 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -11,6 +11,7 @@ DOMAIN = env("DOMAIN") VERSION = "0.0.1" PAGE_LENGTH = env("PAGE_LENGTH", 15) +DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English") # celery CELERY_BROKER = env("CELERY_BROKER") diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index a9932910..98006184 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -80,6 +80,9 @@ {% if book.openlibrary_key %}

{% trans "View on OpenLibrary" %}

{% endif %} + {% if book.inventaire_id %} +

{% trans "View on Inventaire" %}

+ {% endif %} diff --git a/bookwyrm/templates/search_results.html b/bookwyrm/templates/search_results.html index cb1fae39..fdb77f72 100644 --- a/bookwyrm/templates/search_results.html +++ b/bookwyrm/templates/search_results.html @@ -11,10 +11,15 @@
-

{% trans "Matching Books" %}

+

{% trans "Matching Books" %}

{% if not local_results.results %} -

{% blocktrans %}No books found for "{{ query }}"{% endblocktrans %}

+

{% blocktrans %}No books found for "{{ query }}"{% endblocktrans %}

+ {% if not user.is_authenticated %} +

+ {% trans "Log in to import or add books." %} +

+ {% endif %} {% else %}
    {% for result in local_results.results %} @@ -29,39 +34,57 @@ {% if request.user.is_authenticated %} {% if book_results|slice:":1" and local_results.results %}
    -

    +

    {% trans "Didn't find what you were looking for?" %} -

    +

    {% trans "Show results from other catalogues" as button_text %} {% include 'snippets/toggle/open_button.html' with text=button_text small=True controls_text="more-results" %} + + {% if local_results.results %} + {% trans "Hide results from other catalogues" as button_text %} + {% include 'snippets/toggle/close_button.html' with text=button_text small=True controls_text="more-results" %} + {% endif %}
    {% endif %}
    {% for result_set in book_results|slice:"1:" %} {% if result_set.results %} -
    +
    {% if not result_set.connector.local %} -

    - Results from {% if result_set.connector.name %}{{ result_set.connector.name }}{% else %}{{ result_set.connector.identifier }}{% endif %} -

    +
    + +
    + {% trans "Show" as button_text %} + {% include 'snippets/toggle/open_button.html' with text=button_text small=True controls_text="more-results-panel" controls_uid=result_set.connector.identifier class="is-small" icon="arrow-down" pressed=forloop.first %} +
    +
    {% endif %} -
      - {% for result in result_set.results %} -
    • - {% include 'snippets/search_result_text.html' with result=result remote_result=True %} -
    • - {% endfor %} -
    +
    +
    +
    + {% trans "Close" as button_text %} + {% include 'snippets/toggle/toggle_button.html' with label=button_text class="delete" nonbutton=True controls_text="more-results-panel" controls_uid=result_set.connector.identifier pressed=forloop.first %} +
    + +
      + {% for result in result_set.results %} +
    • + {% include 'snippets/search_result_text.html' with result=result remote_result=True %} +
    • + {% endfor %} +
    +
    +
    {% endif %} {% endfor %} - - {% if local_results.results %} - {% trans "Hide results from other catalogues" as button_text %} - {% include 'snippets/toggle/close_button.html' with text=button_text small=True controls_text="more-results" %} - {% endif %}
    @@ -70,10 +93,11 @@ {% endif %}
    -
    -

    {% trans "Matching Users" %}

    + {% if request.user.is_authenticated %} +
    +

    {% trans "Matching Users" %}

    {% if not user_results %} -

    {% blocktrans %}No users found for "{{ query }}"{% endblocktrans %}

    +

    {% blocktrans %}No users found for "{{ query }}"{% endblocktrans %}

    {% endif %}
      {% for result in user_results %} @@ -87,10 +111,11 @@ {% endfor %}
    -
    -

    {% trans "Lists" %}

    + {% endif %} +
    +

    {% trans "Lists" %}

    {% if not list_results %} -

    {% blocktrans %}No lists found for "{{ query }}"{% endblocktrans %}

    +

    {% blocktrans %}No lists found for "{{ query }}"{% endblocktrans %}

    {% endif %} {% for result in list_results %}
    diff --git a/bookwyrm/templates/snippets/search_result_text.html b/bookwyrm/templates/snippets/search_result_text.html index 663ef865..e39d3410 100644 --- a/bookwyrm/templates/snippets/search_result_text.html +++ b/bookwyrm/templates/snippets/search_result_text.html @@ -8,17 +8,17 @@

    {{ result.title }} - +

    +

    {% if result.author %} - {% blocktrans with author=result.author %}by {{ author }}{% endblocktrans %} + {{ result.author }} {% endif %} {% if result.year %} diff --git a/bookwyrm/tests/connectors/test_connector_manager.py b/bookwyrm/tests/connectors/test_connector_manager.py index feded616..34abbeaf 100644 --- a/bookwyrm/tests/connectors/test_connector_manager.py +++ b/bookwyrm/tests/connectors/test_connector_manager.py @@ -17,8 +17,6 @@ class ConnectorManager(TestCase): self.edition = models.Edition.objects.create( title="Example Edition", parent_work=self.work, isbn_10="0000000000" ) - self.work.default_edition = self.edition - self.work.save() self.connector = models.Connector.objects.create( identifier="test_connector", diff --git a/bookwyrm/tests/connectors/test_inventaire_connector.py b/bookwyrm/tests/connectors/test_inventaire_connector.py new file mode 100644 index 00000000..4058b067 --- /dev/null +++ b/bookwyrm/tests/connectors/test_inventaire_connector.py @@ -0,0 +1,158 @@ +""" testing book data connectors """ +import json +import pathlib +from django.test import TestCase +import responses + +from bookwyrm import models +from bookwyrm.connectors.inventaire import Connector + + +class Inventaire(TestCase): + """test loading data from inventaire.io""" + + def setUp(self): + """creates the connector we'll use""" + models.Connector.objects.create( + identifier="inventaire.io", + name="Inventaire", + connector_file="inventaire", + base_url="https://inventaire.io", + books_url="https://inventaire.io", + covers_url="https://covers.inventaire.io", + search_url="https://inventaire.io/search?q=", + isbn_search_url="https://inventaire.io/isbn", + ) + self.connector = Connector("inventaire.io") + + @responses.activate + def test_get_book_data(self): + """flattens the default structure to make it easier to parse""" + responses.add( + responses.GET, + "https://test.url/ok", + json={ + "entities": { + "isbn:9780375757853": { + "claims": { + "wdt:P31": ["wd:Q3331189"], + }, + "uri": "isbn:9780375757853", + } + }, + "redirects": {}, + }, + ) + + result = self.connector.get_book_data("https://test.url/ok") + self.assertEqual(result["wdt:P31"], ["wd:Q3331189"]) + self.assertEqual(result["uri"], "isbn:9780375757853") + + def test_format_search_result(self): + """json to search result objs""" + search_file = pathlib.Path(__file__).parent.joinpath( + "../data/inventaire_search.json" + ) + search_results = json.loads(search_file.read_bytes()) + + results = self.connector.parse_search_data(search_results) + formatted = self.connector.format_search_result(results[0]) + + self.assertEqual(formatted.title, "The Stories of Vladimir Nabokov") + self.assertEqual( + formatted.key, "https://inventaire.io?action=by-uris&uris=wd:Q7766679" + ) + self.assertEqual( + formatted.cover, + "https://covers.inventaire.io/img/entities/ddb32e115a28dcc0465023869ba19f6868ec4042", + ) + + def test_get_cover_url(self): + """figure out where the cover image is""" + cover_blob = {"url": "/img/entities/d46a8"} + result = self.connector.get_cover_url(cover_blob) + self.assertEqual(result, "https://covers.inventaire.io/img/entities/d46a8") + + cover_blob = { + "url": "https://commons.wikimedia.org/wiki/Special:FilePath/The%20Moonstone%201st%20ed.jpg?width=1000", + "file": "The Moonstone 1st ed.jpg", + "credits": { + "text": "Wikimedia Commons", + "url": "https://commons.wikimedia.org/wiki/File:The Moonstone 1st ed.jpg", + }, + } + + result = self.connector.get_cover_url(cover_blob) + self.assertEqual( + result, + "https://commons.wikimedia.org/wiki/Special:FilePath/The%20Moonstone%201st%20ed.jpg?width=1000", + ) + + @responses.activate + def test_resolve_keys(self): + """makes an http request""" + responses.add( + responses.GET, + "https://inventaire.io?action=by-uris&uris=wd:Q465821", + json={ + "entities": { + "wd:Q465821": { + "type": "genre", + "labels": { + "nl": "briefroman", + "en": "epistolary novel", + "de-ch": "Briefroman", + "en-ca": "Epistolary novel", + "nb": "brev- og dagbokroman", + }, + "descriptions": { + "en": "novel written as a series of documents", + "es": "novela escrita como una serie de documentos", + "eo": "romano en la formo de serio de leteroj", + }, + }, + "redirects": {}, + } + }, + ) + responses.add( + responses.GET, + "https://inventaire.io?action=by-uris&uris=wd:Q208505", + json={ + "entities": { + "wd:Q208505": { + "type": "genre", + "labels": { + "en": "crime novel", + }, + }, + } + }, + ) + + keys = [ + "wd:Q465821", + "wd:Q208505", + ] + result = self.connector.resolve_keys(keys) + self.assertEqual(result, ["epistolary novel", "crime novel"]) + + def test_isbn_search(self): + """another search type""" + search_file = pathlib.Path(__file__).parent.joinpath( + "../data/inventaire_isbn_search.json" + ) + search_results = json.loads(search_file.read_bytes()) + + results = self.connector.parse_isbn_search_data(search_results) + formatted = self.connector.format_isbn_search_result(results[0]) + + self.assertEqual(formatted.title, "L'homme aux cercles bleus") + self.assertEqual( + formatted.key, + "https://inventaire.io?action=by-uris&uris=isbn:9782290349229", + ) + self.assertEqual( + formatted.cover, + "https://covers.inventaire.io/img/entities/12345", + ) diff --git a/bookwyrm/tests/connectors/test_self_connector.py b/bookwyrm/tests/connectors/test_self_connector.py index eee7d00c..db97b65a 100644 --- a/bookwyrm/tests/connectors/test_self_connector.py +++ b/bookwyrm/tests/connectors/test_self_connector.py @@ -84,11 +84,11 @@ class SelfConnector(TestCase): title="Edition 1 Title", parent_work=work ) edition_2 = models.Edition.objects.create( - title="Edition 2 Title", parent_work=work + 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) - work.default_edition = edition_2 - work.save() # pick the best edition results = self.connector.search("Edition 1 Title") diff --git a/bookwyrm/tests/data/inventaire_edition.json b/bookwyrm/tests/data/inventaire_edition.json new file mode 100644 index 00000000..1150bc9b --- /dev/null +++ b/bookwyrm/tests/data/inventaire_edition.json @@ -0,0 +1,45 @@ +{ + "entities": { + "isbn:9780375757853": { + "_id": "7beee121a8d9ac345cdf4e9128577723", + "_rev": "2-ac318b04b953ca3894deb77fee28211c", + "type": "edition", + "labels": {}, + "claims": { + "wdt:P31": [ + "wd:Q3331189" + ], + "wdt:P212": [ + "978-0-375-75785-3" + ], + "wdt:P957": [ + "0-375-75785-6" + ], + "wdt:P407": [ + "wd:Q1860" + ], + "wdt:P1476": [ + "The Moonstone" + ], + "wdt:P577": [ + "2001" + ], + "wdt:P629": [ + "wd:Q2362563" + ], + "invp:P2": [ + "d46a8eac7555afa479b8bbb5149f35858e8e19c4" + ] + }, + "created": 1495452670475, + "updated": 1541032981834, + "version": 3, + "uri": "isbn:9780375757853", + "originalLang": "en", + "image": { + "url": "/img/entities/d46a8eac7555afa479b8bbb5149f35858e8e19c4" + } + } + }, + "redirects": {} +} diff --git a/bookwyrm/tests/data/inventaire_isbn_search.json b/bookwyrm/tests/data/inventaire_isbn_search.json new file mode 100644 index 00000000..7328a78f --- /dev/null +++ b/bookwyrm/tests/data/inventaire_isbn_search.json @@ -0,0 +1,48 @@ +{ + "entities": { + "isbn:9782290349229": { + "_id": "d59e3e64f92c6340fbb10c5dcf7c0abf", + "_rev": "3-079ed51158a001dc74caafb21cff1c22", + "type": "edition", + "labels": {}, + "claims": { + "wdt:P31": [ + "wd:Q3331189" + ], + "wdt:P212": [ + "978-2-290-34922-9" + ], + "wdt:P957": [ + "2-290-34922-4" + ], + "wdt:P407": [ + "wd:Q150" + ], + "wdt:P1476": [ + "L'homme aux cercles bleus" + ], + "wdt:P629": [ + "wd:Q3203603" + ], + "wdt:P123": [ + "wd:Q3156592" + ], + "invp:P2": [ + "57883743aa7c6ad25885a63e6e94349ec4f71562" + ], + "wdt:P577": [ + "2005-05-01" + ] + }, + "created": 1485023383338, + "updated": 1609171008418, + "version": 5, + "uri": "isbn:9782290349229", + "originalLang": "fr", + "image": { + "url": "/img/entities/12345" + } + } + }, + "redirects": {} +} diff --git a/bookwyrm/tests/data/inventaire_search.json b/bookwyrm/tests/data/inventaire_search.json new file mode 100644 index 00000000..e80e593a --- /dev/null +++ b/bookwyrm/tests/data/inventaire_search.json @@ -0,0 +1,111 @@ +{ + "results": [ + { + "id": "Q7766679", + "type": "works", + "uri": "wd:Q7766679", + "label": "The Stories of Vladimir Nabokov", + "description": "book by Vladimir Nabokov", + "image": [ + "ddb32e115a28dcc0465023869ba19f6868ec4042" + ], + "_score": 25.180836, + "_popularity": 4 + }, + { + "id": "Q47407212", + "type": "works", + "uri": "wd:Q47407212", + "label": "Conversations with Vladimir Nabokov", + "description": "book edited by Robert Golla", + "image": [], + "_score": 24.41498, + "_popularity": 2 + }, + { + "id": "Q6956987", + "type": "works", + "uri": "wd:Q6956987", + "label": "Nabokov's Congeries", + "description": "book by Vladimir Nabokov", + "image": [], + "_score": 22.343866, + "_popularity": 2 + }, + { + "id": "Q6956986", + "type": "works", + "uri": "wd:Q6956986", + "label": "Nabokov's Butterflies", + "description": "book by Brian Boyd", + "image": [], + "_score": 22.343866, + "_popularity": 2 + }, + { + "id": "Q47472170", + "type": "works", + "uri": "wd:Q47472170", + "label": "A Reader's Guide to Nabokov's \"Lolita\"", + "description": "book by Julian W. Connolly", + "image": [], + "_score": 19.482553, + "_popularity": 2 + }, + { + "id": "Q7936323", + "type": "works", + "uri": "wd:Q7936323", + "label": "Visiting Mrs Nabokov: And Other Excursions", + "description": "book by Martin Amis", + "image": [], + "_score": 18.684965, + "_popularity": 2 + }, + { + "id": "1732d81bf7376e04da27568a778561a4", + "type": "works", + "uri": "inv:1732d81bf7376e04da27568a778561a4", + "label": "Nabokov's Dark Cinema", + "image": [ + "7512805a53da569b11bf29cc3fb272c969619749" + ], + "_score": 16.56681, + "_popularity": 1 + }, + { + "id": "00f118336b02219e1bddc8fa93c56050", + "type": "works", + "uri": "inv:00f118336b02219e1bddc8fa93c56050", + "label": "The Cambridge Companion to Nabokov", + "image": [ + "0683a059fb95430cfa73334f9eff2ef377f3ae3d" + ], + "_score": 15.502292, + "_popularity": 1 + }, + { + "id": "6e59f968a1cd00dbedeb1964dec47507", + "type": "works", + "uri": "inv:6e59f968a1cd00dbedeb1964dec47507", + "label": "Vladimir Nabokov : selected letters, 1940-1977", + "image": [ + "e3ce8c0ee89d576adf2651a6e5ce55fc6d9f8cb3" + ], + "_score": 15.019735, + "_popularity": 1 + }, + { + "id": "Q127149", + "type": "works", + "uri": "wd:Q127149", + "label": "Lolita", + "description": "novel by Vladimir Nabokov", + "image": [ + "51cbfdbf7257b1a6bb3ea3fbb167dbce1fb44a0e" + ], + "_score": 13.458428, + "_popularity": 32 + } + ] +} diff --git a/bookwyrm/tests/data/inventaire_work.json b/bookwyrm/tests/data/inventaire_work.json new file mode 100644 index 00000000..635c52f3 --- /dev/null +++ b/bookwyrm/tests/data/inventaire_work.json @@ -0,0 +1,155 @@ +{ + "entities": { + "wd:Q2362563": { + "type": "work", + "labels": { + "zh-hans": "月亮宝石", + "zh-hant": "月亮寶石", + "zh-hk": "月光石", + "zh-tw": "月光石", + "cy": "The Moonstone", + "ml": "ദ മൂൺസ്റ്റോൺ", + "ja": "月長石", + "te": "ది మూన్ స్టోన్", + "ru": "Лунный камень", + "fr": "La Pierre de lune", + "en": "The Moonstone", + "es": "La piedra lunar", + "it": "La Pietra di Luna", + "zh": "月亮宝石", + "pl": "Kamień Księżycowy", + "sr": "2 Јн", + "ta": "moon stone", + "ar": "حجر القمر", + "fa": "ماه‌الماس", + "uk": "Місячний камінь", + "nl": "The Moonstone", + "de": "Der Monddiamant", + "sl": "Diamant", + "sv": "Månstenen", + "he": "אבן הירח", + "eu": "Ilargi-harriak", + "bg": "Лунният камък", + "ka": "მთვარის ქვა", + "eo": "La Lunŝtono", + "hy": "Լուսնաքար", + "ro": "Piatra Lunii", + "ca": "The Moonstone", + "is": "The Moonstone" + }, + "descriptions": { + "it": "romanzo scritto da Wilkie Collins", + "en": "novel by Wilkie Collins", + "de": "Buch von Wilkie Collins", + "nl": "boek van Wilkie Collins", + "ru": "роман Уилки Коллинза", + "he": "רומן מאת וילקי קולינס", + "ar": "رواية من تأليف ويلكي كولينز", + "fr": "livre de Wilkie Collins", + "es": "libro de Wilkie Collins", + "bg": "роман на Уилки Колинс", + "ka": "უილკი კოლინსის რომანი", + "eo": "angalingva romano far Wilkie Collins", + "ro": "roman de Wilkie Collins" + }, + "aliases": { + "zh": [ + "月光石" + ], + "ml": [ + "The Moonstone" + ], + "fr": [ + "The Moonstone" + ], + "it": [ + "Il diamante indiano", + "La pietra della luna", + "La maledizione del diamante indiano" + ], + "ro": [ + "The Moonstone" + ] + }, + "claims": { + "wdt:P18": [ + "The Moonstone 1st ed.jpg" + ], + "wdt:P31": [ + "wd:Q7725634" + ], + "wdt:P50": [ + "wd:Q210740" + ], + "wdt:P123": [ + "wd:Q4457856" + ], + "wdt:P136": [ + "wd:Q465821", + "wd:Q208505", + "wd:Q10992055" + ], + "wdt:P156": [ + "wd:Q7228798" + ], + "wdt:P268": [ + "12496407z" + ], + "wdt:P407": [ + "wd:Q7979" + ], + "wdt:P577": [ + "1868" + ], + "wdt:P1433": [ + "wd:Q21" + ], + "wdt:P1476": [ + "The Moonstone" + ], + "wdt:P1680": [ + "A Romance" + ], + "wdt:P2034": [ + "155" + ] + }, + "sitelinks": { + "arwiki": "حجر القمر (رواية)", + "bgwiki": "Лунният камък (роман)", + "cywiki": "The Moonstone", + "dewiki": "Der Monddiamant", + "enwiki": "The Moonstone", + "enwikisource": "The Moonstone", + "eswiki": "La piedra lunar", + "euwiki": "Ilargi-harria", + "fawiki": "ماه‌الماس", + "frwiki": "La Pierre de lune (roman de Wilkie Collins)", + "hewiki": "אבן הירח", + "hywiki": "Լուսնաքար", + "iswiki": "The Moonstone", + "itwiki": "La pietra di Luna", + "jawiki": "月長石 (小説)", + "mlwiki": "ദ മൂൺസ്റ്റോൺ", + "plwiki": "Kamień Księżycowy (powieść)", + "ruwiki": "Лунный камень (роман)", + "slwiki": "Diamant (roman)", + "srwikisource": "Нови завјет (Караџић) / 2. Јованова", + "svwiki": "Månstenen", + "tewiki": "ది మూన్‌స్టోన్", + "ukwiki": "Місячний камінь (роман)", + "zhwiki": "月亮宝石" + }, + "uri": "wd:Q2362563", + "image": { + "url": "https://commons.wikimedia.org/wiki/Special:FilePath/The%20Moonstone%201st%20ed.jpg?width=1000", + "file": "The Moonstone 1st ed.jpg", + "credits": { + "text": "Wikimedia Commons", + "url": "https://commons.wikimedia.org/wiki/File:The Moonstone 1st ed.jpg" + } + } + } + }, + "redirects": {} +} diff --git a/bookwyrm/tests/models/test_book_model.py b/bookwyrm/tests/models/test_book_model.py index c80cc4a8..cad00d43 100644 --- a/bookwyrm/tests/models/test_book_model.py +++ b/bookwyrm/tests/models/test_book_model.py @@ -84,9 +84,3 @@ class Book(TestCase): self.first_edition.description = "hi" self.first_edition.save() self.assertEqual(self.first_edition.edition_rank, 1) - - # default edition - self.work.default_edition = self.first_edition - self.work.save() - self.first_edition.refresh_from_db() - self.assertEqual(self.first_edition.edition_rank, 20) diff --git a/bookwyrm/tests/models/test_readthrough_model.py b/bookwyrm/tests/models/test_readthrough_model.py index 93e9e654..986b739b 100644 --- a/bookwyrm/tests/models/test_readthrough_model.py +++ b/bookwyrm/tests/models/test_readthrough_model.py @@ -2,7 +2,7 @@ from django.test import TestCase from django.core.exceptions import ValidationError -from bookwyrm import models, settings +from bookwyrm import models class ReadThrough(TestCase): @@ -19,8 +19,6 @@ class ReadThrough(TestCase): self.edition = models.Edition.objects.create( title="Example Edition", parent_work=self.work ) - self.work.default_edition = self.edition - self.work.save() self.readthrough = models.ReadThrough.objects.create( user=self.user, book=self.edition diff --git a/bookwyrm/tests/views/inbox/test_inbox_create.py b/bookwyrm/tests/views/inbox/test_inbox_create.py index e7a12024..958dfee8 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_create.py +++ b/bookwyrm/tests/views/inbox/test_inbox_create.py @@ -127,6 +127,43 @@ class InboxCreate(TestCase): self.assertTrue(models.Notification.objects.filter(user=self.local_user)) self.assertEqual(models.Notification.objects.get().notification_type, "REPLY") + def test_create_rating(self): + """a remote rating activity""" + 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", + "type": "Rating", + "published": "2021-04-29T21:27:30.014235+00:00", + "attributedTo": "https://example.com/user/mouse", + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "cc": ["https://example.com/user/mouse/followers"], + "replies": { + "id": "https://example.com/user/mouse/reviewrating/12/replies", + "type": "OrderedCollection", + "totalItems": 0, + "first": "https://example.com/user/mouse/reviewrating/12/replies?page=1", + "last": "https://example.com/user/mouse/reviewrating/12/replies?page=1", + "@context": "https://www.w3.org/ns/activitystreams", + }, + "inReplyTo": "", + "summary": "", + "tag": [], + "attachment": [], + "sensitive": False, + "inReplyToBook": "https://example.com/book/1", + "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) + rating = models.ReviewRating.objects.first() + self.assertEqual(rating.book, book) + self.assertEqual(rating.rating, 3.0) + def test_create_list(self): """a new list""" activity = self.create_json diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index e2e041e9..0dddd2a1 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -219,7 +219,7 @@ class ViewsHelpers(TestCase): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): # 1 shared follow self.local_user.following.add(user_2) - user_1.following.add(user_2) + user_1.followers.add(user_2) # 1 shared book models.ShelfBook.objects.create( @@ -264,7 +264,7 @@ class ViewsHelpers(TestCase): local=True, localname=i, ) - user.followers.add(user_1) + user.following.add(user_1) user.followers.add(self.local_user) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): diff --git a/bookwyrm/tests/views/test_readthrough.py b/bookwyrm/tests/views/test_readthrough.py index c9ebf216..882c7929 100644 --- a/bookwyrm/tests/views/test_readthrough.py +++ b/bookwyrm/tests/views/test_readthrough.py @@ -20,8 +20,6 @@ class ReadThrough(TestCase): self.edition = models.Edition.objects.create( title="Example Edition", parent_work=self.work ) - self.work.default_edition = self.edition - self.work.save() self.user = models.User.objects.create_user( "cinco", "cinco@example.com", "seissiete", local=True, localname="cinco" diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 53ceeaa8..24c80b04 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -43,7 +43,7 @@ urlpatterns = [ re_path("^api/updates/notifications/?$", views.get_notification_count), re_path("^api/updates/stream/(?P[a-z]+)/?$", views.get_unread_status_count), # authentication - re_path(r"^login/?$", views.Login.as_view()), + re_path(r"^login/?$", views.Login.as_view(), name="login"), re_path(r"^register/?$", views.Register.as_view()), re_path(r"^logout/?$", views.Logout.as_view()), re_path(r"^password-reset/?$", views.PasswordResetRequest.as_view()), diff --git a/bookwyrm/views/author.py b/bookwyrm/views/author.py index 0bd7b0e0..41298161 100644 --- a/bookwyrm/views/author.py +++ b/bookwyrm/views/author.py @@ -27,7 +27,7 @@ class Author(View): ).distinct() data = { "author": author, - "books": [b.get_default_edition() for b in books], + "books": [b.default_edition for b in books], } return TemplateResponse(request, "author.html", data) diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index 448cf992..6005c9fd 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -39,7 +39,7 @@ class Book(View): return ActivitypubResponse(book.to_activity()) if isinstance(book, models.Work): - book = book.get_default_edition() + book = book.default_edition if not book or not book.parent_work: return HttpResponseNotFound() @@ -156,7 +156,6 @@ class EditBook(View): ), } ) - print(data["author_matches"]) # we're creating a new book if not book: diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 8a60b54c..5a8617fc 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -123,7 +123,7 @@ def get_edition(book_id): """look up a book in the db and return an edition""" book = models.Book.objects.select_subclasses().get(id=book_id) if isinstance(book, models.Work): - book = book.get_default_edition() + book = book.default_edition return book @@ -190,11 +190,11 @@ def get_annotated_users(user, *args, **kwargs): .exclude(Q(id__in=user.blocks.all()) | Q(blocks=user)) .annotate( mutuals=Count( - "following", + "followers", filter=Q( ~Q(id=user.id), ~Q(id__in=user.following.all()), - following__in=user.following.all(), + followers__in=user.following.all(), ), distinct=True, ), diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index 4543b55e..bd5ac3c7 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -30,27 +30,30 @@ class Search(View): ) return JsonResponse([r.json() for r in book_results], safe=False) + data = {"query": query or ""} + # use webfinger for mastodon style account@domain.com username if query and re.match(regex.full_username, query): handle_remote_webfinger(query) # do a user search - user_results = ( - models.User.viewer_aware_objects(request.user) - .annotate( - similarity=Greatest( - TrigramSimilarity("username", query), - TrigramSimilarity("localname", query), + if request.user.is_authenticated: + data["user_results"] = ( + models.User.viewer_aware_objects(request.user) + .annotate( + similarity=Greatest( + TrigramSimilarity("username", query), + TrigramSimilarity("localname", query), + ) ) + .filter( + similarity__gt=0.5, + ) + .order_by("-similarity")[:10] ) - .filter( - similarity__gt=0.5, - ) - .order_by("-similarity")[:10] - ) # any relevent lists? - list_results = ( + data["list_results"] = ( privacy_filter( request.user, models.List.objects, @@ -68,11 +71,7 @@ class Search(View): .order_by("-similarity")[:10] ) - book_results = connector_manager.search(query, min_confidence=min_confidence) - data = { - "book_results": book_results, - "user_results": user_results, - "list_results": list_results, - "query": query or "", - } + data["book_results"] = connector_manager.search( + query, min_confidence=min_confidence + ) return TemplateResponse(request, "search_results.html", data) diff --git a/locale/de_DE/LC_MESSAGES/django.mo b/locale/de_DE/LC_MESSAGES/django.mo index dca72685..3f41eda5 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 7acc3f96..d7ad8724 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-04-26 09:56-0700\n" +"POT-Creation-Date: 2021-04-29 13:24-0700\n" "PO-Revision-Date: 2021-03-02 17:19-0800\n" "Last-Translator: Mouse Reeve \n" "Language-Team: English \n" @@ -100,23 +100,23 @@ msgstr "Username" msgid "A user with that username already exists." msgstr "Dieser Benutzename ist bereits vergeben." -#: bookwyrm/settings.py:152 +#: bookwyrm/settings.py:155 msgid "English" msgstr "Englisch" -#: bookwyrm/settings.py:153 +#: bookwyrm/settings.py:156 msgid "German" msgstr "Deutsch" -#: bookwyrm/settings.py:154 +#: bookwyrm/settings.py:157 msgid "Spanish" msgstr "Spanisch" -#: bookwyrm/settings.py:155 +#: bookwyrm/settings.py:158 msgid "French" msgstr "Französisch" -#: bookwyrm/settings.py:156 +#: bookwyrm/settings.py:159 msgid "Simplified Chinese" msgstr "Vereinfachtes Chinesisch" @@ -178,24 +178,30 @@ msgstr "Laden fehlgeschlagen" msgid "View on OpenLibrary" msgstr "In OpenLibrary ansehen" -#: bookwyrm/templates/book/book.html:102 +#: bookwyrm/templates/book/book.html:85 +#, fuzzy +#| msgid "View on OpenLibrary" +msgid "View on Inventaire" +msgstr "In OpenLibrary ansehen" + +#: bookwyrm/templates/book/book.html:105 #, python-format msgid "(%(review_count)s review)" msgid_plural "(%(review_count)s reviews)" msgstr[0] "(%(review_count)s Bewertung)" msgstr[1] "(%(review_count)s Bewertungen)" -#: bookwyrm/templates/book/book.html:114 +#: bookwyrm/templates/book/book.html:117 msgid "Add Description" msgstr "Beschreibung hinzufügen" -#: bookwyrm/templates/book/book.html:121 +#: bookwyrm/templates/book/book.html:124 #: bookwyrm/templates/book/edit_book.html:107 #: bookwyrm/templates/lists/form.html:12 msgid "Description:" msgstr "Beschreibung:" -#: bookwyrm/templates/book/book.html:125 +#: bookwyrm/templates/book/book.html:128 #: bookwyrm/templates/book/edit_book.html:240 #: bookwyrm/templates/edit_author.html:78 bookwyrm/templates/lists/form.html:42 #: bookwyrm/templates/preferences/edit_user.html:70 @@ -210,7 +216,7 @@ msgstr "Beschreibung:" msgid "Save" msgstr "Speichern" -#: bookwyrm/templates/book/book.html:126 bookwyrm/templates/book/book.html:175 +#: bookwyrm/templates/book/book.html:129 bookwyrm/templates/book/book.html:178 #: bookwyrm/templates/book/cover_modal.html:32 #: bookwyrm/templates/book/edit_book.html:241 #: bookwyrm/templates/edit_author.html:79 @@ -226,19 +232,19 @@ msgstr "Speichern" msgid "Cancel" msgstr "Abbrechen" -#: bookwyrm/templates/book/book.html:135 +#: bookwyrm/templates/book/book.html:138 #, fuzzy, python-format #| msgid "%(title)s by " msgid "%(count)s editions" msgstr "%(title)s von" -#: bookwyrm/templates/book/book.html:143 +#: bookwyrm/templates/book/book.html:146 #, fuzzy, python-format #| msgid "Direct Messages with %(username)s" msgid "This edition is on your %(shelf_name)s shelf." msgstr "Direktnachrichten mit %(username)s" -#: bookwyrm/templates/book/book.html:149 +#: bookwyrm/templates/book/book.html:152 #, fuzzy, python-format #| msgid "" #| " added %(book_title)s to your list " @@ -250,74 +256,74 @@ msgstr "" "hat %(book_title)s zu deiner Liste " "\"%(list_name)s\" Hinzugefügt" -#: bookwyrm/templates/book/book.html:158 +#: bookwyrm/templates/book/book.html:161 msgid "Your reading activity" msgstr "Deine Leseaktivität" -#: bookwyrm/templates/book/book.html:160 +#: bookwyrm/templates/book/book.html:163 msgid "Add read dates" msgstr "Lesedaten hinzufügen" -#: bookwyrm/templates/book/book.html:165 +#: bookwyrm/templates/book/book.html:168 msgid "You don't have any reading activity for this book." msgstr "Du hast keine Leseaktivität für dieses Buch." -#: bookwyrm/templates/book/book.html:172 +#: bookwyrm/templates/book/book.html:175 msgid "Create" msgstr "Erstellen" -#: bookwyrm/templates/book/book.html:194 +#: bookwyrm/templates/book/book.html:197 msgid "Subjects" msgstr "Themen" -#: bookwyrm/templates/book/book.html:206 +#: bookwyrm/templates/book/book.html:209 msgid "Places" msgstr "Orte" -#: bookwyrm/templates/book/book.html:217 bookwyrm/templates/layout.html:65 +#: bookwyrm/templates/book/book.html:220 bookwyrm/templates/layout.html:65 #: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 -#: bookwyrm/templates/search_results.html:91 +#: bookwyrm/templates/search_results.html:115 #: bookwyrm/templates/user/user_layout.html:62 msgid "Lists" msgstr "Listen" -#: bookwyrm/templates/book/book.html:228 +#: bookwyrm/templates/book/book.html:231 #, fuzzy #| msgid "Go to list" msgid "Add to list" msgstr "Zur Liste" -#: bookwyrm/templates/book/book.html:238 +#: bookwyrm/templates/book/book.html:241 #: bookwyrm/templates/book/cover_modal.html:31 #: bookwyrm/templates/lists/list.html:133 msgid "Add" msgstr "Hinzufügen" -#: bookwyrm/templates/book/book.html:254 +#: bookwyrm/templates/book/book.html:257 #, fuzzy #| msgid "Review" msgid "Reviews" msgstr "Bewerten" -#: bookwyrm/templates/book/book.html:259 +#: bookwyrm/templates/book/book.html:262 #, fuzzy #| msgid "Your shelves" msgid "Your reviews" msgstr "Deine Regale" -#: bookwyrm/templates/book/book.html:265 +#: bookwyrm/templates/book/book.html:268 #, fuzzy #| msgid "Your Account" msgid "Your comments" msgstr "Dein Account" -#: bookwyrm/templates/book/book.html:271 +#: bookwyrm/templates/book/book.html:274 #, fuzzy #| msgid "Your books" msgid "Your quotes" msgstr "Deine Bücher" -#: bookwyrm/templates/book/book.html:305 +#: bookwyrm/templates/book/book.html:308 msgid "rated it" msgstr "bewertet" @@ -575,6 +581,7 @@ msgstr "Veröffentlicht von %(publisher)s." #: bookwyrm/templates/feed/feed_layout.html:70 #: bookwyrm/templates/get_started/layout.html:19 #: bookwyrm/templates/get_started/layout.html:52 +#: bookwyrm/templates/search_results.html:72 msgid "Close" msgstr "Schließen" @@ -1123,7 +1130,7 @@ msgid "Search for a user" msgstr "Suche nach Buch oder Benutzer*in" #: bookwyrm/templates/get_started/users.html:13 -#: bookwyrm/templates/search_results.html:76 +#: bookwyrm/templates/search_results.html:99 #, python-format msgid "No users found for \"%(query)s\"" msgstr "Keine Nutzer*innen für \"%(query)s\" gefunden" @@ -1902,23 +1909,33 @@ msgstr "Profil" msgid "Relationships" msgstr "Beziehungen" -#: bookwyrm/templates/search_results.html:33 +#: bookwyrm/templates/search_results.html:20 +msgid "Log in to import or add books." +msgstr "" + +#: bookwyrm/templates/search_results.html:38 msgid "Didn't find what you were looking for?" msgstr "Nicht gefunden, wonach du gesucht hast?" -#: bookwyrm/templates/search_results.html:35 +#: bookwyrm/templates/search_results.html:40 msgid "Show results from other catalogues" msgstr "Ergebnisse aus anderen Katalogen zeigen" -#: bookwyrm/templates/search_results.html:62 +#: bookwyrm/templates/search_results.html:44 msgid "Hide results from other catalogues" msgstr "Ergebnisse aus anderen Katalogen ausblenden" -#: bookwyrm/templates/search_results.html:74 +#: bookwyrm/templates/search_results.html:63 +#, fuzzy +#| msgid "Show more" +msgid "Show" +msgstr "Mehr anzeigen" + +#: bookwyrm/templates/search_results.html:97 msgid "Matching Users" msgstr "Passende Nutzer*innen" -#: bookwyrm/templates/search_results.html:93 +#: bookwyrm/templates/search_results.html:117 #, python-format msgid "No lists found for \"%(query)s\"" msgstr "Keine Liste für \"%(query)s\" gefunden" @@ -2728,12 +2745,7 @@ msgstr "kommentierte" msgid "quoted" msgstr "zitierte" -#: bookwyrm/templates/snippets/search_result_text.html:22 -#, python-format -msgid "by %(author)s" -msgstr "von %(author)s" - -#: bookwyrm/templates/snippets/search_result_text.html:30 +#: bookwyrm/templates/snippets/search_result_text.html:35 msgid "Import book" msgstr "Buch importieren" @@ -4495,6 +4507,10 @@ msgctxt "stick" msgid "club" msgstr "" +#, python-format +#~ msgid "by %(author)s" +#~ msgstr "von %(author)s" + #~ msgid "Deactivate user" #~ msgstr "Nutzer:in deaktivieren" diff --git a/locale/en_US/LC_MESSAGES/django.po b/locale/en_US/LC_MESSAGES/django.po index 6685605a..c3df47ae 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-04-26 09:56-0700\n" +"POT-Creation-Date: 2021-04-29 13:24-0700\n" "PO-Revision-Date: 2021-02-28 17:19-0800\n" "Last-Translator: Mouse Reeve \n" "Language-Team: English \n" @@ -90,23 +90,23 @@ msgstr "" msgid "A user with that username already exists." msgstr "" -#: bookwyrm/settings.py:152 +#: bookwyrm/settings.py:155 msgid "English" msgstr "" -#: bookwyrm/settings.py:153 +#: bookwyrm/settings.py:156 msgid "German" msgstr "" -#: bookwyrm/settings.py:154 +#: bookwyrm/settings.py:157 msgid "Spanish" msgstr "" -#: bookwyrm/settings.py:155 +#: bookwyrm/settings.py:158 msgid "French" msgstr "" -#: bookwyrm/settings.py:156 +#: bookwyrm/settings.py:159 msgid "Simplified Chinese" msgstr "" @@ -166,24 +166,28 @@ msgstr "" msgid "View on OpenLibrary" msgstr "" -#: bookwyrm/templates/book/book.html:102 +#: bookwyrm/templates/book/book.html:85 +msgid "View on Inventaire" +msgstr "" + +#: bookwyrm/templates/book/book.html:105 #, python-format msgid "(%(review_count)s review)" msgid_plural "(%(review_count)s reviews)" msgstr[0] "" msgstr[1] "" -#: bookwyrm/templates/book/book.html:114 +#: bookwyrm/templates/book/book.html:117 msgid "Add Description" msgstr "" -#: bookwyrm/templates/book/book.html:121 +#: bookwyrm/templates/book/book.html:124 #: bookwyrm/templates/book/edit_book.html:107 #: bookwyrm/templates/lists/form.html:12 msgid "Description:" msgstr "" -#: bookwyrm/templates/book/book.html:125 +#: bookwyrm/templates/book/book.html:128 #: bookwyrm/templates/book/edit_book.html:240 #: bookwyrm/templates/edit_author.html:78 bookwyrm/templates/lists/form.html:42 #: bookwyrm/templates/preferences/edit_user.html:70 @@ -198,7 +202,7 @@ msgstr "" msgid "Save" msgstr "" -#: bookwyrm/templates/book/book.html:126 bookwyrm/templates/book/book.html:175 +#: bookwyrm/templates/book/book.html:129 bookwyrm/templates/book/book.html:178 #: bookwyrm/templates/book/cover_modal.html:32 #: bookwyrm/templates/book/edit_book.html:241 #: bookwyrm/templates/edit_author.html:79 @@ -214,81 +218,81 @@ msgstr "" msgid "Cancel" msgstr "" -#: bookwyrm/templates/book/book.html:135 +#: bookwyrm/templates/book/book.html:138 #, python-format msgid "%(count)s editions" msgstr "" -#: bookwyrm/templates/book/book.html:143 +#: bookwyrm/templates/book/book.html:146 #, python-format msgid "This edition is on your %(shelf_name)s shelf." msgstr "" -#: bookwyrm/templates/book/book.html:149 +#: bookwyrm/templates/book/book.html:152 #, python-format msgid "" "A different edition of this book is on your %(shelf_name)s shelf." msgstr "" -#: bookwyrm/templates/book/book.html:158 +#: bookwyrm/templates/book/book.html:161 msgid "Your reading activity" msgstr "" -#: bookwyrm/templates/book/book.html:160 +#: bookwyrm/templates/book/book.html:163 msgid "Add read dates" msgstr "" -#: bookwyrm/templates/book/book.html:165 +#: bookwyrm/templates/book/book.html:168 msgid "You don't have any reading activity for this book." msgstr "" -#: bookwyrm/templates/book/book.html:172 +#: bookwyrm/templates/book/book.html:175 msgid "Create" msgstr "" -#: bookwyrm/templates/book/book.html:194 +#: bookwyrm/templates/book/book.html:197 msgid "Subjects" msgstr "" -#: bookwyrm/templates/book/book.html:206 +#: bookwyrm/templates/book/book.html:209 msgid "Places" msgstr "" -#: bookwyrm/templates/book/book.html:217 bookwyrm/templates/layout.html:65 +#: bookwyrm/templates/book/book.html:220 bookwyrm/templates/layout.html:65 #: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 -#: bookwyrm/templates/search_results.html:91 +#: bookwyrm/templates/search_results.html:115 #: bookwyrm/templates/user/user_layout.html:62 msgid "Lists" msgstr "" -#: bookwyrm/templates/book/book.html:228 +#: bookwyrm/templates/book/book.html:231 msgid "Add to list" msgstr "" -#: bookwyrm/templates/book/book.html:238 +#: bookwyrm/templates/book/book.html:241 #: bookwyrm/templates/book/cover_modal.html:31 #: bookwyrm/templates/lists/list.html:133 msgid "Add" msgstr "" -#: bookwyrm/templates/book/book.html:254 +#: bookwyrm/templates/book/book.html:257 msgid "Reviews" msgstr "" -#: bookwyrm/templates/book/book.html:259 +#: bookwyrm/templates/book/book.html:262 msgid "Your reviews" msgstr "" -#: bookwyrm/templates/book/book.html:265 +#: bookwyrm/templates/book/book.html:268 msgid "Your comments" msgstr "" -#: bookwyrm/templates/book/book.html:271 +#: bookwyrm/templates/book/book.html:274 msgid "Your quotes" msgstr "" -#: bookwyrm/templates/book/book.html:305 +#: bookwyrm/templates/book/book.html:308 msgid "rated it" msgstr "" @@ -531,6 +535,7 @@ msgstr "" #: bookwyrm/templates/feed/feed_layout.html:70 #: bookwyrm/templates/get_started/layout.html:19 #: bookwyrm/templates/get_started/layout.html:52 +#: bookwyrm/templates/search_results.html:72 msgid "Close" msgstr "" @@ -1028,7 +1033,7 @@ msgid "Search for a user" msgstr "" #: bookwyrm/templates/get_started/users.html:13 -#: bookwyrm/templates/search_results.html:76 +#: bookwyrm/templates/search_results.html:99 #, python-format msgid "No users found for \"%(query)s\"" msgstr "" @@ -1735,23 +1740,31 @@ msgstr "" msgid "Relationships" msgstr "" -#: bookwyrm/templates/search_results.html:33 +#: bookwyrm/templates/search_results.html:20 +msgid "Log in to import or add books." +msgstr "" + +#: bookwyrm/templates/search_results.html:38 msgid "Didn't find what you were looking for?" msgstr "" -#: bookwyrm/templates/search_results.html:35 +#: bookwyrm/templates/search_results.html:40 msgid "Show results from other catalogues" msgstr "" -#: bookwyrm/templates/search_results.html:62 +#: bookwyrm/templates/search_results.html:44 msgid "Hide results from other catalogues" msgstr "" -#: bookwyrm/templates/search_results.html:74 +#: bookwyrm/templates/search_results.html:63 +msgid "Show" +msgstr "" + +#: bookwyrm/templates/search_results.html:97 msgid "Matching Users" msgstr "" -#: bookwyrm/templates/search_results.html:93 +#: bookwyrm/templates/search_results.html:117 #, python-format msgid "No lists found for \"%(query)s\"" msgstr "" @@ -2481,12 +2494,7 @@ msgstr "" msgid "quoted" msgstr "" -#: bookwyrm/templates/snippets/search_result_text.html:22 -#, python-format -msgid "by %(author)s" -msgstr "" - -#: bookwyrm/templates/snippets/search_result_text.html:30 +#: bookwyrm/templates/snippets/search_result_text.html:35 msgid "Import book" msgstr "" diff --git a/locale/es/LC_MESSAGES/django.mo b/locale/es/LC_MESSAGES/django.mo index 64baee4f..1fb1ae53 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 7994ce69..dab4c486 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-04-26 09:56-0700\n" +"POT-Creation-Date: 2021-04-29 13:24-0700\n" "PO-Revision-Date: 2021-03-19 11:49+0800\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -90,23 +90,23 @@ msgstr "nombre de usuario" msgid "A user with that username already exists." msgstr "Ya existe un usuario con ese nombre." -#: bookwyrm/settings.py:152 +#: bookwyrm/settings.py:155 msgid "English" msgstr "Inglés" -#: bookwyrm/settings.py:153 +#: bookwyrm/settings.py:156 msgid "German" msgstr "Aléman" -#: bookwyrm/settings.py:154 +#: bookwyrm/settings.py:157 msgid "Spanish" msgstr "Español" -#: bookwyrm/settings.py:155 +#: bookwyrm/settings.py:158 msgid "French" msgstr "Francés" -#: bookwyrm/settings.py:156 +#: bookwyrm/settings.py:159 msgid "Simplified Chinese" msgstr "Chino simplificado" @@ -166,24 +166,30 @@ msgstr "No se pudo cargar la portada" msgid "View on OpenLibrary" msgstr "Ver en OpenLibrary" -#: bookwyrm/templates/book/book.html:102 +#: bookwyrm/templates/book/book.html:85 +#, fuzzy +#| msgid "View on OpenLibrary" +msgid "View on Inventaire" +msgstr "Ver en OpenLibrary" + +#: bookwyrm/templates/book/book.html:105 #, python-format msgid "(%(review_count)s review)" msgid_plural "(%(review_count)s reviews)" msgstr[0] "(%(review_count)s reseña)" msgstr[1] "(%(review_count)s reseñas)" -#: bookwyrm/templates/book/book.html:114 +#: bookwyrm/templates/book/book.html:117 msgid "Add Description" msgstr "Agregar descripción" -#: bookwyrm/templates/book/book.html:121 +#: bookwyrm/templates/book/book.html:124 #: bookwyrm/templates/book/edit_book.html:107 #: bookwyrm/templates/lists/form.html:12 msgid "Description:" msgstr "Descripción:" -#: bookwyrm/templates/book/book.html:125 +#: bookwyrm/templates/book/book.html:128 #: bookwyrm/templates/book/edit_book.html:240 #: bookwyrm/templates/edit_author.html:78 bookwyrm/templates/lists/form.html:42 #: bookwyrm/templates/preferences/edit_user.html:70 @@ -198,7 +204,7 @@ msgstr "Descripción:" msgid "Save" msgstr "Guardar" -#: bookwyrm/templates/book/book.html:126 bookwyrm/templates/book/book.html:175 +#: bookwyrm/templates/book/book.html:129 bookwyrm/templates/book/book.html:178 #: bookwyrm/templates/book/cover_modal.html:32 #: bookwyrm/templates/book/edit_book.html:241 #: bookwyrm/templates/edit_author.html:79 @@ -214,18 +220,18 @@ msgstr "Guardar" msgid "Cancel" msgstr "Cancelar" -#: bookwyrm/templates/book/book.html:135 +#: bookwyrm/templates/book/book.html:138 #, python-format msgid "%(count)s editions" msgstr "%(count)s ediciones" -#: bookwyrm/templates/book/book.html:143 +#: bookwyrm/templates/book/book.html:146 #, python-format msgid "This edition is on your %(shelf_name)s shelf." msgstr "" "Esta edición está en tu %(shelf_name)s estante." -#: bookwyrm/templates/book/book.html:149 +#: bookwyrm/templates/book/book.html:152 #, python-format msgid "" "A different edition of this book is on your edición diferente de este libro está en tu " "%(shelf_name)s estante." -#: bookwyrm/templates/book/book.html:158 +#: bookwyrm/templates/book/book.html:161 msgid "Your reading activity" msgstr "Tu actividad de lectura" -#: bookwyrm/templates/book/book.html:160 +#: bookwyrm/templates/book/book.html:163 msgid "Add read dates" msgstr "Agregar fechas de lectura" -#: bookwyrm/templates/book/book.html:165 +#: bookwyrm/templates/book/book.html:168 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:172 +#: bookwyrm/templates/book/book.html:175 msgid "Create" msgstr "Crear" -#: bookwyrm/templates/book/book.html:194 +#: bookwyrm/templates/book/book.html:197 msgid "Subjects" msgstr "Sujetos" -#: bookwyrm/templates/book/book.html:206 +#: bookwyrm/templates/book/book.html:209 msgid "Places" msgstr "Lugares" -#: bookwyrm/templates/book/book.html:217 bookwyrm/templates/layout.html:65 +#: bookwyrm/templates/book/book.html:220 bookwyrm/templates/layout.html:65 #: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 -#: bookwyrm/templates/search_results.html:91 +#: bookwyrm/templates/search_results.html:115 #: bookwyrm/templates/user/user_layout.html:62 msgid "Lists" msgstr "Listas" -#: bookwyrm/templates/book/book.html:228 +#: bookwyrm/templates/book/book.html:231 msgid "Add to list" msgstr "Agregar a lista" -#: bookwyrm/templates/book/book.html:238 +#: bookwyrm/templates/book/book.html:241 #: bookwyrm/templates/book/cover_modal.html:31 #: bookwyrm/templates/lists/list.html:133 msgid "Add" msgstr "Agregar" -#: bookwyrm/templates/book/book.html:254 +#: bookwyrm/templates/book/book.html:257 #, fuzzy #| msgid "Review" msgid "Reviews" msgstr "Reseña" -#: bookwyrm/templates/book/book.html:259 +#: bookwyrm/templates/book/book.html:262 #, fuzzy #| msgid "Your shelves" msgid "Your reviews" msgstr "Tus estantes" -#: bookwyrm/templates/book/book.html:265 +#: bookwyrm/templates/book/book.html:268 #, fuzzy #| msgid "Your Account" msgid "Your comments" msgstr "Tu cuenta" -#: bookwyrm/templates/book/book.html:271 +#: bookwyrm/templates/book/book.html:274 #, fuzzy #| msgid "Your books" msgid "Your quotes" msgstr "Tus libros" -#: bookwyrm/templates/book/book.html:305 +#: bookwyrm/templates/book/book.html:308 msgid "rated it" msgstr "lo calificó con" @@ -542,6 +548,7 @@ msgstr "Publicado por %(publisher)s." #: bookwyrm/templates/feed/feed_layout.html:70 #: bookwyrm/templates/get_started/layout.html:19 #: bookwyrm/templates/get_started/layout.html:52 +#: bookwyrm/templates/search_results.html:72 msgid "Close" msgstr "Cerrar" @@ -1054,7 +1061,7 @@ msgid "Search for a user" msgstr "Buscar un usuario" #: bookwyrm/templates/get_started/users.html:13 -#: bookwyrm/templates/search_results.html:76 +#: bookwyrm/templates/search_results.html:99 #, python-format msgid "No users found for \"%(query)s\"" msgstr "No se encontró ningún usuario correspondiente a \"%(query)s\"" @@ -1805,23 +1812,33 @@ msgstr "Perfil" msgid "Relationships" msgstr "Relaciones" -#: bookwyrm/templates/search_results.html:33 +#: bookwyrm/templates/search_results.html:20 +msgid "Log in to import or add books." +msgstr "" + +#: bookwyrm/templates/search_results.html:38 msgid "Didn't find what you were looking for?" msgstr "¿No encontraste lo que buscabas?" -#: bookwyrm/templates/search_results.html:35 +#: bookwyrm/templates/search_results.html:40 msgid "Show results from other catalogues" msgstr "Mostrar resultados de otros catálogos" -#: bookwyrm/templates/search_results.html:62 +#: bookwyrm/templates/search_results.html:44 msgid "Hide results from other catalogues" msgstr "Ocultar resultados de otros catálogos" -#: bookwyrm/templates/search_results.html:74 +#: bookwyrm/templates/search_results.html:63 +#, fuzzy +#| msgid "Show more" +msgid "Show" +msgstr "Mostrar más" + +#: bookwyrm/templates/search_results.html:97 msgid "Matching Users" msgstr "Usuarios correspondientes" -#: bookwyrm/templates/search_results.html:93 +#: bookwyrm/templates/search_results.html:117 #, python-format msgid "No lists found for \"%(query)s\"" msgstr "No se encontró ningúna lista correspondiente a \"%(query)s\"" @@ -2571,12 +2588,7 @@ msgstr "comentó en" msgid "quoted" msgstr "citó" -#: bookwyrm/templates/snippets/search_result_text.html:22 -#, python-format -msgid "by %(author)s" -msgstr "por %(author)s" - -#: bookwyrm/templates/snippets/search_result_text.html:30 +#: bookwyrm/templates/snippets/search_result_text.html:35 msgid "Import book" msgstr "Importar libro" @@ -4299,6 +4311,10 @@ msgctxt "stick" msgid "club" msgstr "garrote" +#, python-format +#~ msgid "by %(author)s" +#~ msgstr "por %(author)s" + #, python-format #~ msgid "%(rating)s star" #~ msgid_plural "%(rating)s stars" diff --git a/locale/fr_FR/LC_MESSAGES/django.mo b/locale/fr_FR/LC_MESSAGES/django.mo index b91a542d..b88e6e3e 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 ee2f7936..01963e46 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-04-26 09:56-0700\n" +"POT-Creation-Date: 2021-04-29 13:24-0700\n" "PO-Revision-Date: 2021-04-05 12:44+0100\n" "Last-Translator: Fabien Basmaison \n" "Language-Team: Mouse Reeve \n" @@ -96,23 +96,23 @@ msgstr "nom du compte :" msgid "A user with that username already exists." msgstr "Ce nom est déjà associé à un compte." -#: bookwyrm/settings.py:152 +#: bookwyrm/settings.py:155 msgid "English" msgstr "English" -#: bookwyrm/settings.py:153 +#: bookwyrm/settings.py:156 msgid "German" msgstr "Deutsch" -#: bookwyrm/settings.py:154 +#: bookwyrm/settings.py:157 msgid "Spanish" msgstr "Español" -#: bookwyrm/settings.py:155 +#: bookwyrm/settings.py:158 msgid "French" msgstr "Français" -#: bookwyrm/settings.py:156 +#: bookwyrm/settings.py:159 msgid "Simplified Chinese" msgstr "简化字" @@ -172,24 +172,30 @@ msgstr "La couverture n’a pu être chargée" msgid "View on OpenLibrary" msgstr "Voir sur OpenLibrary" -#: bookwyrm/templates/book/book.html:102 +#: bookwyrm/templates/book/book.html:85 +#, fuzzy +#| msgid "View on OpenLibrary" +msgid "View on Inventaire" +msgstr "Voir sur OpenLibrary" + +#: bookwyrm/templates/book/book.html:105 #, 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:114 +#: bookwyrm/templates/book/book.html:117 msgid "Add Description" msgstr "Ajouter une description" -#: bookwyrm/templates/book/book.html:121 +#: bookwyrm/templates/book/book.html:124 #: bookwyrm/templates/book/edit_book.html:107 #: bookwyrm/templates/lists/form.html:12 msgid "Description:" msgstr "Description :" -#: bookwyrm/templates/book/book.html:125 +#: bookwyrm/templates/book/book.html:128 #: bookwyrm/templates/book/edit_book.html:240 #: bookwyrm/templates/edit_author.html:78 bookwyrm/templates/lists/form.html:42 #: bookwyrm/templates/preferences/edit_user.html:70 @@ -204,7 +210,7 @@ msgstr "Description :" msgid "Save" msgstr "Enregistrer" -#: bookwyrm/templates/book/book.html:126 bookwyrm/templates/book/book.html:175 +#: bookwyrm/templates/book/book.html:129 bookwyrm/templates/book/book.html:178 #: bookwyrm/templates/book/cover_modal.html:32 #: bookwyrm/templates/book/edit_book.html:241 #: bookwyrm/templates/edit_author.html:79 @@ -220,18 +226,18 @@ msgstr "Enregistrer" msgid "Cancel" msgstr "Annuler" -#: bookwyrm/templates/book/book.html:135 +#: bookwyrm/templates/book/book.html:138 #, python-format msgid "%(count)s editions" msgstr "%(count)s éditions" -#: bookwyrm/templates/book/book.html:143 +#: bookwyrm/templates/book/book.html:146 #, 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:149 +#: bookwyrm/templates/book/book.html:152 #, python-format msgid "" "A different edition of this book is on your édition différente de ce livre existe sur " "votre étagère %(shelf_name)s." -#: bookwyrm/templates/book/book.html:158 +#: bookwyrm/templates/book/book.html:161 msgid "Your reading activity" msgstr "Votre activité de lecture" -#: bookwyrm/templates/book/book.html:160 +#: bookwyrm/templates/book/book.html:163 msgid "Add read dates" msgstr "Ajouter des dates de lecture" -#: bookwyrm/templates/book/book.html:165 +#: bookwyrm/templates/book/book.html:168 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:172 +#: bookwyrm/templates/book/book.html:175 msgid "Create" msgstr "Créer" -#: bookwyrm/templates/book/book.html:194 +#: bookwyrm/templates/book/book.html:197 msgid "Subjects" msgstr "Sujets" -#: bookwyrm/templates/book/book.html:206 +#: bookwyrm/templates/book/book.html:209 msgid "Places" msgstr "Lieux" -#: bookwyrm/templates/book/book.html:217 bookwyrm/templates/layout.html:65 +#: bookwyrm/templates/book/book.html:220 bookwyrm/templates/layout.html:65 #: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 -#: bookwyrm/templates/search_results.html:91 +#: bookwyrm/templates/search_results.html:115 #: bookwyrm/templates/user/user_layout.html:62 msgid "Lists" msgstr "Listes" -#: bookwyrm/templates/book/book.html:228 +#: bookwyrm/templates/book/book.html:231 msgid "Add to list" msgstr "Ajouter à la liste" -#: bookwyrm/templates/book/book.html:238 +#: bookwyrm/templates/book/book.html:241 #: bookwyrm/templates/book/cover_modal.html:31 #: bookwyrm/templates/lists/list.html:133 msgid "Add" msgstr "Ajouter" -#: bookwyrm/templates/book/book.html:254 +#: bookwyrm/templates/book/book.html:257 #, fuzzy #| msgid "Review" msgid "Reviews" msgstr "Critique" -#: bookwyrm/templates/book/book.html:259 +#: bookwyrm/templates/book/book.html:262 #, fuzzy #| msgid "Your shelves" msgid "Your reviews" msgstr "Vos étagères" -#: bookwyrm/templates/book/book.html:265 +#: bookwyrm/templates/book/book.html:268 #, fuzzy #| msgid "Your Account" msgid "Your comments" msgstr "Votre compte" -#: bookwyrm/templates/book/book.html:271 +#: bookwyrm/templates/book/book.html:274 #, fuzzy #| msgid "Your books" msgid "Your quotes" msgstr "Vos livres" -#: bookwyrm/templates/book/book.html:305 +#: bookwyrm/templates/book/book.html:308 msgid "rated it" msgstr "l’a noté" @@ -548,6 +554,7 @@ msgstr "Publié par %(publisher)s." #: bookwyrm/templates/feed/feed_layout.html:70 #: bookwyrm/templates/get_started/layout.html:19 #: bookwyrm/templates/get_started/layout.html:52 +#: bookwyrm/templates/search_results.html:72 msgid "Close" msgstr "Fermer" @@ -1066,7 +1073,7 @@ msgid "Search for a user" msgstr "Chercher un compte" #: bookwyrm/templates/get_started/users.html:13 -#: bookwyrm/templates/search_results.html:76 +#: bookwyrm/templates/search_results.html:99 #, python-format msgid "No users found for \"%(query)s\"" msgstr "Aucun compte trouvé pour « %(query)s »" @@ -1830,23 +1837,33 @@ msgstr "Profil" msgid "Relationships" msgstr "Relations" -#: bookwyrm/templates/search_results.html:33 +#: bookwyrm/templates/search_results.html:20 +msgid "Log in to import or add books." +msgstr "" + +#: bookwyrm/templates/search_results.html:38 msgid "Didn't find what you were looking for?" msgstr "Vous n’avez pas trouvé ce que vous cherchiez ?" -#: bookwyrm/templates/search_results.html:35 +#: bookwyrm/templates/search_results.html:40 msgid "Show results from other catalogues" msgstr "Montrer les résultats d’autres catalogues" -#: bookwyrm/templates/search_results.html:62 +#: bookwyrm/templates/search_results.html:44 msgid "Hide results from other catalogues" msgstr "Masquer les résultats d’autres catalogues" -#: bookwyrm/templates/search_results.html:74 +#: bookwyrm/templates/search_results.html:63 +#, fuzzy +#| msgid "Show more" +msgid "Show" +msgstr "Déplier" + +#: bookwyrm/templates/search_results.html:97 msgid "Matching Users" msgstr "Comptes correspondants" -#: bookwyrm/templates/search_results.html:93 +#: bookwyrm/templates/search_results.html:117 #, python-format msgid "No lists found for \"%(query)s\"" msgstr "Aucune liste trouvée pour « %(query)s »" @@ -2605,12 +2622,7 @@ msgstr "a commenté" msgid "quoted" msgstr "a cité" -#: bookwyrm/templates/snippets/search_result_text.html:22 -#, python-format -msgid "by %(author)s" -msgstr "par %(author)s" - -#: bookwyrm/templates/snippets/search_result_text.html:30 +#: bookwyrm/templates/snippets/search_result_text.html:35 msgid "Import book" msgstr "Importer le livre" @@ -4341,6 +4353,10 @@ msgctxt "stick" msgid "club" msgstr "" +#, python-format +#~ msgid "by %(author)s" +#~ msgstr "par %(author)s" + #~ msgid "Deactivate user" #~ msgstr "Désactiver le compte" diff --git a/locale/zh_Hans/LC_MESSAGES/django.mo b/locale/zh_Hans/LC_MESSAGES/django.mo index 07b8d63c..765ee77f 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 789960cc..783fc70a 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-04-26 09:56-0700\n" +"POT-Creation-Date: 2021-04-29 13:24-0700\n" "PO-Revision-Date: 2021-03-20 00:56+0000\n" "Last-Translator: Kana \n" "Language-Team: Mouse Reeve \n" @@ -96,23 +96,23 @@ msgstr "用户名" msgid "A user with that username already exists." msgstr "已经存在使用该用户名的用户。" -#: bookwyrm/settings.py:152 +#: bookwyrm/settings.py:155 msgid "English" msgstr "English(英语)" -#: bookwyrm/settings.py:153 +#: bookwyrm/settings.py:156 msgid "German" msgstr "Deutsch(德语)" -#: bookwyrm/settings.py:154 +#: bookwyrm/settings.py:157 msgid "Spanish" msgstr "Español(西班牙语)" -#: bookwyrm/settings.py:155 +#: bookwyrm/settings.py:158 msgid "French" msgstr "Français(法语)" -#: bookwyrm/settings.py:156 +#: bookwyrm/settings.py:159 msgid "Simplified Chinese" msgstr "简体中文" @@ -172,23 +172,29 @@ msgstr "加载封面失败" msgid "View on OpenLibrary" msgstr "在 OpenLibrary 查看" -#: bookwyrm/templates/book/book.html:102 +#: bookwyrm/templates/book/book.html:85 +#, fuzzy +#| msgid "View on OpenLibrary" +msgid "View on Inventaire" +msgstr "在 OpenLibrary 查看" + +#: bookwyrm/templates/book/book.html:105 #, python-format msgid "(%(review_count)s review)" msgid_plural "(%(review_count)s reviews)" msgstr[0] "(%(review_count)s 则书评)" -#: bookwyrm/templates/book/book.html:114 +#: bookwyrm/templates/book/book.html:117 msgid "Add Description" msgstr "添加描述" -#: bookwyrm/templates/book/book.html:121 +#: bookwyrm/templates/book/book.html:124 #: bookwyrm/templates/book/edit_book.html:107 #: bookwyrm/templates/lists/form.html:12 msgid "Description:" msgstr "描述:" -#: bookwyrm/templates/book/book.html:125 +#: bookwyrm/templates/book/book.html:128 #: bookwyrm/templates/book/edit_book.html:240 #: bookwyrm/templates/edit_author.html:78 bookwyrm/templates/lists/form.html:42 #: bookwyrm/templates/preferences/edit_user.html:70 @@ -203,7 +209,7 @@ msgstr "描述:" msgid "Save" msgstr "保存" -#: bookwyrm/templates/book/book.html:126 bookwyrm/templates/book/book.html:175 +#: bookwyrm/templates/book/book.html:129 bookwyrm/templates/book/book.html:178 #: bookwyrm/templates/book/cover_modal.html:32 #: bookwyrm/templates/book/edit_book.html:241 #: bookwyrm/templates/edit_author.html:79 @@ -219,17 +225,17 @@ msgstr "保存" msgid "Cancel" msgstr "取消" -#: bookwyrm/templates/book/book.html:135 +#: bookwyrm/templates/book/book.html:138 #, python-format msgid "%(count)s editions" msgstr "%(count)s 个版本" -#: bookwyrm/templates/book/book.html:143 +#: bookwyrm/templates/book/book.html:146 #, python-format msgid "This edition is on your %(shelf_name)s shelf." msgstr "此版本在你的 %(shelf_name)s 书架上。" -#: bookwyrm/templates/book/book.html:149 +#: bookwyrm/templates/book/book.html:152 #, python-format msgid "" "A different edition of this book is on your 另一个版本 在你的 %(shelf_name)s 书架上。" -#: bookwyrm/templates/book/book.html:158 +#: bookwyrm/templates/book/book.html:161 msgid "Your reading activity" msgstr "你的阅读活动" -#: bookwyrm/templates/book/book.html:160 +#: bookwyrm/templates/book/book.html:163 msgid "Add read dates" msgstr "添加阅读日期" -#: bookwyrm/templates/book/book.html:165 +#: bookwyrm/templates/book/book.html:168 msgid "You don't have any reading activity for this book." msgstr "你还没有任何这本书的阅读活动。" -#: bookwyrm/templates/book/book.html:172 +#: bookwyrm/templates/book/book.html:175 msgid "Create" msgstr "创建" -#: bookwyrm/templates/book/book.html:194 +#: bookwyrm/templates/book/book.html:197 msgid "Subjects" msgstr "主题" -#: bookwyrm/templates/book/book.html:206 +#: bookwyrm/templates/book/book.html:209 msgid "Places" msgstr "地点" -#: bookwyrm/templates/book/book.html:217 bookwyrm/templates/layout.html:65 +#: bookwyrm/templates/book/book.html:220 bookwyrm/templates/layout.html:65 #: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12 -#: bookwyrm/templates/search_results.html:91 +#: bookwyrm/templates/search_results.html:115 #: bookwyrm/templates/user/user_layout.html:62 msgid "Lists" msgstr "列表" -#: bookwyrm/templates/book/book.html:228 +#: bookwyrm/templates/book/book.html:231 msgid "Add to list" msgstr "添加到列表" -#: bookwyrm/templates/book/book.html:238 +#: bookwyrm/templates/book/book.html:241 #: bookwyrm/templates/book/cover_modal.html:31 #: bookwyrm/templates/lists/list.html:133 msgid "Add" msgstr "添加" -#: bookwyrm/templates/book/book.html:254 +#: bookwyrm/templates/book/book.html:257 #, fuzzy #| msgid "Review" msgid "Reviews" msgstr "书评" -#: bookwyrm/templates/book/book.html:259 +#: bookwyrm/templates/book/book.html:262 #, fuzzy #| msgid "Your shelves" msgid "Your reviews" msgstr "你的书架" -#: bookwyrm/templates/book/book.html:265 +#: bookwyrm/templates/book/book.html:268 #, fuzzy #| msgid "Your Account" msgid "Your comments" msgstr "你的帐号" -#: bookwyrm/templates/book/book.html:271 +#: bookwyrm/templates/book/book.html:274 #, fuzzy #| msgid "Your books" msgid "Your quotes" msgstr "你的书目" -#: bookwyrm/templates/book/book.html:305 +#: bookwyrm/templates/book/book.html:308 msgid "rated it" msgstr "评价了" @@ -546,6 +552,7 @@ msgstr "由 %(publisher)s 出版。" #: bookwyrm/templates/feed/feed_layout.html:70 #: bookwyrm/templates/get_started/layout.html:19 #: bookwyrm/templates/get_started/layout.html:52 +#: bookwyrm/templates/search_results.html:72 msgid "Close" msgstr "关闭" @@ -1046,7 +1053,7 @@ msgid "Search for a user" msgstr "搜索用户" #: bookwyrm/templates/get_started/users.html:13 -#: bookwyrm/templates/search_results.html:76 +#: bookwyrm/templates/search_results.html:99 #, python-format msgid "No users found for \"%(query)s\"" msgstr "没有找到 \"%(query)s\" 的用户" @@ -1795,23 +1802,33 @@ msgstr "个人资料" msgid "Relationships" msgstr "关系" -#: bookwyrm/templates/search_results.html:33 +#: bookwyrm/templates/search_results.html:20 +msgid "Log in to import or add books." +msgstr "" + +#: bookwyrm/templates/search_results.html:38 msgid "Didn't find what you were looking for?" msgstr "没有找到你想找的?" -#: bookwyrm/templates/search_results.html:35 +#: bookwyrm/templates/search_results.html:40 msgid "Show results from other catalogues" msgstr "显示其它类别的结果" -#: bookwyrm/templates/search_results.html:62 +#: bookwyrm/templates/search_results.html:44 msgid "Hide results from other catalogues" msgstr "隐藏其它类别的结果" -#: bookwyrm/templates/search_results.html:74 +#: bookwyrm/templates/search_results.html:63 +#, fuzzy +#| msgid "Show more" +msgid "Show" +msgstr "显示更多" + +#: bookwyrm/templates/search_results.html:97 msgid "Matching Users" msgstr "匹配的用户" -#: bookwyrm/templates/search_results.html:93 +#: bookwyrm/templates/search_results.html:117 #, python-format msgid "No lists found for \"%(query)s\"" msgstr "没有找到 \"%(query)s\" 的列表" @@ -2570,12 +2587,7 @@ msgstr "评论了" msgid "quoted" msgstr "引用了" -#: bookwyrm/templates/snippets/search_result_text.html:22 -#, python-format -msgid "by %(author)s" -msgstr "由 %(author)s 所著" - -#: bookwyrm/templates/snippets/search_result_text.html:30 +#: bookwyrm/templates/snippets/search_result_text.html:35 msgid "Import book" msgstr "导入书目" @@ -4292,6 +4304,10 @@ msgctxt "stick" msgid "club" msgstr "" +#, python-format +#~ msgid "by %(author)s" +#~ msgstr "由 %(author)s 所著" + #~ msgid "Deactivate user" #~ msgstr "停用用户"