mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-12-04 23:36:32 +00:00
Merge branch 'main' into production
This commit is contained in:
commit
fd0e4c6e13
22 changed files with 188 additions and 130 deletions
|
@ -201,6 +201,19 @@ def add_status_on_create(sender, instance, created, *args, **kwargs):
|
||||||
for stream in streams.values():
|
for stream in streams.values():
|
||||||
stream.add_status(instance)
|
stream.add_status(instance)
|
||||||
|
|
||||||
|
if sender != models.Boost:
|
||||||
|
return
|
||||||
|
# remove the original post and other, earlier boosts
|
||||||
|
boosted = instance.boost.boosted_status
|
||||||
|
old_versions = models.Boost.objects.filter(
|
||||||
|
boosted_status__id=boosted.id,
|
||||||
|
created_date__lt=instance.created_date,
|
||||||
|
)
|
||||||
|
for stream in streams.values():
|
||||||
|
stream.remove_object_from_related_stores(boosted)
|
||||||
|
for status in old_versions:
|
||||||
|
stream.remove_object_from_related_stores(status)
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.post_delete, sender=models.Boost)
|
@receiver(signals.post_delete, sender=models.Boost)
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
@ -208,7 +221,10 @@ def remove_boost_on_delete(sender, instance, *args, **kwargs):
|
||||||
"""boosts are deleted"""
|
"""boosts are deleted"""
|
||||||
# we're only interested in new statuses
|
# we're only interested in new statuses
|
||||||
for stream in streams.values():
|
for stream in streams.values():
|
||||||
|
# remove the boost
|
||||||
stream.remove_object_from_related_stores(instance)
|
stream.remove_object_from_related_stores(instance)
|
||||||
|
# re-add the original status
|
||||||
|
stream.add_status(instance.boosted_status)
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.post_save, sender=models.UserFollows)
|
@receiver(signals.post_save, sender=models.UserFollows)
|
||||||
|
|
|
@ -37,7 +37,7 @@ class AbstractMinimalConnector(ABC):
|
||||||
for field in self_fields:
|
for field in self_fields:
|
||||||
setattr(self, field, getattr(info, field))
|
setattr(self, field, getattr(info, field))
|
||||||
|
|
||||||
def search(self, query, min_confidence=None):
|
def search(self, query, min_confidence=None, timeout=5):
|
||||||
"""free text search"""
|
"""free text search"""
|
||||||
params = {}
|
params = {}
|
||||||
if min_confidence:
|
if min_confidence:
|
||||||
|
@ -46,6 +46,7 @@ class AbstractMinimalConnector(ABC):
|
||||||
data = self.get_search_data(
|
data = self.get_search_data(
|
||||||
"%s%s" % (self.search_url, query),
|
"%s%s" % (self.search_url, query),
|
||||||
params=params,
|
params=params,
|
||||||
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
|
@ -218,7 +219,7 @@ def dict_from_mappings(data, mappings):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_data(url, params=None):
|
def get_data(url, params=None, timeout=10):
|
||||||
"""wrapper for request.get"""
|
"""wrapper for request.get"""
|
||||||
# check if the url is blocked
|
# check if the url is blocked
|
||||||
if models.FederatedServer.is_blocked(url):
|
if models.FederatedServer.is_blocked(url):
|
||||||
|
@ -234,6 +235,7 @@ def get_data(url, params=None):
|
||||||
"Accept": "application/json; charset=utf-8",
|
"Accept": "application/json; charset=utf-8",
|
||||||
"User-Agent": settings.USER_AGENT,
|
"User-Agent": settings.USER_AGENT,
|
||||||
},
|
},
|
||||||
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
except (RequestError, SSLError, ConnectionError) as e:
|
except (RequestError, SSLError, ConnectionError) as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
@ -250,7 +252,7 @@ def get_data(url, params=None):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_image(url):
|
def get_image(url, timeout=10):
|
||||||
"""wrapper for requesting an image"""
|
"""wrapper for requesting an image"""
|
||||||
try:
|
try:
|
||||||
resp = requests.get(
|
resp = requests.get(
|
||||||
|
@ -258,6 +260,7 @@ def get_image(url):
|
||||||
headers={
|
headers={
|
||||||
"User-Agent": settings.USER_AGENT,
|
"User-Agent": settings.USER_AGENT,
|
||||||
},
|
},
|
||||||
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
except (RequestError, SSLError) as e:
|
except (RequestError, SSLError) as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
""" interface with whatever connectors the app has """
|
""" interface with whatever connectors the app has """
|
||||||
|
from datetime import datetime
|
||||||
import importlib
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
@ -29,9 +30,11 @@ def search(query, min_confidence=0.1, return_first=False):
|
||||||
isbn = re.sub(r"[\W_]", "", query)
|
isbn = re.sub(r"[\W_]", "", query)
|
||||||
maybe_isbn = len(isbn) in [10, 13] # ISBN10 or ISBN13
|
maybe_isbn = len(isbn) in [10, 13] # ISBN10 or ISBN13
|
||||||
|
|
||||||
|
timeout = 15
|
||||||
|
start_time = datetime.now()
|
||||||
for connector in get_connectors():
|
for connector in get_connectors():
|
||||||
result_set = None
|
result_set = None
|
||||||
if maybe_isbn and connector.isbn_search_url and connector.isbn_search_url == "":
|
if maybe_isbn and connector.isbn_search_url and connector.isbn_search_url != "":
|
||||||
# Search on ISBN
|
# Search on ISBN
|
||||||
try:
|
try:
|
||||||
result_set = connector.isbn_search(isbn)
|
result_set = connector.isbn_search(isbn)
|
||||||
|
@ -59,6 +62,8 @@ def search(query, min_confidence=0.1, return_first=False):
|
||||||
"results": result_set,
|
"results": result_set,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
if (datetime.now() - start_time).seconds >= timeout:
|
||||||
|
break
|
||||||
|
|
||||||
if return_first:
|
if return_first:
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -3,7 +3,7 @@ from functools import reduce
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
from django.contrib.postgres.search import SearchRank, SearchVector
|
from django.contrib.postgres.search import SearchRank, SearchVector
|
||||||
from django.db.models import Count, OuterRef, Subquery, F, Q
|
from django.db.models import OuterRef, Subquery, F, Q
|
||||||
|
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
from .abstract_connector import AbstractConnector, SearchResult
|
from .abstract_connector import AbstractConnector, SearchResult
|
||||||
|
@ -122,6 +122,8 @@ def search_identifiers(query, *filters):
|
||||||
results = models.Edition.objects.filter(
|
results = models.Edition.objects.filter(
|
||||||
*filters, reduce(operator.or_, (Q(**f) for f in or_filters))
|
*filters, reduce(operator.or_, (Q(**f) for f in or_filters))
|
||||||
).distinct()
|
).distinct()
|
||||||
|
if results.count() <= 1:
|
||||||
|
return results
|
||||||
|
|
||||||
# when there are multiple editions of the same work, pick the default.
|
# when there are multiple editions of the same work, pick the default.
|
||||||
# it would be odd for this to happen.
|
# it would be odd for this to happen.
|
||||||
|
@ -146,19 +148,15 @@ def search_title_author(query, min_confidence, *filters):
|
||||||
)
|
)
|
||||||
|
|
||||||
results = (
|
results = (
|
||||||
models.Edition.objects.annotate(search=vector)
|
models.Edition.objects.annotate(rank=SearchRank(vector, query))
|
||||||
.annotate(rank=SearchRank(vector, query))
|
|
||||||
.filter(*filters, rank__gt=min_confidence)
|
.filter(*filters, rank__gt=min_confidence)
|
||||||
.order_by("-rank")
|
.order_by("-rank")
|
||||||
)
|
)
|
||||||
|
|
||||||
# when there are multiple editions of the same work, pick the closest
|
# when there are multiple editions of the same work, pick the closest
|
||||||
editions_of_work = (
|
editions_of_work = results.values("parent_work__id").values_list("parent_work__id")
|
||||||
results.values("parent_work")
|
|
||||||
.annotate(Count("parent_work"))
|
|
||||||
.values_list("parent_work")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# filter out multiple editions of the same work
|
||||||
for work_id in set(editions_of_work):
|
for work_id in set(editions_of_work):
|
||||||
editions = results.filter(parent_work=work_id)
|
editions = results.filter(parent_work=work_id)
|
||||||
default = editions.order_by("-edition_rank").first()
|
default = editions.order_by("-edition_rank").first()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
""" activitypub-aware django model fields """
|
""" activitypub-aware django model fields """
|
||||||
from dataclasses import MISSING
|
from dataclasses import MISSING
|
||||||
|
import imghdr
|
||||||
import re
|
import re
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
@ -9,7 +10,7 @@ from django.contrib.postgres.fields import ArrayField as DjangoArrayField
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.forms import ClearableFileInput, ImageField
|
from django.forms import ClearableFileInput, ImageField as DjangoImageField
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
|
@ -334,10 +335,14 @@ class TagField(ManyToManyField):
|
||||||
|
|
||||||
|
|
||||||
class ClearableFileInputWithWarning(ClearableFileInput):
|
class ClearableFileInputWithWarning(ClearableFileInput):
|
||||||
|
"""max file size warning"""
|
||||||
|
|
||||||
template_name = "widgets/clearable_file_input_with_warning.html"
|
template_name = "widgets/clearable_file_input_with_warning.html"
|
||||||
|
|
||||||
|
|
||||||
class CustomImageField(ImageField):
|
class CustomImageField(DjangoImageField):
|
||||||
|
"""overwrites image field for form"""
|
||||||
|
|
||||||
widget = ClearableFileInputWithWarning
|
widget = ClearableFileInputWithWarning
|
||||||
|
|
||||||
|
|
||||||
|
@ -400,11 +405,12 @@ class ImageField(ActivitypubFieldMixin, models.ImageField):
|
||||||
if not response:
|
if not response:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
image_name = str(uuid4()) + "." + url.split(".")[-1]
|
|
||||||
image_content = ContentFile(response.content)
|
image_content = ContentFile(response.content)
|
||||||
|
image_name = str(uuid4()) + "." + imghdr.what(None, image_content.read())
|
||||||
return [image_name, image_content]
|
return [image_name, image_content]
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
|
"""special case for forms"""
|
||||||
return super().formfield(
|
return super().formfield(
|
||||||
**{
|
**{
|
||||||
"form_class": CustomImageField,
|
"form_class": CustomImageField,
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<a href="{{ author.local_path }}/edit">
|
<a href="{{ author.local_path }}/edit">
|
||||||
<span class="icon icon-pencil" title="{% trans 'Edit Author' %}" aria-hidden="True"></span>
|
<span class="icon icon-pencil" title="{% trans 'Edit Author' %}" aria-hidden="True"></span>
|
||||||
<span>{% trans "Edit Author" %}</span>
|
<span class="is-hidden-mobile">{% trans "Edit Author" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -8,27 +8,36 @@
|
||||||
<div class="block" itemscope itemtype="https://schema.org/Book">
|
<div class="block" itemscope itemtype="https://schema.org/Book">
|
||||||
<div class="columns is-mobile">
|
<div class="columns is-mobile">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<h1 class="title">
|
<h1 class="title" itemprop="name">
|
||||||
<span itemprop="name">
|
{{ book.title }}
|
||||||
{{ book.title }}{% if book.subtitle %}:
|
|
||||||
<small>{{ book.subtitle }}</small>
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{% if book.series %}
|
|
||||||
<meta itemprop="isPartOf" content="{{ book.series }}">
|
|
||||||
<meta itemprop="volumeNumber" content="{{ book.series_number }}">
|
|
||||||
|
|
||||||
<small class="has-text-grey-dark">
|
|
||||||
({{ book.series }}{% if book.series_number %} #{{ book.series_number }}{% endif %})
|
|
||||||
</small>
|
|
||||||
<br>
|
|
||||||
{% endif %}
|
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
{% if book.subtitle or book.series %}
|
||||||
|
<p class="subtitle title is-5">
|
||||||
|
{% if book.subtitle %}
|
||||||
|
<meta
|
||||||
|
itemprop="alternativeHeadline"
|
||||||
|
content="{{ book.subtitle | escape }}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<span class="has-text-weight-bold">
|
||||||
|
{{ book.subtitle }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if book.series %}
|
||||||
|
<meta itemprop="isPartOf" content="{{ book.series | escape }}">
|
||||||
|
<meta itemprop="volumeNumber" content="{{ book.series_number }}">
|
||||||
|
|
||||||
|
({{ book.series }}{% if book.series_number %} #{{ book.series_number }}{% endif %})
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if book.authors %}
|
{% if book.authors %}
|
||||||
<h2 class="subtitle">
|
<div class="subtitle">
|
||||||
{% trans "by" %} {% include 'snippets/authors.html' with book=book %}
|
{% trans "by" %} {% include 'snippets/authors.html' with book=book %}
|
||||||
</h2>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -36,7 +45,7 @@
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<a href="{{ book.id }}/edit">
|
<a href="{{ book.id }}/edit">
|
||||||
<span class="icon icon-pencil" title="{% trans "Edit Book" %}" aria-hidden=True></span>
|
<span class="icon icon-pencil" title="{% trans "Edit Book" %}" aria-hidden=True></span>
|
||||||
<span>{% trans "Edit Book" %}</span>
|
<span class="is-hidden-mobile">{% trans "Edit Book" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -84,7 +93,7 @@
|
||||||
|
|
||||||
<div class="column is-three-fifths">
|
<div class="column is-three-fifths">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h3
|
<div
|
||||||
class="field is-grouped"
|
class="field is-grouped"
|
||||||
itemprop="aggregateRating"
|
itemprop="aggregateRating"
|
||||||
itemscope
|
itemscope
|
||||||
|
@ -102,7 +111,7 @@
|
||||||
{% plural %}
|
{% plural %}
|
||||||
({{ review_count }} reviews)
|
({{ review_count }} reviews)
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</h3>
|
</div>
|
||||||
|
|
||||||
{% with full=book|book_description itemprop='abstract' %}
|
{% with full=book|book_description itemprop='abstract' %}
|
||||||
{% include 'snippets/trimmed_text.html' %}
|
{% include 'snippets/trimmed_text.html' %}
|
||||||
|
@ -180,7 +189,7 @@
|
||||||
<p>{% trans "You don't have any reading activity for this book." %}</p>
|
<p>{% trans "You don't have any reading activity for this book." %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for readthrough in readthroughs %}
|
{% for readthrough in readthroughs %}
|
||||||
{% include 'snippets/readthrough.html' with readthrough=readthrough %}
|
{% include 'book/readthrough.html' with readthrough=readthrough %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
<hr aria-hidden="true">
|
<hr aria-hidden="true">
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
{% include 'snippets/shelve_button/shelve_button.html' with book=book switch_mode=True %}
|
{% include 'snippets/shelve_button/shelve_button.html' with book=book switch_mode=True right=True %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
{% load tz %}
|
{% load tz %}
|
||||||
<div class="content box is-shadowless has-background-white-bis">
|
<div class="content">
|
||||||
<div id="hide-edit-readthrough-{{ readthrough.id }}">
|
<div id="hide-edit-readthrough-{{ readthrough.id }}" class="box is-shadowless has-background-white-bis">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{% trans "Progress Updates:" %}
|
{% trans "Progress Updates:" %}
|
|
@ -20,8 +20,8 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button is-primary" type="submit">Join Directory</button>
|
<button class="button is-primary" type="submit">Join Directory</button>
|
||||||
<p class="help">
|
<p class="help">
|
||||||
{% url 'settings-profile' as path %}
|
{% url 'prefs-profile' as path %}
|
||||||
{% blocktrans %}You can opt-out at any time in your <a href="{{ path }}">profile settings.</a>{% endblocktrans %}
|
{% blocktrans with path=path %}You can opt-out at any time in your <a href="{{ path }}">profile settings.</a>{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
<span class="icon icon-warning"></span>
|
<span class="icon icon-warning"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column is-clipped">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<p>
|
<p>
|
||||||
{# DESCRIPTION #}
|
{# DESCRIPTION #}
|
||||||
|
@ -137,7 +137,7 @@
|
||||||
{# PREVIEW #}
|
{# PREVIEW #}
|
||||||
<div class="notification py-2 {% if notification.id in unread %}is-primary is-light{% else %}has-background-white{% if notification.notification_type == 'REPLY' or notification.notification_type == 'MENTION' %} has-text-black{% else %}-bis has-text-grey-dark{% endif %}{% endif %}">
|
<div class="notification py-2 {% if notification.id in unread %}is-primary is-light{% else %}has-background-white{% if notification.notification_type == 'REPLY' or notification.notification_type == 'MENTION' %} has-text-black{% else %}-bis has-text-grey-dark{% endif %}{% endif %}">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column is-clipped">
|
||||||
{% include 'snippets/status_preview.html' with status=related_status %}
|
{% include 'snippets/status_preview.html' with status=related_status %}
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow {% if notification.notification_type == 'REPLY' or notification.notification_type == 'MENTION' %}has-text-black{% else %}has-text-grey-dark{% endif %}">
|
<div class="column is-narrow {% if notification.notification_type == 'REPLY' or notification.notification_type == 'MENTION' %}has-text-black{% else %}has-text-grey-dark{% endif %}">
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
{% block edit-button %}
|
{% block edit-button %}
|
||||||
<a href="{% url 'settings-import-blocklist' %}">
|
<a href="{% url 'settings-import-blocklist' %}">
|
||||||
<span class="icon icon-plus" title="{% trans 'Add instance' %}" aria-hidden="True"></span>
|
<span class="icon icon-plus" title="{% trans 'Add instance' %}" aria-hidden="True"></span>
|
||||||
<span>{% trans "Add instance" %}</span>
|
<span class="is-hidden-mobile">{% trans "Add instance" %}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
class="is-sr-only"
|
class="is-sr-only"
|
||||||
type="radio"
|
type="radio"
|
||||||
name="rating"
|
name="rating"
|
||||||
value="0"
|
value=""
|
||||||
{% if default_rating == 0 or not default_rating %}checked{% endif %}
|
{% if default_rating == 0 or not default_rating %}checked{% endif %}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<a href="{% url 'prefs-profile' %}">
|
<a href="{% url 'prefs-profile' %}">
|
||||||
<span class="icon icon-pencil" title="Edit profile" aria-hidden="true"></span>
|
<span class="icon icon-pencil" title="Edit profile" aria-hidden="true"></span>
|
||||||
<span>{% trans "Edit profile" %}</span>
|
<span class="is-hidden-mobile">{% trans "Edit profile" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
<h2 class="title">
|
<h2 class="title">
|
||||||
{% include 'user/shelf/books_header.html' %}
|
{% include 'user/shelf/books_header.html' %}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="columns">
|
<div class="columns is-mobile scroll-x">
|
||||||
{% for shelf in shelves %}
|
{% for shelf in shelves %}
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<h3>{{ shelf.name }}
|
<h3>{{ shelf.name }}
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<a target="_blank" href="{{ user.local_path }}/rss">
|
<a target="_blank" href="{{ user.local_path }}/rss">
|
||||||
<span class="icon icon-rss" aria-hidden="true"></span>
|
<span class="icon icon-rss" aria-hidden="true"></span>
|
||||||
<span>{% trans "RSS feed" %}</span>
|
<span class="is-hidden-mobile">{% trans "RSS feed" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import os
|
import os
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from django import template
|
from django import template
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
@ -20,13 +21,16 @@ def get_user_identifier(user):
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name="book_title")
|
@register.filter(name="book_title")
|
||||||
def get_title(book):
|
def get_title(book, too_short=5):
|
||||||
"""display the subtitle if the title is short"""
|
"""display the subtitle if the title is short"""
|
||||||
if not book:
|
if not book:
|
||||||
return ""
|
return ""
|
||||||
title = book.title
|
title = book.title
|
||||||
if len(title) < 6 and book.subtitle:
|
if len(title) <= too_short and book.subtitle:
|
||||||
title = "{:s}: {:s}".format(title, book.subtitle)
|
title = _("%(title)s: %(subtitle)s") % {
|
||||||
|
"title": title,
|
||||||
|
"subtitle": book.subtitle,
|
||||||
|
}
|
||||||
return title
|
return title
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ from bookwyrm import activitypub, models, settings
|
||||||
# pylint: disable=too-many-public-methods
|
# pylint: disable=too-many-public-methods
|
||||||
@patch("bookwyrm.models.Status.broadcast")
|
@patch("bookwyrm.models.Status.broadcast")
|
||||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||||
|
@patch("bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores")
|
||||||
class Status(TestCase):
|
class Status(TestCase):
|
||||||
"""lotta types of statuses"""
|
"""lotta types of statuses"""
|
||||||
|
|
||||||
|
@ -384,7 +385,8 @@ class Status(TestCase):
|
||||||
user=self.local_user, notification_type="GLORB"
|
user=self.local_user, notification_type="GLORB"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_create_broadcast(self, _, broadcast_mock):
|
# pylint: disable=unused-argument
|
||||||
|
def test_create_broadcast(self, one, two, broadcast_mock, *_):
|
||||||
"""should send out two verions of a status on create"""
|
"""should send out two verions of a status on create"""
|
||||||
models.Comment.objects.create(
|
models.Comment.objects.create(
|
||||||
content="hi", user=self.local_user, book=self.book
|
content="hi", user=self.local_user, book=self.book
|
||||||
|
|
|
@ -16,6 +16,7 @@ from bookwyrm.templatetags import (
|
||||||
|
|
||||||
|
|
||||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||||
|
@patch("bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores")
|
||||||
class TemplateTags(TestCase):
|
class TemplateTags(TestCase):
|
||||||
"""lotta different things here"""
|
"""lotta different things here"""
|
||||||
|
|
||||||
|
@ -38,28 +39,28 @@ class TemplateTags(TestCase):
|
||||||
)
|
)
|
||||||
self.book = models.Edition.objects.create(title="Test Book")
|
self.book = models.Edition.objects.create(title="Test Book")
|
||||||
|
|
||||||
def test_get_user_rating(self, _):
|
def test_get_user_rating(self, *_):
|
||||||
"""get a user's most recent rating of a book"""
|
"""get a user's most recent rating of a book"""
|
||||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||||
models.Review.objects.create(user=self.user, book=self.book, rating=3)
|
models.Review.objects.create(user=self.user, book=self.book, rating=3)
|
||||||
self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 3)
|
self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 3)
|
||||||
|
|
||||||
def test_get_user_rating_doesnt_exist(self, _):
|
def test_get_user_rating_doesnt_exist(self, *_):
|
||||||
"""there is no rating available"""
|
"""there is no rating available"""
|
||||||
self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 0)
|
self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 0)
|
||||||
|
|
||||||
def test_get_user_identifer_local(self, _):
|
def test_get_user_identifer_local(self, *_):
|
||||||
"""fall back to the simplest uid available"""
|
"""fall back to the simplest uid available"""
|
||||||
self.assertNotEqual(self.user.username, self.user.localname)
|
self.assertNotEqual(self.user.username, self.user.localname)
|
||||||
self.assertEqual(utilities.get_user_identifier(self.user), "mouse")
|
self.assertEqual(utilities.get_user_identifier(self.user), "mouse")
|
||||||
|
|
||||||
def test_get_user_identifer_remote(self, _):
|
def test_get_user_identifer_remote(self, *_):
|
||||||
"""for a remote user, should be their full username"""
|
"""for a remote user, should be their full username"""
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
utilities.get_user_identifier(self.remote_user), "rat@example.com"
|
utilities.get_user_identifier(self.remote_user), "rat@example.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_replies(self, _):
|
def test_get_replies(self, *_):
|
||||||
"""direct replies to a status"""
|
"""direct replies to a status"""
|
||||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||||
parent = models.Review.objects.create(
|
parent = models.Review.objects.create(
|
||||||
|
@ -87,7 +88,7 @@ class TemplateTags(TestCase):
|
||||||
self.assertTrue(second_child in replies)
|
self.assertTrue(second_child in replies)
|
||||||
self.assertFalse(third_child in replies)
|
self.assertFalse(third_child in replies)
|
||||||
|
|
||||||
def test_get_parent(self, _):
|
def test_get_parent(self, *_):
|
||||||
"""get the reply parent of a status"""
|
"""get the reply parent of a status"""
|
||||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||||
parent = models.Review.objects.create(
|
parent = models.Review.objects.create(
|
||||||
|
@ -101,7 +102,7 @@ class TemplateTags(TestCase):
|
||||||
self.assertEqual(result, parent)
|
self.assertEqual(result, parent)
|
||||||
self.assertIsInstance(result, models.Review)
|
self.assertIsInstance(result, models.Review)
|
||||||
|
|
||||||
def test_get_user_liked(self, _):
|
def test_get_user_liked(self, *_):
|
||||||
"""did a user like a status"""
|
"""did a user like a status"""
|
||||||
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
||||||
|
|
||||||
|
@ -110,7 +111,7 @@ class TemplateTags(TestCase):
|
||||||
models.Favorite.objects.create(user=self.user, status=status)
|
models.Favorite.objects.create(user=self.user, status=status)
|
||||||
self.assertTrue(interaction.get_user_liked(self.user, status))
|
self.assertTrue(interaction.get_user_liked(self.user, status))
|
||||||
|
|
||||||
def test_get_user_boosted(self, _):
|
def test_get_user_boosted(self, *_):
|
||||||
"""did a user boost a status"""
|
"""did a user boost a status"""
|
||||||
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
||||||
|
|
||||||
|
@ -119,7 +120,7 @@ class TemplateTags(TestCase):
|
||||||
models.Boost.objects.create(user=self.user, boosted_status=status)
|
models.Boost.objects.create(user=self.user, boosted_status=status)
|
||||||
self.assertTrue(interaction.get_user_boosted(self.user, status))
|
self.assertTrue(interaction.get_user_boosted(self.user, status))
|
||||||
|
|
||||||
def test_get_boosted(self, _):
|
def test_get_boosted(self, *_):
|
||||||
"""load a boosted status"""
|
"""load a boosted status"""
|
||||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||||
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
||||||
|
@ -128,7 +129,7 @@ class TemplateTags(TestCase):
|
||||||
self.assertIsInstance(boosted, models.Review)
|
self.assertIsInstance(boosted, models.Review)
|
||||||
self.assertEqual(boosted, status)
|
self.assertEqual(boosted, status)
|
||||||
|
|
||||||
def test_get_book_description(self, _):
|
def test_get_book_description(self, *_):
|
||||||
"""grab it from the edition or the parent"""
|
"""grab it from the edition or the parent"""
|
||||||
work = models.Work.objects.create(title="Test Work")
|
work = models.Work.objects.create(title="Test Work")
|
||||||
self.book.parent_work = work
|
self.book.parent_work = work
|
||||||
|
@ -144,12 +145,12 @@ class TemplateTags(TestCase):
|
||||||
self.book.save()
|
self.book.save()
|
||||||
self.assertEqual(bookwyrm_tags.get_book_description(self.book), "hello")
|
self.assertEqual(bookwyrm_tags.get_book_description(self.book), "hello")
|
||||||
|
|
||||||
def test_get_uuid(self, _):
|
def test_get_uuid(self, *_):
|
||||||
"""uuid functionality"""
|
"""uuid functionality"""
|
||||||
uuid = utilities.get_uuid("hi")
|
uuid = utilities.get_uuid("hi")
|
||||||
self.assertTrue(re.match(r"hi[A-Za-z0-9\-]", uuid))
|
self.assertTrue(re.match(r"hi[A-Za-z0-9\-]", uuid))
|
||||||
|
|
||||||
def test_get_markdown(self, _):
|
def test_get_markdown(self, *_):
|
||||||
"""mardown format data"""
|
"""mardown format data"""
|
||||||
result = markdown.get_markdown("_hi_")
|
result = markdown.get_markdown("_hi_")
|
||||||
self.assertEqual(result, "<p><em>hi</em></p>")
|
self.assertEqual(result, "<p><em>hi</em></p>")
|
||||||
|
@ -157,13 +158,13 @@ class TemplateTags(TestCase):
|
||||||
result = markdown.get_markdown("<marquee>_hi_</marquee>")
|
result = markdown.get_markdown("<marquee>_hi_</marquee>")
|
||||||
self.assertEqual(result, "<p><em>hi</em></p>")
|
self.assertEqual(result, "<p><em>hi</em></p>")
|
||||||
|
|
||||||
def test_get_mentions(self, _):
|
def test_get_mentions(self, *_):
|
||||||
"""list of people mentioned"""
|
"""list of people mentioned"""
|
||||||
status = models.Status.objects.create(content="hi", user=self.remote_user)
|
status = models.Status.objects.create(content="hi", user=self.remote_user)
|
||||||
result = status_display.get_mentions(status, self.user)
|
result = status_display.get_mentions(status, self.user)
|
||||||
self.assertEqual(result, "@rat@example.com ")
|
self.assertEqual(result, "@rat@example.com ")
|
||||||
|
|
||||||
def test_related_status(self, _):
|
def test_related_status(self, *_):
|
||||||
"""gets the subclass model for a notification status"""
|
"""gets the subclass model for a notification status"""
|
||||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||||
status = models.Status.objects.create(content="hi", user=self.user)
|
status = models.Status.objects.create(content="hi", user=self.user)
|
||||||
|
|
|
@ -51,7 +51,8 @@ class InboxActivities(TestCase):
|
||||||
models.SiteSettings.objects.create()
|
models.SiteSettings.objects.create()
|
||||||
|
|
||||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||||
def test_boost(self, redis_mock):
|
@patch("bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores")
|
||||||
|
def test_boost(self, redis_mock, _):
|
||||||
"""boost a status"""
|
"""boost a status"""
|
||||||
self.assertEqual(models.Notification.objects.count(), 0)
|
self.assertEqual(models.Notification.objects.count(), 0)
|
||||||
activity = {
|
activity = {
|
||||||
|
@ -81,7 +82,8 @@ class InboxActivities(TestCase):
|
||||||
|
|
||||||
@responses.activate
|
@responses.activate
|
||||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||||
def test_boost_remote_status(self, redis_mock):
|
@patch("bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores")
|
||||||
|
def test_boost_remote_status(self, redis_mock, _):
|
||||||
"""boost a status from a remote server"""
|
"""boost a status from a remote server"""
|
||||||
work = models.Work.objects.create(title="work title")
|
work = models.Work.objects.create(title="work title")
|
||||||
book = models.Edition.objects.create(
|
book = models.Edition.objects.create(
|
||||||
|
@ -153,12 +155,13 @@ class InboxActivities(TestCase):
|
||||||
views.inbox.activity_task(activity)
|
views.inbox.activity_task(activity)
|
||||||
self.assertEqual(models.Boost.objects.count(), 0)
|
self.assertEqual(models.Boost.objects.count(), 0)
|
||||||
|
|
||||||
def test_unboost(self):
|
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||||
|
@patch("bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores")
|
||||||
|
def test_unboost(self, *_):
|
||||||
"""undo a boost"""
|
"""undo a boost"""
|
||||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
boost = models.Boost.objects.create(
|
||||||
boost = models.Boost.objects.create(
|
boosted_status=self.status, user=self.remote_user
|
||||||
boosted_status=self.status, user=self.remote_user
|
)
|
||||||
)
|
|
||||||
activity = {
|
activity = {
|
||||||
"type": "Undo",
|
"type": "Undo",
|
||||||
"actor": "hi",
|
"actor": "hi",
|
||||||
|
@ -175,11 +178,7 @@ class InboxActivities(TestCase):
|
||||||
"published": "Mon, 25 May 2020 19:31:20 GMT",
|
"published": "Mon, 25 May 2020 19:31:20 GMT",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
with patch(
|
views.inbox.activity_task(activity)
|
||||||
"bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores"
|
|
||||||
) as redis_mock:
|
|
||||||
views.inbox.activity_task(activity)
|
|
||||||
self.assertTrue(redis_mock.called)
|
|
||||||
self.assertFalse(models.Boost.objects.exists())
|
self.assertFalse(models.Boost.objects.exists())
|
||||||
|
|
||||||
def test_unboost_unknown_boost(self):
|
def test_unboost_unknown_boost(self):
|
||||||
|
|
|
@ -8,6 +8,7 @@ from bookwyrm import models, views
|
||||||
|
|
||||||
|
|
||||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||||
|
@patch("bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores")
|
||||||
class InteractionViews(TestCase):
|
class InteractionViews(TestCase):
|
||||||
"""viewing and creating statuses"""
|
"""viewing and creating statuses"""
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ class InteractionViews(TestCase):
|
||||||
parent_work=work,
|
parent_work=work,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_favorite(self, _):
|
def test_favorite(self, *_):
|
||||||
"""create and broadcast faving a status"""
|
"""create and broadcast faving a status"""
|
||||||
view = views.Favorite.as_view()
|
view = views.Favorite.as_view()
|
||||||
request = self.factory.post("")
|
request = self.factory.post("")
|
||||||
|
@ -58,7 +59,7 @@ class InteractionViews(TestCase):
|
||||||
self.assertEqual(notification.user, self.local_user)
|
self.assertEqual(notification.user, self.local_user)
|
||||||
self.assertEqual(notification.related_user, self.remote_user)
|
self.assertEqual(notification.related_user, self.remote_user)
|
||||||
|
|
||||||
def test_unfavorite(self, _):
|
def test_unfavorite(self, *_):
|
||||||
"""unfav a status"""
|
"""unfav a status"""
|
||||||
view = views.Unfavorite.as_view()
|
view = views.Unfavorite.as_view()
|
||||||
request = self.factory.post("")
|
request = self.factory.post("")
|
||||||
|
@ -75,7 +76,7 @@ class InteractionViews(TestCase):
|
||||||
self.assertEqual(models.Favorite.objects.count(), 0)
|
self.assertEqual(models.Favorite.objects.count(), 0)
|
||||||
self.assertEqual(models.Notification.objects.count(), 0)
|
self.assertEqual(models.Notification.objects.count(), 0)
|
||||||
|
|
||||||
def test_boost(self, _):
|
def test_boost(self, *_):
|
||||||
"""boost a status"""
|
"""boost a status"""
|
||||||
view = views.Boost.as_view()
|
view = views.Boost.as_view()
|
||||||
request = self.factory.post("")
|
request = self.factory.post("")
|
||||||
|
@ -97,7 +98,7 @@ class InteractionViews(TestCase):
|
||||||
self.assertEqual(notification.related_user, self.remote_user)
|
self.assertEqual(notification.related_user, self.remote_user)
|
||||||
self.assertEqual(notification.related_status, status)
|
self.assertEqual(notification.related_status, status)
|
||||||
|
|
||||||
def test_self_boost(self, _):
|
def test_self_boost(self, *_):
|
||||||
"""boost your own status"""
|
"""boost your own status"""
|
||||||
view = views.Boost.as_view()
|
view = views.Boost.as_view()
|
||||||
request = self.factory.post("")
|
request = self.factory.post("")
|
||||||
|
@ -121,7 +122,7 @@ class InteractionViews(TestCase):
|
||||||
|
|
||||||
self.assertFalse(models.Notification.objects.exists())
|
self.assertFalse(models.Notification.objects.exists())
|
||||||
|
|
||||||
def test_boost_unlisted(self, _):
|
def test_boost_unlisted(self, *_):
|
||||||
"""boost a status"""
|
"""boost a status"""
|
||||||
view = views.Boost.as_view()
|
view = views.Boost.as_view()
|
||||||
request = self.factory.post("")
|
request = self.factory.post("")
|
||||||
|
@ -136,7 +137,7 @@ class InteractionViews(TestCase):
|
||||||
boost = models.Boost.objects.get()
|
boost = models.Boost.objects.get()
|
||||||
self.assertEqual(boost.privacy, "unlisted")
|
self.assertEqual(boost.privacy, "unlisted")
|
||||||
|
|
||||||
def test_boost_private(self, _):
|
def test_boost_private(self, *_):
|
||||||
"""boost a status"""
|
"""boost a status"""
|
||||||
view = views.Boost.as_view()
|
view = views.Boost.as_view()
|
||||||
request = self.factory.post("")
|
request = self.factory.post("")
|
||||||
|
@ -149,7 +150,7 @@ class InteractionViews(TestCase):
|
||||||
view(request, status.id)
|
view(request, status.id)
|
||||||
self.assertFalse(models.Boost.objects.exists())
|
self.assertFalse(models.Boost.objects.exists())
|
||||||
|
|
||||||
def test_boost_twice(self, _):
|
def test_boost_twice(self, *_):
|
||||||
"""boost a status"""
|
"""boost a status"""
|
||||||
view = views.Boost.as_view()
|
view = views.Boost.as_view()
|
||||||
request = self.factory.post("")
|
request = self.factory.post("")
|
||||||
|
@ -161,13 +162,17 @@ class InteractionViews(TestCase):
|
||||||
view(request, status.id)
|
view(request, status.id)
|
||||||
self.assertEqual(models.Boost.objects.count(), 1)
|
self.assertEqual(models.Boost.objects.count(), 1)
|
||||||
|
|
||||||
def test_unboost(self, _):
|
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||||
|
def test_unboost(self, *_):
|
||||||
"""undo a boost"""
|
"""undo a boost"""
|
||||||
view = views.Unboost.as_view()
|
view = views.Unboost.as_view()
|
||||||
request = self.factory.post("")
|
request = self.factory.post("")
|
||||||
request.user = self.remote_user
|
request.user = self.remote_user
|
||||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
status = models.Status.objects.create(user=self.local_user, content="hi")
|
||||||
status = models.Status.objects.create(user=self.local_user, content="hi")
|
|
||||||
|
with patch(
|
||||||
|
"bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores"
|
||||||
|
):
|
||||||
views.Boost.as_view()(request, status.id)
|
views.Boost.as_view()(request, status.id)
|
||||||
|
|
||||||
self.assertEqual(models.Boost.objects.count(), 1)
|
self.assertEqual(models.Boost.objects.count(), 1)
|
||||||
|
|
|
@ -37,8 +37,12 @@ class ManageInvites(View):
|
||||||
PAGE_LENGTH,
|
PAGE_LENGTH,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
page = paginated.get_page(request.GET.get("page"))
|
||||||
data = {
|
data = {
|
||||||
"invites": paginated.get_page(request.GET.get("page")),
|
"invites": page,
|
||||||
|
"page_range": paginated.get_elided_page_range(
|
||||||
|
page.number, on_each_side=2, on_ends=1
|
||||||
|
),
|
||||||
"form": forms.CreateInviteForm(),
|
"form": forms.CreateInviteForm(),
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, "settings/manage_invites.html", data)
|
return TemplateResponse(request, "settings/manage_invites.html", data)
|
||||||
|
@ -118,15 +122,16 @@ class ManageInviteRequests(View):
|
||||||
reduce(operator.or_, (Q(**f) for f in filters))
|
reduce(operator.or_, (Q(**f) for f in filters))
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
paginated = Paginator(
|
paginated = Paginator(requests, PAGE_LENGTH)
|
||||||
requests,
|
|
||||||
PAGE_LENGTH,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
page = paginated.get_page(request.GET.get("page"))
|
||||||
data = {
|
data = {
|
||||||
"ignored": ignored,
|
"ignored": ignored,
|
||||||
"count": paginated.count,
|
"count": paginated.count,
|
||||||
"requests": paginated.get_page(request.GET.get("page")),
|
"requests": page,
|
||||||
|
"page_range": paginated.get_elided_page_range(
|
||||||
|
page.number, on_each_side=2, on_ends=1
|
||||||
|
),
|
||||||
"sort": sort,
|
"sort": sort,
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, "settings/manage_invite_requests.html", data)
|
return TemplateResponse(request, "settings/manage_invite_requests.html", data)
|
||||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: 0.1.1\n"
|
"Project-Id-Version: 0.1.1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2021-06-07 14:09+0000\n"
|
"POT-Creation-Date: 2021-06-09 16:46+0000\n"
|
||||||
"PO-Revision-Date: 2021-04-05 12:44+0100\n"
|
"PO-Revision-Date: 2021-04-05 12:44+0100\n"
|
||||||
"Last-Translator: Fabien Basmaison <contact@arkhi.org>\n"
|
"Last-Translator: Fabien Basmaison <contact@arkhi.org>\n"
|
||||||
"Language-Team: Mouse Reeve <LL@li.org>\n"
|
"Language-Team: Mouse Reeve <LL@li.org>\n"
|
||||||
|
@ -91,23 +91,23 @@ msgstr "nom du compte :"
|
||||||
msgid "A user with that username already exists."
|
msgid "A user with that username already exists."
|
||||||
msgstr "Ce nom est déjà associé à un compte."
|
msgstr "Ce nom est déjà associé à un compte."
|
||||||
|
|
||||||
#: bookwyrm/settings.py:160
|
#: bookwyrm/settings.py:156
|
||||||
msgid "English"
|
msgid "English"
|
||||||
msgstr "English"
|
msgstr "English"
|
||||||
|
|
||||||
#: bookwyrm/settings.py:161
|
#: bookwyrm/settings.py:157
|
||||||
msgid "German"
|
msgid "German"
|
||||||
msgstr "Deutsch"
|
msgstr "Deutsch"
|
||||||
|
|
||||||
#: bookwyrm/settings.py:162
|
#: bookwyrm/settings.py:158
|
||||||
msgid "Spanish"
|
msgid "Spanish"
|
||||||
msgstr "Español"
|
msgstr "Español"
|
||||||
|
|
||||||
#: bookwyrm/settings.py:163
|
#: bookwyrm/settings.py:159
|
||||||
msgid "French"
|
msgid "French"
|
||||||
msgstr "Français"
|
msgstr "Français"
|
||||||
|
|
||||||
#: bookwyrm/settings.py:164
|
#: bookwyrm/settings.py:160
|
||||||
msgid "Simplified Chinese"
|
msgid "Simplified Chinese"
|
||||||
msgstr "简化字"
|
msgstr "简化字"
|
||||||
|
|
||||||
|
@ -154,12 +154,12 @@ msgid "Wikipedia"
|
||||||
msgstr "Wikipedia"
|
msgstr "Wikipedia"
|
||||||
|
|
||||||
#: bookwyrm/templates/author/author.html:69
|
#: bookwyrm/templates/author/author.html:69
|
||||||
#: bookwyrm/templates/book/book.html:78
|
#: bookwyrm/templates/book/book.html:87
|
||||||
msgid "View on OpenLibrary"
|
msgid "View on OpenLibrary"
|
||||||
msgstr "Voir sur OpenLibrary"
|
msgstr "Voir sur OpenLibrary"
|
||||||
|
|
||||||
#: bookwyrm/templates/author/author.html:77
|
#: bookwyrm/templates/author/author.html:77
|
||||||
#: bookwyrm/templates/book/book.html:81
|
#: bookwyrm/templates/book/book.html:90
|
||||||
msgid "View on Inventaire"
|
msgid "View on Inventaire"
|
||||||
msgstr "Voir sur Inventaire"
|
msgstr "Voir sur Inventaire"
|
||||||
|
|
||||||
|
@ -252,7 +252,7 @@ msgid "Goodreads key:"
|
||||||
msgstr "Clé Goodreads :"
|
msgstr "Clé Goodreads :"
|
||||||
|
|
||||||
#: bookwyrm/templates/author/edit_author.html:116
|
#: bookwyrm/templates/author/edit_author.html:116
|
||||||
#: bookwyrm/templates/book/book.html:124
|
#: bookwyrm/templates/book/book.html:133
|
||||||
#: bookwyrm/templates/book/edit_book.html:321
|
#: bookwyrm/templates/book/edit_book.html:321
|
||||||
#: bookwyrm/templates/lists/form.html:42
|
#: bookwyrm/templates/lists/form.html:42
|
||||||
#: bookwyrm/templates/preferences/edit_user.html:70
|
#: bookwyrm/templates/preferences/edit_user.html:70
|
||||||
|
@ -269,7 +269,7 @@ msgid "Save"
|
||||||
msgstr "Enregistrer"
|
msgstr "Enregistrer"
|
||||||
|
|
||||||
#: bookwyrm/templates/author/edit_author.html:117
|
#: bookwyrm/templates/author/edit_author.html:117
|
||||||
#: bookwyrm/templates/book/book.html:125 bookwyrm/templates/book/book.html:174
|
#: bookwyrm/templates/book/book.html:134 bookwyrm/templates/book/book.html:183
|
||||||
#: bookwyrm/templates/book/cover_modal.html:32
|
#: bookwyrm/templates/book/cover_modal.html:32
|
||||||
#: bookwyrm/templates/book/edit_book.html:322
|
#: bookwyrm/templates/book/edit_book.html:322
|
||||||
#: bookwyrm/templates/moderation/report_modal.html:34
|
#: bookwyrm/templates/moderation/report_modal.html:34
|
||||||
|
@ -284,98 +284,98 @@ msgstr "Enregistrer"
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Annuler"
|
msgstr "Annuler"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:31
|
#: bookwyrm/templates/book/book.html:40
|
||||||
#: bookwyrm/templates/discover/large-book.html:25
|
#: bookwyrm/templates/discover/large-book.html:25
|
||||||
#: bookwyrm/templates/discover/small-book.html:19
|
#: bookwyrm/templates/discover/small-book.html:19
|
||||||
msgid "by"
|
msgid "by"
|
||||||
msgstr "par"
|
msgstr "par"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:39 bookwyrm/templates/book/book.html:40
|
#: bookwyrm/templates/book/book.html:48 bookwyrm/templates/book/book.html:49
|
||||||
msgid "Edit Book"
|
msgid "Edit Book"
|
||||||
msgstr "Modifier le livre"
|
msgstr "Modifier le livre"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:57
|
#: bookwyrm/templates/book/book.html:66
|
||||||
#: bookwyrm/templates/book/cover_modal.html:5
|
#: bookwyrm/templates/book/cover_modal.html:5
|
||||||
msgid "Add cover"
|
msgid "Add cover"
|
||||||
msgstr "Ajouter une couverture"
|
msgstr "Ajouter une couverture"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:61
|
#: bookwyrm/templates/book/book.html:70
|
||||||
msgid "Failed to load cover"
|
msgid "Failed to load cover"
|
||||||
msgstr "La couverture n’a pu être chargée"
|
msgstr "La couverture n’a pu être chargée"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:101
|
#: bookwyrm/templates/book/book.html:110
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "(%(review_count)s review)"
|
msgid "(%(review_count)s review)"
|
||||||
msgid_plural "(%(review_count)s reviews)"
|
msgid_plural "(%(review_count)s reviews)"
|
||||||
msgstr[0] "(%(review_count)s critique)"
|
msgstr[0] "(%(review_count)s critique)"
|
||||||
msgstr[1] "(%(review_count)s critiques)"
|
msgstr[1] "(%(review_count)s critiques)"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:113
|
#: bookwyrm/templates/book/book.html:122
|
||||||
msgid "Add Description"
|
msgid "Add Description"
|
||||||
msgstr "Ajouter une description"
|
msgstr "Ajouter une description"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:120
|
#: bookwyrm/templates/book/book.html:129
|
||||||
#: bookwyrm/templates/book/edit_book.html:136
|
#: bookwyrm/templates/book/edit_book.html:136
|
||||||
#: bookwyrm/templates/lists/form.html:12
|
#: bookwyrm/templates/lists/form.html:12
|
||||||
msgid "Description:"
|
msgid "Description:"
|
||||||
msgstr "Description :"
|
msgstr "Description :"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:134
|
#: bookwyrm/templates/book/book.html:143
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "<a href=\"%(path)s/editions\">%(count)s editions</a>"
|
msgid "<a href=\"%(path)s/editions\">%(count)s editions</a>"
|
||||||
msgstr "<a href=\"%(path)s/editions\">%(count)s éditions</a>"
|
msgstr "<a href=\"%(path)s/editions\">%(count)s éditions</a>"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:142
|
#: bookwyrm/templates/book/book.html:151
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This edition is on your <a href=\"%(path)s\">%(shelf_name)s</a> shelf."
|
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>."
|
msgstr "Cette édition est sur votre étagère <a href=\"%(path)s\">%(shelf_name)s</a>."
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:148
|
#: bookwyrm/templates/book/book.html:157
|
||||||
#, python-format
|
#, 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."
|
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 "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>."
|
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:159
|
#: bookwyrm/templates/book/book.html:168
|
||||||
msgid "Your reading activity"
|
msgid "Your reading activity"
|
||||||
msgstr "Votre activité de lecture"
|
msgstr "Votre activité de lecture"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:162
|
#: bookwyrm/templates/book/book.html:171
|
||||||
msgid "Add read dates"
|
msgid "Add read dates"
|
||||||
msgstr "Ajouter des dates de lecture"
|
msgstr "Ajouter des dates de lecture"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:171
|
#: bookwyrm/templates/book/book.html:180
|
||||||
msgid "Create"
|
msgid "Create"
|
||||||
msgstr "Créer"
|
msgstr "Créer"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:181
|
#: bookwyrm/templates/book/book.html:190
|
||||||
msgid "You don't have any reading activity for this book."
|
msgid "You don't have any reading activity for this book."
|
||||||
msgstr "Vous n’avez aucune activité de lecture pour ce livre"
|
msgstr "Vous n’avez aucune activité de lecture pour ce livre"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:200
|
#: bookwyrm/templates/book/book.html:209
|
||||||
msgid "Reviews"
|
msgid "Reviews"
|
||||||
msgstr "Critiques"
|
msgstr "Critiques"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:205
|
#: bookwyrm/templates/book/book.html:214
|
||||||
msgid "Your reviews"
|
msgid "Your reviews"
|
||||||
msgstr "Vos critiques"
|
msgstr "Vos critiques"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:211
|
#: bookwyrm/templates/book/book.html:220
|
||||||
msgid "Your comments"
|
msgid "Your comments"
|
||||||
msgstr "Vos commentaires"
|
msgstr "Vos commentaires"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:217
|
#: bookwyrm/templates/book/book.html:226
|
||||||
msgid "Your quotes"
|
msgid "Your quotes"
|
||||||
msgstr "Vos citations"
|
msgstr "Vos citations"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:253
|
#: bookwyrm/templates/book/book.html:262
|
||||||
msgid "Subjects"
|
msgid "Subjects"
|
||||||
msgstr "Sujets"
|
msgstr "Sujets"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:265
|
#: bookwyrm/templates/book/book.html:274
|
||||||
msgid "Places"
|
msgid "Places"
|
||||||
msgstr "Lieux"
|
msgstr "Lieux"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:276 bookwyrm/templates/layout.html:61
|
#: bookwyrm/templates/book/book.html:285 bookwyrm/templates/layout.html:61
|
||||||
#: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12
|
#: bookwyrm/templates/lists/lists.html:5 bookwyrm/templates/lists/lists.html:12
|
||||||
#: bookwyrm/templates/search/layout.html:25
|
#: bookwyrm/templates/search/layout.html:25
|
||||||
#: bookwyrm/templates/search/layout.html:50
|
#: bookwyrm/templates/search/layout.html:50
|
||||||
|
@ -383,11 +383,11 @@ msgstr "Lieux"
|
||||||
msgid "Lists"
|
msgid "Lists"
|
||||||
msgstr "Listes"
|
msgstr "Listes"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:287
|
#: bookwyrm/templates/book/book.html:296
|
||||||
msgid "Add to list"
|
msgid "Add to list"
|
||||||
msgstr "Ajouter à la liste"
|
msgstr "Ajouter à la liste"
|
||||||
|
|
||||||
#: bookwyrm/templates/book/book.html:297
|
#: bookwyrm/templates/book/book.html:306
|
||||||
#: bookwyrm/templates/book/cover_modal.html:31
|
#: bookwyrm/templates/book/cover_modal.html:31
|
||||||
#: bookwyrm/templates/lists/list.html:179
|
#: bookwyrm/templates/lists/list.html:179
|
||||||
msgid "Add"
|
msgid "Add"
|
||||||
|
@ -2921,6 +2921,11 @@ msgstr "Niveau d’accès :"
|
||||||
msgid "File exceeds maximum size: 10MB"
|
msgid "File exceeds maximum size: 10MB"
|
||||||
msgstr "Ce fichier dépasse la taille limite : 10 Mo"
|
msgstr "Ce fichier dépasse la taille limite : 10 Mo"
|
||||||
|
|
||||||
|
#: bookwyrm/templatetags/utilities.py:30
|
||||||
|
#, python-format
|
||||||
|
msgid "%(title)s: %(subtitle)s"
|
||||||
|
msgstr "%(title)s (%(subtitle)s)"
|
||||||
|
|
||||||
#: bookwyrm/views/import_data.py:67
|
#: bookwyrm/views/import_data.py:67
|
||||||
msgid "Not a valid csv file"
|
msgid "Not a valid csv file"
|
||||||
msgstr "Fichier CSV non valide"
|
msgstr "Fichier CSV non valide"
|
||||||
|
|
Loading…
Reference in a new issue