Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2021-04-30 06:54:05 -07:00
commit 8761357905
74 changed files with 2195 additions and 661 deletions

View file

@ -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]"

View file

@ -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]"

View file

@ -9,10 +9,11 @@ Permission is hereby granted, free of charge, to any person or organization (the
1. The above copyright notice and this permission notice shall be included in all copies or modified versions of the Software.
2. The User is one of the following:
a. An individual person, laboring for themselves
b. A non-profit organization
c. An educational institution
d. An organization that seeks shared profit for all of its members, and allows non-members to set the cost of their labor
1. An individual person, laboring for themselves
2. A non-profit organization
3. An educational institution
4. An organization that seeks shared profit for all of its members, and allows non-members to set the cost of their labor
3. If the User is an organization with owners, then all owners are workers and all workers are owners with equal equity and/or equal vote.

View file

@ -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"

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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),

View file

@ -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()

View file

@ -1,3 +1,3 @@
""" settings book data connectors """
CONNECTORS = ["openlibrary", "self_connector", "bookwyrm_connector"]
CONNECTORS = ["openlibrary", "inventaire", "self_connector", "bookwyrm_connector"]

View file

@ -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",

View file

@ -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,
),
),
]

View file

@ -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
),
),
]

View file

@ -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 = []

View file

@ -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",
),
]

View file

@ -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)

View file

@ -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()

View file

@ -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,

View file

@ -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")
@ -34,6 +35,8 @@ LOCALE_PATHS = [
os.path.join(BASE_DIR, "locale"),
]
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/

View file

@ -43,10 +43,6 @@ body {
white-space: nowrap !important;
width: 0.01em !important;
}
.m-0-mobile {
margin: 0 !important;
}
}
.button.is-transparent {
@ -132,7 +128,7 @@ body {
*
* \e9d9: filled star
* \e9d7: empty star;
******************************************************************************/
* -------------------------------------------------------------------------- */
.form-rate-stars {
width: max-content;
@ -158,70 +154,67 @@ body {
}
/** Book covers
*
* - .is-cover gives the behaviour of the cover and its surrounding. (optional)
* - .cover-container gives the dimensions and position (for borders, image and other elements).
* - .book-cover is positioned and sized based on its container.
*
* To have the cover within specific dimensions, specify a width or height for
* standard bulmas named breapoints:
*
* `is-(w|h)-(auto|xs|s|m|l|xl|xxl)[-(mobile|tablet|desktop)]`
*
* The cover will be centered horizontally and vertically within those dimensions.
*
* When using `.column.is-N`, add `.is-w-auto` to the container so that the flex
* calculations are not biased by the default `max-content`.
******************************************************************************/
.column.is-cover {
flex-grow: 0 !important;
}
.column.is-cover,
.column.is-cover + .column {
flex-basis: auto !important;
}
.cover-container {
height: 250px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
width: max-content;
max-width: 250px;
max-width: 100%;
overflow: hidden;
}
.cover-container.is-large {
height: max-content;
max-width: 330px;
}
.cover-container.is-large img {
max-height: 500px;
height: auto;
}
.cover-container.is-medium {
height: 150px;
}
.cover-container.is-small {
height: 100px;
}
@media only screen and (max-width: 768px) {
.cover-container {
height: 200px;
width: max-content;
}
.cover-container.is-medium {
height: 100px;
}
}
/* Book cover
* -------------------------------------------------------------------------- */
.book-cover {
height: 100%;
object-fit: scale-down;
display: block;
max-width: 100%;
max-height: 100%;
/* Useful when stretching under-sized images. */
image-rendering: optimizeQuality;
image-rendering: smooth;
}
.no-cover {
position: relative;
white-space: normal;
}
/* Cover caption
* -------------------------------------------------------------------------- */
.no-cover div {
.no-cover .cover_caption {
position: absolute;
padding: 1em;
color: white;
top: 0;
right: 0;
bottom: 0;
left: 0;
text-align: center;
}
.cover-container.is-medium .no-cover div {
font-size: 0.9em;
padding: 0.3em;
}
.cover-container.is-small .no-cover div {
font-size: 0.7em;
padding: 0.1em;
padding: 0.25em;
font-size: 0.75em;
color: white;
background-color: #002549;
}
/** Avatars
@ -232,16 +225,6 @@ body {
display: inline;
}
.is-32x32 {
min-width: 32px;
min-height: 32px;
}
.is-96x96 {
min-width: 96px;
min-height: 96px;
}
/** Statuses: Quotes
*
* \e906: icon-quote-open
@ -346,3 +329,386 @@ body {
display: none;
}
}
/* Dimensions
* @todo These could be in rem.
******************************************************************************/
.is-32x32 {
min-width: 32px !important;
min-height: 32px !important;
}
.is-96x96 {
min-width: 96px !important;
min-height: 96px !important;
}
.is-w-auto {
width: auto !important;
}
.is-w-xs {
width: 80px !important;
}
.is-w-s {
width: 100px !important;
}
.is-w-m {
width: 150px !important;
}
.is-w-l {
width: 200px !important;
}
.is-w-xl {
width: 250px !important;
}
.is-w-xxl {
width: 500px !important;
}
.is-h-xs {
height: 80px !important;
}
.is-h-s {
height: 100px !important;
}
.is-h-m {
height: 150px !important;
}
.is-h-l {
height: 200px !important;
}
.is-h-xl {
height: 250px !important;
}
.is-h-xxl {
height: 500px !important;
}
@media only screen and (max-width: 768px) {
.is-w-auto-mobile {
width: auto !important;
}
.is-w-xs-mobile {
width: 80px !important;
}
.is-w-s-mobile {
width: 100px !important;
}
.is-w-m-mobile {
width: 150px !important;
}
.is-w-l-mobile {
width: 200px !important;
}
.is-w-xl-mobile {
width: 250px !important;
}
.is-w-xxl-mobile {
width: 500px !important;
}
.is-h-xs-mobile {
height: 80px !important;
}
.is-h-s-mobile {
height: 100px !important;
}
.is-h-m-mobile {
height: 150px !important;
}
.is-h-l-mobile {
height: 200px !important;
}
.is-h-xl-mobile {
height: 250px !important;
}
.is-h-xxl-mobile {
height: 500px !important;
}
}
@media only screen and (min-width: 769px) {
.is-w-auto-tablet {
width: auto !important;
}
.is-w-xs-tablet {
width: 80px !important;
}
.is-w-s-tablet {
width: 100px !important;
}
.is-w-m-tablet {
width: 150px !important;
}
.is-w-l-tablet {
width: 200px !important;
}
.is-w-xl-tablet {
width: 250px !important;
}
.is-w-xxl-tablet {
width: 500px !important;
}
.is-h-xs-tablet {
height: 80px !important;
}
.is-h-s-tablet {
height: 100px !important;
}
.is-h-m-tablet {
height: 150px !important;
}
.is-h-l-tablet {
height: 200px !important;
}
.is-h-xl-tablet {
height: 250px !important;
}
.is-h-xxl-tablet {
height: 500px !important;
}
}
@media only screen and (min-width: 1024px) {
.is-w-auto-desktop {
width: auto !important;
}
.is-w-xs-desktop {
width: 80px !important;
}
.is-w-s-desktop {
width: 100px !important;
}
.is-w-m-desktop {
width: 150px !important;
}
.is-w-l-desktop {
width: 200px !important;
}
.is-w-xl-desktop {
width: 250px !important;
}
.is-w-xxl-desktop {
width: 500px !important;
}
.is-h-xs-desktop {
height: 80px !important;
}
.is-h-s-desktop {
height: 100px !important;
}
.is-h-m-desktop {
height: 150px !important;
}
.is-h-l-desktop {
height: 200px !important;
}
.is-h-xl-desktop {
height: 250px !important;
}
.is-h-xxl-desktop {
height: 500px !important;
}
}
/* Alignments
*
* Use them with `.align.to-(c|t|r|b|l)[-(mobile|tablet)]`
******************************************************************************/
/* Flex item position
* -------------------------------------------------------------------------- */
.align {
display: flex !important;
flex-direction: row !important;
}
.align.to-c {
justify-content: center !important;
}
.align.to-t {
align-items: flex-start !important;
}
.align.to-r {
justify-content: flex-end !important;
}
.align.to-b {
align-items: flex-end !important;
}
.align.to-l {
justify-content: flex-start !important;
}
@media screen and (max-width: 768px) {
.align.to-c-mobile {
justify-content: center !important;
}
.align.to-t-mobile {
align-items: flex-start !important;
}
.align.to-r-mobile {
justify-content: flex-end !important;
}
.align.to-b-mobile {
align-items: flex-end !important;
}
.align.to-l-mobile {
justify-content: flex-start !important;
}
}
@media screen and (min-width: 769px) {
.align.to-c-tablet {
justify-content: center !important;
}
.align.to-t-tablet {
align-items: flex-start !important;
}
.align.to-r-tablet {
justify-content: flex-end !important;
}
.align.to-b-tablet {
align-items: flex-end !important;
}
.align.to-l-tablet {
justify-content: flex-start !important;
}
}
/* Spacings
*
* Those are supplementary rules to Bulmas. They follow the same conventions.
* Add those youll need.
******************************************************************************/
.mr-auto {
margin-right: auto !important;
}
.ml-auto {
margin-left: auto !important;
}
@media screen and (max-width: 768px) {
.m-0-mobile {
margin: 0 !important;
}
.mr-auto-mobile {
margin-right: auto !important;
}
.ml-auto-mobile {
margin-left: auto !important;
}
.mt-3-mobile {
margin-top: 0.75rem !important;
}
.ml-3-mobile {
margin-left: 0.75rem !important;
}
.mx-3-mobile {
margin-right: 0.75rem !important;
margin-left: 0.75rem !important;
}
.my-3-mobile {
margin-top: 0.75rem !important;
margin-bottom: 0.75rem !important;
}
}
@media screen and (min-width: 769px) {
.m-0-tablet {
margin: 0 !important;
}
.mr-auto-tablet {
margin-right: auto !important;
}
.ml-auto-tablet {
margin-left: auto !important;
}
.mt-3-tablet {
margin-top: 0.75rem !important;
}
.ml-3-tablet {
margin-left: 0.75rem !important;
}
.mx-3-tablet {
margin-right: 0.75rem !important;
margin-left: 0.75rem !important;
}
.my-3-tablet {
margin-top: 0.75rem !important;
margin-bottom: 0.75rem !important;
}
}

File diff suppressed because one or more lines are too long

View file

@ -48,10 +48,9 @@
<div class="columns">
<div class="column is-one-fifth">
<div class="is-clipped">
{% include 'snippets/book_cover.html' with book=book size=large %}
{% include 'snippets/rate_action.html' with user=request.user book=book %}
</div>
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m-mobile' %}
{% include 'snippets/rate_action.html' with user=request.user book=book %}
<div class="mb-3">
{% include 'snippets/shelve_button/shelve_button.html' %}
</div>
@ -81,6 +80,9 @@
{% if book.openlibrary_key %}
<p><a href="https://openlibrary.org/books/{{ book.openlibrary_key }}" target="_blank" rel="noopener">{% trans "View on OpenLibrary" %}</a></p>
{% endif %}
{% if book.inventaire_id %}
<p><a href="https://inventaire.io/entity/{{ book.inventaire_id }}" target="_blank" rel="noopener">{% trans "View on Inventaire" %}</a></p>
{% endif %}
</section>
</div>

View file

@ -169,10 +169,11 @@
<div class="column is-half">
<h2 class="title is-4">{% trans "Cover" %}</h2>
<div class="columns">
<div class="column is-narrow">
{% include 'snippets/book_cover.html' with book=book size="small" %}
<div class="column is-3 is-cover">
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-xl-mobile is-w-auto-tablet' %}
</div>
<div class="column is-narrow">
<div class="column">
<div class="block">
<p>
<label class="label" for="id_cover">{% trans "Upload cover:" %}</label>

View file

@ -13,32 +13,34 @@
<div class="block">
{% for book in editions %}
<div class="columns">
<div class="column is-2">
<div class="columns is-gapless mb-6">
<div class="column is-cover">
<a href="/book/{{ book.id }}">
{% include 'snippets/book_cover.html' with book=book size="medium" %}
{% include 'snippets/book_cover.html' with book=book cover_class='is-w-m is-h-m align to-l-mobile' %}
</a>
</div>
<div class="column is-7">
<h2 class="title is-5">
<div class="column my-3-mobile ml-3-tablet mr-auto">
<h2 class="title is-5 mb-1">
<a href="/book/{{ book.id }}" class="has-text-black">
{{ book.title }}
</a>
</h2>
{% with book=book %}
<div class="columns is-multiline">
<div class="columns is-multiline is-gapless ml-3-tablet">
<div class="column is-half">
{% include 'book/publisher_info.html' %}
</div>
<div class="column is-half ">
<div class="column ml-3-tablet">
{% include 'book/book_identifiers.html' %}
</div>
</div>
{% endwith %}
</div>
<div class="column is-3">
<div class="column is-narrow">
{% include 'snippets/shelve_button/shelve_button.html' with book=book switch_mode=True %}
</div>
</div>

View file

@ -11,14 +11,15 @@
{% with 0|uuid as uuid %}
<div class="box columns">
{% if book %}
<div class="column is-one-third">
<div class="column is-3 is-cover">
<div class="block">
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book %}</a>
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book cover_class='is-w-auto-tablet is-h-l-mobile' %}</a>
</div>
<h3 class="title is-6">{% include 'snippets/book_titleby.html' with book=book %}</h3>
</div>
{% endif %}
<div class="column is-two-thirds">
<div class="column">
{% if draft.reply_parent %}
{% include 'snippets/status/status.html' with status=draft.reply_parent no_interact=True %}
{% endif %}

View file

@ -1,19 +1,38 @@
{% load bookwyrm_tags %}
{% load i18n %}
{% if book %}
<div class="columns">
<div class="column is-narrow">
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book size="large" %}</a>
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
</div>
<div class="column">
<h3 class="title is-5"><a href="/book/{{ book.id }}">{{ book.title }}</a></h3>
{% if book.authors %}
<p class="subtitle is-5">{% trans "by" %} {% include 'snippets/authors.html' with book=book %}</p>
{% endif %}
{% if book|book_description %}
<blockquote class="content">{{ book|book_description|to_markdown|safe|truncatewords_html:50 }}</blockquote>
{% endif %}
</div>
</div>
{% with book=book %}
<div class="columns is-gapless">
<div class="column is-5-tablet is-cover">
<a
class="align to-b to-l"
href="{{ book.local_path }}"
>{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto-tablet' %}</a>
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
</div>
<div class="column mt-3-mobile ml-3-tablet">
<h3 class="title is-5">
<a href="/book/{{ book.id }}">{{ book.title }}</a>
</h3>
{% if book.authors %}
<p class="subtitle is-5">
{% trans "by" %}
{% include 'snippets/authors.html' %}
</p>
{% endif %}
{% if book|book_description %}
<blockquote class="content">
{{ book|book_description|to_markdown|safe|truncatewords_html:50 }}
</blockquote>
{% endif %}
</div>
</div>
{% endwith %}
{% endif %}

View file

@ -1,12 +1,24 @@
{% load bookwyrm_tags %}
{% load i18n %}
{% if book %}
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book %}</a>
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
{% with book=book %}
<a href="{{ book.local_path }}">
{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-h-l-tablet is-w-auto align to-b to-l' %}
</a>
<h3 class="title is-6"><a href="/book/{{ book.id }}">{{ book.title }}</a></h3>
{% if book.authors %}
<p class="subtitle is-6">{% trans "by" %} {% include 'snippets/authors.html' with book=book %}</p>
{% endif %}
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
<h3 class="title is-6">
<a href="/book/{{ book.id }}">{{ book.title }}</a>
</h3>
{% if book.authors %}
<p class="subtitle is-6">
{% trans "by" %}
{% include 'snippets/authors.html' %}
</p>
{% endif %}
{% endwith %}
{% endif %}

View file

@ -37,7 +37,7 @@
aria-label="{{ book.title }}"
aria-selected="{% if active_book == book.id|stringformat:'d' %}true{% elif shelf_counter == 1 and forloop.first %}true{% else %}false{% endif %}"
aria-controls="book-{{ book.id }}">
{% include 'snippets/book_cover.html' with book=book size="medium" %}
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m' %}
</a>
</li>
{% endfor %}

View file

@ -1,8 +1,8 @@
{% load i18n %}
<div class="column is-narrow is-clipped has-text-centered">
{% include 'snippets/book_cover.html' with book=book %}
<label class="label" for="id_shelve_{{ book.id }}">
<div class="select is-small">
<div class="column is-cover">
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-l is-h-m-mobile' %}
<div class="select is-small mt-1 mb-3">
<select name="{{ book.id }}" aria-label="{% blocktrans with book_title=book.title %}Have you read {{ book_title }}?{% endblocktrans %}">
<option disabled selected value>Add to your books</option>
{% for shelf in request.user.shelf_set.all %}

View file

@ -23,34 +23,43 @@
<form class="block" name="add-books" method="post" action="{% url 'get-started-books' %}">
{% csrf_token %}
<h3 class="title is-5">{% trans "Suggested Books" %}</h3>
<fieldset name="books" class="columns scroll-x is-mobile">
{% if book_results %}
<div class="column is-narrow content">
<p class="help mb-0">Search results</p>
<div class="columns is-mobile">
{% for book in book_results %}
{% include 'get_started/book_preview.html' %}
{% endfor %}
</div>
</div>
{% endif %}
{% if popular_books %}
<div class="column is-narrow content">
<p class="help mb-0">
{% blocktrans %}Popular on {{ site_name }}{% endblocktrans %}
</p>
<div class="columns is-mobile">
{% for book in popular_books %}
{% include 'get_started/book_preview.html' %}
{% endfor %}
</div>
</div>
{% endif %}
{% if not book_results and not popular_books %}
<p><em>{% trans "No books found" %}</em></p>
{% endif %}
</fieldset>
<div class="block scroll-x">
<fieldset name="books" class="columns is-mobile">
{% if book_results %}
<div class="column is-narrow">
<p class="help mb-0">Search results</p>
<div class="columns is-mobile">
{% for book in book_results %}
{% include 'get_started/book_preview.html' %}
{% endfor %}
</div>
</div>
{% endif %}
{% if popular_books %}
<div class="column is-narrow">
<p class="help mb-0">
{% blocktrans %}Popular on {{ site_name }}{% endblocktrans %}
</p>
<div class="columns is-mobile">
{% for book in popular_books %}
{% include 'get_started/book_preview.html' %}
{% endfor %}
</div>
</div>
{% endif %}
{% if not book_results and not popular_books %}
<p><em>{% trans "No books found" %}</em></p>
{% endif %}
</fieldset>
</div>
<button type="submit" class="button is-primary">{% trans "Save &amp; continue" %}</button>
</form>
{% endblock %}

View file

@ -45,21 +45,22 @@
</section>
{% if goal.books %}
<section class="content">
<h2>
<section>
<h2 class="title is-4">
{% if goal.user == request.user %}
{% blocktrans %}Your {{ year }} Books{% endblocktrans %}
{% else %}
{% blocktrans with username=goal.user.display_name %}{{ username }}'s {{ year }} Books{% endblocktrans %}
{% endif %}
</h2>
<div class="columns is-multiline">
<div class="columns is-mobile is-multiline">
{% for book in goal.books %}
<div class="column is-one-fifth">
<div class="is-clipped">
<a href="{{ book.book.local_path }}">{% include 'snippets/book_cover.html' with book=book.book %}</a>
<div class="column is-cover">
<a href="{{ book.book.local_path }}">
{% include 'snippets/book_cover.html' with book=book.book cover_class='is-h-xl is-h-l-mobile' %}
</a>
</div>
</div>
{% endfor %}
</div>
</section>

View file

@ -125,7 +125,7 @@
<td>
{% if item.book %}
<a href="/book/{{ item.book.id }}">
{% include 'snippets/book_cover.html' with book=item.book size='small' %}
{% include 'snippets/book_cover.html' with book=item.book cover_class='is-h-s' %}
</a>
{% endif %}
</td>

View file

@ -2,49 +2,71 @@
{% load i18n %}
{% block panel %}
<section class="content block">
<h2>{% trans "Pending Books" %}</h2>
<p><a href="{% url 'list' list.id %}">{% trans "Go to list" %}</a></p>
<section class="block">
<div class="columns is-mobile is-multiline is-align-items-baseline">
<div class="column is-narrow">
<h2 class="title is-4">{% trans "Pending Books" %}</h2>
</div>
<p class="column is-narrow"><a href="{% url 'list' list.id %}">{% trans "Go to list" %}</a></p>
</div>
{% if not pending.exists %}
<p>{% trans "You're all set!" %}</p>
<p>{% trans "You're all set!" %}</p>
{% else %}
<table class="table is-striped">
<tr>
<th></th>
<th>{% trans "Book" %}</th>
<th>{% trans "Suggested by" %}</th>
<th></th>
</tr>
<dl>
{% for item in pending %}
<tr>
<td>
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=item.book size="small" %}</a>
</td>
<td>
{% include 'snippets/book_titleby.html' with book=item.book %}
</td>
<td>
<a href="{{ item.user.local_path }}">{{ item.user.display_name }}</a>
</td>
<td>
<div class="field has-addons">
<form class="control" method="POST" action="{% url 'list-curate' list.id %}">
{% csrf_token %}
<input type="hidden" name="item" value="{{ item.id }}">
<input type="hidden" name="approved" value="true">
<button class="button">{% trans "Approve" %}</button>
</form>
<form class="control" method="POST" action="{% url 'list-curate' list.id %}">
{% csrf_token %}
<input type="hidden" name="item" value="{{ item.id }}">
<input type="hidden" name="approved" value="false">
<button class="button is-danger is-light">{% trans "Discard" %}</button>
</div>
</form>
</td>
</tr>
{% with book=item.book %}
<div
class="
columns is-gapless
is-vcentered is-justify-content-space-between
mb-6
"
>
<dt class="column mr-auto">
<div class="columns is-mobile is-gapless is-vcentered">
<a
class="column is-cover"
href="{{ book.local_path }}"
aria-hidden="true"
>
{% include 'snippets/book_cover.html' with cover_class='is-w-xs-mobile is-w-s is-h-xs-mobile is-h-s' %}
</a>
<div class="column ml-3">
{% include 'snippets/book_titleby.html' %}
</div>
</div>
</dt>
<dd class="column is-4-tablet mx-3-tablet my-3-mobile">
{% trans "Suggested by" %}
<a href="{{ item.user.local_path }}">
{{ item.user.display_name }}
</a>
</dd>
<dd class="column is-narrow field has-addons">
<form class="control" method="POST" action="{% url 'list-curate' list.id %}">
{% csrf_token %}
<input type="hidden" name="item" value="{{ item.id }}">
<input type="hidden" name="approved" value="true">
<button class="button">{% trans "Approve" %}</button>
</form>
<form class="control" method="POST" action="{% url 'list-curate' list.id %}">
{% csrf_token %}
<input type="hidden" name="item" value="{{ item.id }}">
<input type="hidden" name="approved" value="false">
<button class="button is-danger is-light">{% trans "Discard" %}</button>
</form>
</dd>
</div>
{% endwith %}
{% endfor %}
</table>
</dl>
{% endif %}
</section>
{% endblock %}

View file

@ -28,18 +28,29 @@
{% else %}
<ol start="{{ items.start_index }}">
{% for item in items %}
<li class="block pb-3">
<li class="block mb-5">
<div class="card">
<div class="card-content columns p-0 pr-2 mb-0 is-mobile">
<div class="column is-narrow pt-0 pb-0">
<a href="{{ item.book.local_path }}">{% include 'snippets/book_cover.html' with book=item.book size="medium" %}</a>
{% with book=item.book %}
<div
class="
card-content p-0 mb-0
columns is-mobile is-gapless
"
>
<div class="column is-2-mobile is-cover align to-t">
<a href="{{ item.book.local_path }}" aria-hidden="true">
{% include 'snippets/book_cover.html' with cover_class='is-w-auto is-h-m-tablet' %}
</a>
</div>
<div class="column ml-3">
<span>{% include 'snippets/book_titleby.html' %}</span>
{% include 'snippets/stars.html' with rating=item.book|rating:request.user %}
{% include 'snippets/shelve_button/shelve_button.html' %}
</div>
</div>
<div class="column is-flex-direction-column is-align-items-self-start">
<span>{% include 'snippets/book_titleby.html' with book=item.book %}</span>
{% include 'snippets/stars.html' with rating=item.book|rating:request.user %}
{% include 'snippets/shelve_button/shelve_button.html' with book=item.book %}
</div>
</div>
{% endwith %}
<div class="card-footer has-background-white-bis is-align-items-baseline">
<div class="card-footer-item">
<div>
@ -76,7 +87,7 @@
{% include "snippets/pagination.html" with page=items %}
</section>
<section class="column is-one-quarter content">
<section class="column is-one-quarter">
<h2>{% trans "Sort List" %}</h2>
<form name="sort" action="{% url 'list' list.id %}" method="GET" class="block">
<label class="label" for="id_sort_by">{% trans "Sort By" %}</label>
@ -118,24 +129,36 @@
<p>{% trans "No books found" %}</p>
{% endif %}
{% endif %}
{% for book in suggested_books %}
{% if book %}
<div class="block columns is-mobile">
<div class="column is-narrow">
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book size="small" %}</a>
</div>
<div class="column">
<p>{% include 'snippets/book_titleby.html' with book=book %}</p>
<form name="add-book" method="post" action="{% url 'list-add-book' %}{% if query %}?q={{ query }}{% endif %}">
{% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="list" value="{{ list.id }}">
<button type="submit" class="button is-small is-link">{% if list.curation == 'open' or request.user == list.user %}{% trans "Add" %}{% else %}{% trans "Suggest" %}{% endif %}</button>
</form>
</div>
</div>
{% if suggested_books|length > 0 %}
{% for book in suggested_books %}
<div class="columns is-mobile is-gapless">
<a
class="column is-2-mobile is-3-tablet is-cover align to-c"
href="{{ book.local_path }}"
aria-hidden="true"
>
{% include 'snippets/book_cover.html' with book=book cover_class='is-w-auto is-h-s-mobile align to-t' %}
</a>
<div class="column ml-3">
<p>{% include 'snippets/book_titleby.html' with book=book %}</p>
<form
class="mt-1"
name="add-book"
method="post"
action="{% url 'list-add-book' %}{% if query %}?q={{ query }}{% endif %}"
>
{% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="list" value="{{ list.id }}">
<button type="submit" class="button is-small is-link">{% if list.curation == 'open' or request.user == list.user %}{% trans "Add" %}{% else %}{% trans "Suggest" %}{% endif %}</button>
</form>
</div>
</div>
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
</section>
</div>

View file

@ -8,11 +8,19 @@
<a href="{{ list.local_path }}">{{ list.name }}</a> <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span>
</h4>
</header>
<div class="card-image is-flex is-clipped">
{% for book in list.listitem_set.all|slice:5 %}
<a href="{{ book.book.local_path }}">{% include 'snippets/book_cover.html' with book=book.book size="small" %}</a>
{% endfor %}
</div>
{% with list_books=list.listitem_set.all|slice:5 %}
{% if list_books %}
<div class="card-image columns is-mobile is-gapless is-clipped">
{% for book in list_books %}
<a class="column is-cover" href="{{ book.book.local_path }}">
{% include 'snippets/book_cover.html' with book=book.book cover_class='is-h-s' aria='show' %}
</a>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="card-content is-flex-grow-0">
<div {% if list.description %}title="{{ list.description }}"{% endif %}>
{% if list.description %}

View file

@ -28,7 +28,7 @@
</div>
{% if lists %}
<section class="block content">
<section class="block">
{% include 'lists/list_items.html' with lists=lists %}
</section>

View file

@ -8,7 +8,7 @@
{% endblock %}
{% block panel %}
<form name="edit-profile" action="/change-password/" method="post" enctype="multipart/form-data">
<form name="edit-profile" action="{% url 'prefs-password' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="block">
<label class="label" for="id_password">{% trans "New password:" %}</label>

View file

@ -11,10 +11,15 @@
<div class="block columns">
<div class="column">
<h2 class="title">{% trans "Matching Books" %}</h2>
<h2 class="title is-4">{% trans "Matching Books" %}</h2>
<section class="block">
{% if not local_results.results %}
<p>{% blocktrans %}No books found for "{{ query }}"{% endblocktrans %}</p>
<p><em>{% blocktrans %}No books found for "{{ query }}"{% endblocktrans %}</em></p>
{% if not user.is_authenticated %}
<p>
<a href="{% url 'login' %}">{% trans "Log in to import or add books." %}</a>
</p>
{% endif %}
{% else %}
<ul>
{% for result in local_results.results %}
@ -29,39 +34,57 @@
{% if request.user.is_authenticated %}
{% if book_results|slice:":1" and local_results.results %}
<div class="block">
<p>
<h3 class="title is-6">
{% trans "Didn't find what you were looking for?" %}
</p>
</h3>
{% 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 %}
</div>
{% endif %}
<div class="{% if local_results.results %}is-hidden{% endif %}" id="more-results">
{% for result_set in book_results|slice:"1:" %}
{% if result_set.results %}
<section class="block">
<section class="box has-background-white-bis">
{% if not result_set.connector.local %}
<h3 class="title is-5">
Results from <a href="{{ result_set.connector.base_url }}" target="_blank">{% if result_set.connector.name %}{{ result_set.connector.name }}{% else %}{{ result_set.connector.identifier }}{% endif %}</a>
</h3>
<header class="columns is-mobile">
<div class="column">
<h3 class="title is-5">
Results from
<a href="{{ result_set.connector.base_url }}" target="_blank">{{ result_set.connector.name|default:result_set.connector.identifier }}</a>
</h3>
</div>
<div class="column is-narrow">
{% 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 %}
</div>
</header>
{% endif %}
<ul>
{% for result in result_set.results %}
<li class="pb-4">
{% include 'snippets/search_result_text.html' with result=result remote_result=True %}
</li>
{% endfor %}
</ul>
<div class="box has-background-white is-shadowless{% if not forloop.first %} is-hidden{% endif %}" id="more-results-panel-{{ result_set.connector.identifier }}">
<div class="is-flex is-flex-direction-row-reverse">
<div>
{% 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 %}
</div>
<ul class="is-flex-grow-1">
{% for result in result_set.results %}
<li class="mb-5">
{% include 'snippets/search_result_text.html' with result=result remote_result=True %}
</li>
{% endfor %}
</ul>
</div>
</div>
</section>
{% 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 %}
</div>
<div class="block">
@ -70,10 +93,11 @@
{% endif %}
</div>
<div class="column">
<section class="block">
<h2 class="title">{% trans "Matching Users" %}</h2>
{% if request.user.is_authenticated %}
<section class="box">
<h2 class="title is-4">{% trans "Matching Users" %}</h2>
{% if not user_results %}
<p>{% blocktrans %}No users found for "{{ query }}"{% endblocktrans %}</p>
<p><em>{% blocktrans %}No users found for "{{ query }}"{% endblocktrans %}</em></p>
{% endif %}
<ul>
{% for result in user_results %}
@ -87,10 +111,11 @@
{% endfor %}
</ul>
</section>
<section class="block">
<h2 class="title">{% trans "Lists" %}</h2>
{% endif %}
<section class="box">
<h2 class="title is-4">{% trans "Lists" %}</h2>
{% if not list_results %}
<p>{% blocktrans %}No lists found for "{{ query }}"{% endblocktrans %}</p>
<p><em>{% blocktrans %}No lists found for "{{ query }}"{% endblocktrans %}</em></p>
{% endif %}
{% for result in list_results %}
<div class="block">

View file

@ -3,27 +3,44 @@
{% load bookwyrm_tags %}
{% load i18n %}
<div class="cover-container is-{{ size }}">
{% if book.cover %}
<img
class="book-cover"
src="/images/{{ book.cover }}"
alt="{{ book.alt_text }}"
title="{{ book.alt_text }}"
itemprop="thumbnailUrl"
>
{% else %}
<div class="no-cover book-cover">
<img
class="book-cover"
src="/static/images/no_cover.jpg"
alt="{% trans "No cover" %}"
>
<figure
class="
cover-container
{{ cover_class }}
<div>
<p>{{ book.alt_text }}</p>
</div>
</div>
{% if not book.cover %}
no-cover
{% endif %}
"
{% if aria != "show" %}
aria-hidden="true"
{% endif %}
</div>
{% if book.alt_text %}
title="{{ book.alt_text }}"
{% endif %}
>
<img
class="book-cover"
{% if book.cover %}
src="{% if img_path is None %}/images/{% else %}{{ img_path }}{% endif %}{{ book.cover }}"
itemprop="thumbnailUrl"
{% if book.alt_text %}
alt="{{ book.alt_text }}"
{% endif %}
{% else %}
src="/static/images/no_cover.jpg"
alt="{% trans "No cover" %}"
{% endif %}
>
{% if not book.cover and book.alt_text %}
<figcaption class="cover_caption">
<p>{{ book.alt_text }}</p>
</figcaption>
{% endif %}
</figure>
{% endspaceless %}

View file

@ -1,10 +1,13 @@
<div class="columns is-multiline">
<div class="columns is-mobile is-multiline">
{% for book in books %}
<div class="column is-narrow">
<div class="box">
<a href="/book/{{ book.id }}">
{% include 'snippets/book_cover.html' with book=book %}
</a>
<div class="box is-flex is-flex-direction-column is-align-items-center">
<div class="mb-3">
<a href="/book/{{ book.id }}">
{% include 'snippets/book_cover.html' with book=book cover_class='is-w-l-mobile is-h-l-mobile is-w-l-tablet is-h-xl-tablet' %}
</a>
</div>
{% include 'snippets/shelve_button/shelve_button.html' with book=book switch_mode=True %}
</div>
</div>

View file

@ -1,34 +1,41 @@
{% load i18n %}
<div class="columns is-mobile">
<div class="cover-container is-small column is-2">
{% if result.cover %}
<img src="{{ result.cover }}" class="book-cover" aria-hidden="true">
{% else %}
<div class="no-cover book-cover">
<img class="book-cover" src="/static/images/no_cover.jpg" aria-hidden="true">
<div>
<p>{% trans "No cover" %}</p>
</div>
</div>
{% endif %}
<div class="columns is-mobile is-gapless">
<div class="column is-cover">
{% include 'snippets/book_cover.html' with book=result cover_class='is-w-xs is-h-xs' img_path=false %}
</div>
<div class="column">
<div class="column is-10 ml-3">
<p>
<strong>
<a href="{{ result.key }}"{% if remote_result %} rel=”noopener” target="_blank"{% endif %}>{{ result.title }}</a>
<a
href="{{ result.view_link|default:result.key }}"
{% if remote_result %}
rel=”noopener”
target="_blank"
{% endif %}
>{{ result.title }}</a>
</strong>
</p>
<p>
{% if result.author %}
{% blocktrans with author=result.author %}by {{ author }}{% endblocktrans %}{% endif %}{% if result.year %} ({{ result.year }})
{{ result.author }}
{% endif %}
{% if result.year %}
({{ result.year }})
{% endif %}
</p>
{% if remote_result %}
<form action="/resolve-book" method="POST">
{% csrf_token %}
<input type="hidden" name="remote_id" value="{{ result.key }}">
<button type="submit" class="button is-small is-link">{% trans "Import book" %}</button>
</form>
<form class="mt-1" action="/resolve-book" method="post">
{% csrf_token %}
<input type="hidden" name="remote_id" value="{{ result.key }}">
<button type="submit" class="button is-small is-link">
{% trans "Import book" %}
</button>
</form>
{% endif %}
</div>
</div>

View file

@ -10,27 +10,30 @@
{% endif %}
>
<div class="columns">
<div class="columns is-gapless">
{% if not hide_book %}
{% with book=status.book|default:status.mention_books.first %}
{% if book %}
<div class="column is-narrow">
<div class="columns is-mobile">
<div class="column is-narrow">
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book %}</a>
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
{% include 'snippets/shelve_button/shelve_button.html' with book=book %}
</div>
<div class="column is-hidden-tablet">
<p>{{ book|book_description|to_markdown|default:""|safe|truncatewords_html:15 }}</p>
</div>
</div>
</div>
{% endif %}
{% endwith %}
{% with book=status.book|default:status.mention_books.first %}
{% if book %}
<div class="column is-cover">
<div class="columns is-mobile is-gapless">
<div class="column is-cover">
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book cover_class='is-w-s-mobile is-h-l-tablet' %}</a>
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
{% include 'snippets/shelve_button/shelve_button.html' with book=book %}
</div>
<div class="column ml-3-mobile is-hidden-tablet">
<p>{{ book|book_description|to_markdown|default:""|safe|truncatewords_html:15 }}</p>
</div>
</div>
</div>
{% endif %}
{% endwith %}
{% endif %}
<article class="column">
<article class="column ml-3-tablet my-3-mobile">
{% if status_type == 'Review' %}
<header class="mb-2">
<h3

View file

@ -4,20 +4,25 @@
{% load i18n %}
{% if not hide_book %}
{% with book=status.book|default:status.mention_books.first %}
<div class="columns is-mobile">
<div class="column is-narrow">
<div>
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book size="small" %}</a>
{% with book=status.book|default:status.mention_books.first %}
<div class="columns is-mobile is-gapless">
<a class="column is-cover is-narrow" href="{{ book.local_path }}">
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-xs is-h-s-tablet' %}
</a>
<div class="column ml-3">
<h3 class="title is-6 mb-1">
{% include 'snippets/book_titleby.html' with book=book %}
</h3>
<p>
{{ book|book_description|to_markdown|default:""|safe|truncatewords_html:20 }}
</p>
{% include 'snippets/shelve_button/shelve_button.html' with book=book %}
</div>
</div>
</div>
<div class="column">
<h3 class="title is-6 mb-1">{% include 'snippets/book_titleby.html' with book=book %}</h3>
<p>{{ book|book_description|to_markdown|default:""|safe|truncatewords_html:20 }}</p>
{% include 'snippets/shelve_button/shelve_button.html' with book=book %}
</div>
</div>
{% endwith %}
{% endwith %}
{% endif %}
{% endspaceless %}

View file

@ -88,7 +88,7 @@
{% spaceless %}
<tr class="book-preview">
<td class="book-preview-top-row">
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book size="small" %}</a>
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book cover_class='is-w-s-tablet is-h-s' %}</a>
</td>
<td data-title="{% trans "Title" %}">
<a href="{{ book.local_path }}">{{ book.title }}</a>

View file

@ -36,7 +36,7 @@
{% for book in shelf.books %}
<div class="control">
<a href="{{ book.local_path }}">
{% include 'snippets/book_cover.html' with book=book size="medium" %}
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m is-h-s-mobile' %}
</a>
</div>
{% endfor %}

View file

@ -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",

View file

@ -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",
)

View file

@ -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")

View file

@ -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": {}
}

View file

@ -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": {}
}

View file

@ -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
}
]
}

View file

@ -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": {}
}

View file

@ -26,20 +26,23 @@ class BaseModel(TestCase):
outbox="https://example.com/users/rat/outbox",
)
class BookWyrmTestModel(base_model.BookWyrmModel):
"""just making it not abstract"""
self.test_model = BookWyrmTestModel()
def test_remote_id(self):
"""these should be generated"""
instance = base_model.BookWyrmModel()
instance.id = 1
expected = instance.get_remote_id()
self.assertEqual(expected, "https://%s/bookwyrmmodel/1" % DOMAIN)
self.test_model.id = 1
expected = self.test_model.get_remote_id()
self.assertEqual(expected, "https://%s/bookwyrmtestmodel/1" % DOMAIN)
def test_remote_id_with_user(self):
"""format of remote id when there's a user object"""
instance = base_model.BookWyrmModel()
instance.user = self.local_user
instance.id = 1
expected = instance.get_remote_id()
self.assertEqual(expected, "https://%s/user/mouse/bookwyrmmodel/1" % DOMAIN)
self.test_model.user = self.local_user
self.test_model.id = 1
expected = self.test_model.get_remote_id()
self.assertEqual(expected, "https://%s/user/mouse/bookwyrmtestmodel/1" % DOMAIN)
def test_set_remote_id(self):
"""this function sets remote ids after creation"""

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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"):

View file

@ -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"

View file

@ -43,7 +43,7 @@ urlpatterns = [
re_path("^api/updates/notifications/?$", views.get_notification_count),
re_path("^api/updates/stream/(?P<stream>[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()),
@ -224,7 +224,11 @@ urlpatterns = [
re_path(r"^hide-goal/?$", views.hide_goal, name="hide-goal"),
# preferences
re_path(r"^preferences/profile/?$", views.EditUser.as_view(), name="prefs-profile"),
re_path(r"^preferences/password/?$", views.ChangePassword.as_view()),
re_path(
r"^preferences/password/?$",
views.ChangePassword.as_view(),
name="prefs-password",
),
re_path(r"^preferences/block/?$", views.Block.as_view()),
re_path(r"^block/(?P<user_id>\d+)/?$", views.Block.as_view()),
re_path(r"^unblock/(?P<user_id>\d+)/?$", views.unblock),

View file

@ -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)

View file

@ -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:

View file

@ -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,
),

View file

@ -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)

Binary file not shown.

View file

@ -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 <mousereeve@riseup.net>\n"
"Language-Team: English <LL@li.org>\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 "<a href=\"%(path)s\">%(title)s</a> by "
msgid "<a href=\"%(path)s/editions\">%(count)s editions</a>"
msgstr "<a href=\"%(path)s\">%(title)s</a> von"
#: bookwyrm/templates/book/book.html:143
#: bookwyrm/templates/book/book.html:146
#, fuzzy, python-format
#| msgid "Direct Messages with <a href=\"%(path)s\">%(username)s</a>"
msgid "This edition is on your <a href=\"%(path)s\">%(shelf_name)s</a> shelf."
msgstr "Direktnachrichten mit <a href=\"%(path)s\">%(username)s</a>"
#: bookwyrm/templates/book/book.html:149
#: bookwyrm/templates/book/book.html:152
#, fuzzy, python-format
#| msgid ""
#| " added <em><a href=\"%(book_path)s\">%(book_title)s</a></em> to your list "
@ -250,74 +256,74 @@ msgstr ""
"hat <em><a href=\"%(book_path)s\">%(book_title)s</a></em> zu deiner Liste "
"\"<a href=\"%(list_path)s\">%(list_name)s</a>\" 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"

View file

@ -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 <mousereeve@riseup.net>\n"
"Language-Team: English <LL@li.org>\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 "<a href=\"%(path)s/editions\">%(count)s editions</a>"
msgstr ""
#: bookwyrm/templates/book/book.html:143
#: bookwyrm/templates/book/book.html:146
#, python-format
msgid "This edition is on your <a href=\"%(path)s\">%(shelf_name)s</a> shelf."
msgstr ""
#: bookwyrm/templates/book/book.html:149
#: bookwyrm/templates/book/book.html:152
#, python-format
msgid ""
"A <a href=\"%(book_path)s\">different edition</a> of this book is on your <a "
"href=\"%(shelf_path)s\">%(shelf_name)s</a> 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 ""

Binary file not shown.

View file

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 "<a href=\"%(path)s/editions\">%(count)s editions</a>"
msgstr "<a href=\"%(path)s/editions\">%(count)s ediciones</a>"
#: bookwyrm/templates/book/book.html:143
#: bookwyrm/templates/book/book.html:146
#, python-format
msgid "This edition is on your <a href=\"%(path)s\">%(shelf_name)s</a> shelf."
msgstr ""
"Esta edición está en tu <a href=\"%(path)s\">%(shelf_name)s</a> estante."
#: bookwyrm/templates/book/book.html:149
#: bookwyrm/templates/book/book.html:152
#, python-format
msgid ""
"A <a href=\"%(book_path)s\">different edition</a> of this book is on your <a "
@ -234,72 +240,72 @@ msgstr ""
"Una <a href=\"%(book_path)s\">edición diferente</a> de este libro está en tu "
"<a href=\"%(shelf_path)s\">%(shelf_name)s</a> 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"

Binary file not shown.

View file

@ -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 <contact@arkhi.org>\n"
"Language-Team: Mouse Reeve <LL@li.org>\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 na 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 "<a href=\"%(path)s/editions\">%(count)s editions</a>"
msgstr "<a href=\"%(path)s/editions\">%(count)s éditions</a>"
#: bookwyrm/templates/book/book.html:143
#: bookwyrm/templates/book/book.html:146
#, python-format
msgid "This edition is on your <a href=\"%(path)s\">%(shelf_name)s</a> shelf."
msgstr ""
"Cette édition est sur votre étagère <a href=\"%(path)s\">%(shelf_name)s</a>."
#: bookwyrm/templates/book/book.html:149
#: bookwyrm/templates/book/book.html:152
#, python-format
msgid ""
"A <a href=\"%(book_path)s\">different edition</a> of this book is on your <a "
@ -240,72 +246,72 @@ msgstr ""
"Une <a href=\"%(book_path)s\">édition différente</a> de ce livre existe sur "
"votre étagère <a href=\"%(shelf_path)s\">%(shelf_name)s</a>."
#: 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 navez 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 "la 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 navez 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 dautres catalogues"
#: bookwyrm/templates/search_results.html:62
#: bookwyrm/templates/search_results.html:44
msgid "Hide results from other catalogues"
msgstr "Masquer les résultats dautres 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"

Binary file not shown.

View file

@ -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 <gudzpoz@live.com>\n"
"Language-Team: Mouse Reeve <LL@li.org>\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 "<a href=\"%(path)s/editions\">%(count)s editions</a>"
msgstr "<a href=\"%(path)s/editions\">%(count)s 个版本</a>"
#: bookwyrm/templates/book/book.html:143
#: bookwyrm/templates/book/book.html:146
#, python-format
msgid "This edition is on your <a href=\"%(path)s\">%(shelf_name)s</a> shelf."
msgstr "此版本在你的 <a href=\"%(path)s\">%(shelf_name)s</a> 书架上。"
#: bookwyrm/templates/book/book.html:149
#: bookwyrm/templates/book/book.html:152
#, python-format
msgid ""
"A <a href=\"%(book_path)s\">different edition</a> of this book is on your <a "
@ -238,72 +244,72 @@ msgstr ""
"本书的 <a href=\"%(book_path)s\">另一个版本</a> 在你的 <a href="
"\"%(shelf_path)s\">%(shelf_name)s</a> 书架上。"
#: 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 "停用用户"

View file

@ -1,5 +1,5 @@
celery==4.4.2
Django==3.1.8
Django==3.2.0
django-model-utils==4.0.0
environs==7.2.0
flower==0.9.4