Merge branch 'main' into book-file-links

This commit is contained in:
Mouse Reeve 2021-10-07 15:27:16 -07:00
commit 37e03e8993
37 changed files with 1879 additions and 1636 deletions

View file

@ -8,7 +8,6 @@ from django.utils import timezone
from bookwyrm import models
from bookwyrm.redis_store import RedisStore, r
from bookwyrm.tasks import app
from bookwyrm.views.helpers import privacy_filter
class ActivityStream(RedisStore):
@ -43,7 +42,7 @@ class ActivityStream(RedisStore):
def add_user_statuses(self, viewer, user):
"""add a user's statuses to another user's feed"""
# only add the statuses that the viewer should be able to see (ie, not dms)
statuses = privacy_filter(viewer, user.status_set.all())
statuses = models.Status.privacy_filter(viewer).filter(user=user)
self.bulk_add_objects_to_store(statuses, self.stream_id(viewer))
def remove_user_statuses(self, viewer, user):
@ -113,9 +112,8 @@ class ActivityStream(RedisStore):
def get_statuses_for_user(self, user): # pylint: disable=no-self-use
"""given a user, what statuses should they see on this stream"""
return privacy_filter(
return models.Status.privacy_filter(
user,
models.Status.objects.select_subclasses(),
privacy_levels=["public", "unlisted", "followers"],
)
@ -139,11 +137,15 @@ class HomeStream(ActivityStream):
).distinct()
def get_statuses_for_user(self, user):
return privacy_filter(
return models.Status.privacy_filter(
user,
models.Status.objects.select_subclasses(),
privacy_levels=["public", "unlisted", "followers"],
following_only=True,
).exclude(
~Q( # remove everything except
Q(user__followers=user) # user following
| Q(user=user) # is self
| Q(mention_users=user) # mentions user
),
)
@ -160,11 +162,10 @@ class LocalStream(ActivityStream):
def get_statuses_for_user(self, user):
# all public statuses by a local user
return privacy_filter(
return models.Status.privacy_filter(
user,
models.Status.objects.select_subclasses().filter(user__local=True),
privacy_levels=["public"],
)
).filter(user__local=True)
class BooksStream(ActivityStream):
@ -197,50 +198,53 @@ class BooksStream(ActivityStream):
books = user.shelfbook_set.values_list(
"book__parent_work__id", flat=True
).distinct()
return privacy_filter(
user,
models.Status.objects.select_subclasses()
return (
models.Status.privacy_filter(
user,
privacy_levels=["public"],
)
.filter(
Q(comment__book__parent_work__id__in=books)
| Q(quotation__book__parent_work__id__in=books)
| Q(review__book__parent_work__id__in=books)
| Q(mention_books__parent_work__id__in=books)
)
.distinct(),
privacy_levels=["public"],
.distinct()
)
def add_book_statuses(self, user, book):
"""add statuses about a book to a user's feed"""
work = book.parent_work
statuses = privacy_filter(
user,
models.Status.objects.select_subclasses()
statuses = (
models.Status.privacy_filter(
user,
privacy_levels=["public"],
)
.filter(
Q(comment__book__parent_work=work)
| Q(quotation__book__parent_work=work)
| Q(review__book__parent_work=work)
| Q(mention_books__parent_work=work)
)
.distinct(),
privacy_levels=["public"],
.distinct()
)
self.bulk_add_objects_to_store(statuses, self.stream_id(user))
def remove_book_statuses(self, user, book):
"""add statuses about a book to a user's feed"""
work = book.parent_work
statuses = privacy_filter(
user,
models.Status.objects.select_subclasses()
statuses = (
models.Status.privacy_filter(
user,
privacy_levels=["public"],
)
.filter(
Q(comment__book__parent_work=work)
| Q(quotation__book__parent_work=work)
| Q(review__book__parent_work=work)
| Q(mention_books__parent_work=work)
)
.distinct(),
privacy_levels=["public"],
.distinct()
)
self.bulk_remove_objects_from_store(statuses, self.stream_id(user))

View file

@ -144,6 +144,7 @@ class EditUserForm(CustomForm):
"default_post_privacy",
"discoverable",
"preferred_timezone",
"preferred_language",
]
help_texts = {f: None for f in fields}

View file

@ -0,0 +1,30 @@
# Generated by Django 3.2.5 on 2021-10-06 19:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0105_alter_connector_connector_file"),
]
operations = [
migrations.AddField(
model_name="user",
name="preferred_language",
field=models.CharField(
blank=True,
choices=[
("en-us", "English"),
("de-de", "German"),
("es", "Spanish"),
("fr-fr", "French"),
("zh-hans", "Simplified Chinese"),
("zh-hant", "Traditional Chinese"),
],
max_length=255,
null=True,
),
),
]

View file

@ -4,6 +4,7 @@ from Crypto import Random
from django.core.exceptions import PermissionDenied
from django.db import models
from django.db.models import Q
from django.dispatch import receiver
from django.http import Http404
from django.utils.translation import gettext_lazy as _
@ -105,6 +106,52 @@ class BookWyrmModel(models.Model):
raise PermissionDenied()
@classmethod
def privacy_filter(cls, viewer, privacy_levels=None):
"""filter objects that have "user" and "privacy" fields"""
queryset = cls.objects
if hasattr(queryset, "select_subclasses"):
queryset = queryset.select_subclasses()
privacy_levels = privacy_levels or ["public", "unlisted", "followers", "direct"]
# you can't see followers only or direct messages if you're not logged in
if viewer.is_anonymous:
privacy_levels = [
p for p in privacy_levels if not p in ["followers", "direct"]
]
else:
# exclude blocks from both directions
queryset = queryset.exclude(
Q(user__blocked_by=viewer) | Q(user__blocks=viewer)
)
# filter to only provided privacy levels
queryset = queryset.filter(privacy__in=privacy_levels)
if "followers" in privacy_levels:
queryset = cls.followers_filter(queryset, viewer)
# exclude direct messages not intended for the user
if "direct" in privacy_levels:
queryset = cls.direct_filter(queryset, viewer)
return queryset
@classmethod
def followers_filter(cls, queryset, viewer):
"""Override-able filter for "followers" privacy level"""
return queryset.exclude(
~Q( # user isn't following and it isn't their own status
Q(user__followers=viewer) | Q(user=viewer)
),
privacy="followers", # and the status is followers only
)
@classmethod
def direct_filter(cls, queryset, viewer):
"""Override-able filter for "direct" privacy level"""
return queryset.exclude(~Q(user=viewer), privacy="direct")
@receiver(models.signals.post_save)
# pylint: disable=unused-argument

View file

@ -6,6 +6,7 @@ from django.apps import apps
from django.core.exceptions import PermissionDenied
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Q
from django.dispatch import receiver
from django.template.loader import get_template
from django.utils import timezone
@ -207,6 +208,18 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
if isinstance(self, (GeneratedNote, ReviewRating)):
raise PermissionDenied()
@classmethod
def privacy_filter(cls, viewer, privacy_levels=None):
queryset = super().privacy_filter(viewer, privacy_levels=privacy_levels)
return queryset.filter(deleted=False)
@classmethod
def direct_filter(cls, queryset, viewer):
"""Overridden filter for "direct" privacy level"""
return queryset.exclude(
~Q(Q(user=viewer) | Q(mention_users=viewer)), privacy="direct"
)
class GeneratedNote(Status):
"""these are app-generated messages about user activity"""

View file

@ -17,7 +17,7 @@ from bookwyrm.connectors import get_data, ConnectorException
from bookwyrm.models.shelf import Shelf
from bookwyrm.models.status import Status, Review
from bookwyrm.preview_images import generate_user_preview_image_task
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES, USE_HTTPS
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES, USE_HTTPS, LANGUAGES
from bookwyrm.signatures import create_key_pair
from bookwyrm.tasks import app
from bookwyrm.utils import regex
@ -133,6 +133,12 @@ class User(OrderedCollectionPageMixin, AbstractUser):
default=str(pytz.utc),
max_length=255,
)
preferred_language = models.CharField(
choices=LANGUAGES,
null=True,
blank=True,
max_length=255,
)
deactivation_reason = models.CharField(
max_length=255, choices=DeactivationReason, null=True, blank=True
)

View file

@ -30,6 +30,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LOCALE_PATHS = [
os.path.join(BASE_DIR, "locale"),
]
LANGUAGE_COOKIE_NAME = env.str("LANGUAGE_COOKIE_NAME", "django_language")
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
@ -161,11 +162,11 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = "en-us"
LANGUAGES = [
("en-us", _("English")),
("de-de", _("German")),
("es", _("Spanish")),
("fr-fr", _("French")),
("zh-hans", _("Simplified Chinese")),
("zh-hant", _("Traditional Chinese")),
("de-de", _("Deutsch (German)")), # German
("es", _("Español (Spanish)")), # Spanish
("fr-fr", _("Français (French)")), # French
("zh-hans", _("简体中文 (Simplified Chinese)")), # Simplified Chinese
("zh-hant", _("繁體中文 (Traditional Chinese)")), # Traditional Chinese
]
@ -210,12 +211,13 @@ if USE_S3:
AWS_S3_OBJECT_PARAMETERS = {"CacheControl": "max-age=86400"}
# S3 Static settings
STATIC_LOCATION = "static"
STATIC_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/{STATIC_LOCATION}/"
STATIC_URL = f"{PROTOCOL}://{AWS_S3_CUSTOM_DOMAIN}/{STATIC_LOCATION}/"
STATICFILES_STORAGE = "bookwyrm.storage_backends.StaticStorage"
# S3 Media settings
MEDIA_LOCATION = "images"
MEDIA_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/{MEDIA_LOCATION}/"
MEDIA_URL = f"{PROTOCOL}://{AWS_S3_CUSTOM_DOMAIN}/{MEDIA_LOCATION}/"
MEDIA_FULL_URL = MEDIA_URL
STATIC_FULL_URL = STATIC_URL
DEFAULT_FILE_STORAGE = "bookwyrm.storage_backends.ImagesStorage"
# I don't know if it's used, but the site crashes without it
STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static"))
@ -225,4 +227,5 @@ else:
STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static"))
MEDIA_URL = "/images/"
MEDIA_FULL_URL = f"{PROTOCOL}://{DOMAIN}{MEDIA_URL}"
STATIC_FULL_URL = f"{PROTOCOL}://{DOMAIN}{STATIC_URL}"
MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images"))

View file

@ -28,7 +28,7 @@
</label>
<label class="field">
<input type="radio" name="curation" value="open"{% if list.curation == 'open' %} checked{% endif %}> {% trans "Open" %}
<input type="radio" name="curation" value="open"{% if list.curation == 'open' %} checked{% endif %}> {% trans "Open" context "curation type" %}
<p class="help mb-2">{% trans "Anyone can add books to this list" %}</p>
</label>
</fieldset>

View file

@ -91,6 +91,12 @@
{{ form.preferred_timezone }}
</div>
</div>
<div class="field">
<label class="label" for="id_preferred_language">{% trans "Language:" %}</label>
<div class="select">
{{ form.preferred_language }}
</div>
</div>
</div>
</section>

View file

@ -1,6 +1,6 @@
{% load i18n %}{% load humanize %}{% load utilities %}
{% blocktrans trimmed with title=book|book_title path=book.remote_id display_rating=rating|floatformat:"0" count counter=rating|add:0 %}
{% blocktrans trimmed with title=book|book_title path=book.remote_id display_rating=rating|floatformat:"-1" count counter=rating|add:0 %}
rated <em><a href="{{ path }}">{{ title }}</a></em>: {{ display_rating }} star
{% plural %}
rated <em><a href="{{ path }}">{{ title }}</a></em>: {{ display_rating }} stars

View file

@ -1,7 +1,7 @@
{% load i18n %}
{% if rating %}
{% blocktrans with book_title=book.title|safe display_rating=rating|floatformat:"0" review_title=name|safe count counter=rating %}Review of "{{ book_title }}" ({{ display_rating }} star): {{ review_title }}{% plural %}Review of "{{ book_title }}" ({{ display_rating }} stars): {{ review_title }}{% endblocktrans %}
{% blocktrans with book_title=book.title|safe display_rating=rating|floatformat:"-1" review_title=name|safe count counter=rating %}Review of "{{ book_title }}" ({{ display_rating }} star): {{ review_title }}{% plural %}Review of "{{ book_title }}" ({{ display_rating }} stars): {{ review_title }}{% endblocktrans %}
{% else %}

View file

@ -2,7 +2,7 @@
from django import template
from django.db.models import Avg
from bookwyrm import models, views
from bookwyrm import models
register = template.Library()
@ -11,8 +11,8 @@ register = template.Library()
@register.filter(name="rating")
def get_rating(book, user):
"""get the overall rating of a book"""
queryset = views.helpers.privacy_filter(
user, models.Review.objects.filter(book__parent_work__editions=book)
queryset = models.Review.privacy_filter(user).filter(
book__parent_work__editions=book
)
return queryset.aggregate(Avg("rating"))["rating__avg"]

View file

@ -16,7 +16,7 @@ from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.connectors import connector_manager
from bookwyrm.connectors.abstract_connector import get_image
from bookwyrm.settings import PAGE_LENGTH
from bookwyrm.views.helpers import is_api_request, privacy_filter
from bookwyrm.views.helpers import is_api_request
# pylint: disable=no-self-use
@ -48,8 +48,8 @@ class Book(View):
raise Http404()
# all reviews for all editions of the book
reviews = privacy_filter(
request.user, models.Review.objects.filter(book__parent_work__editions=book)
reviews = models.Review.privacy_filter(request.user).filter(
book__parent_work__editions=book
)
# the reviews to show
@ -66,12 +66,9 @@ class Book(View):
queryset = queryset.select_related("user").order_by("-published_date")
paginated = Paginator(queryset, PAGE_LENGTH)
lists = privacy_filter(
request.user,
models.List.objects.filter(
listitem__approved=True,
listitem__book__in=book.parent_work.editions.all(),
),
lists = models.List.privacy_filter(request.user,).filter(
listitem__approved=True,
listitem__book__in=book.parent_work.editions.all(),
)
data = {
"book": book,

View file

@ -13,7 +13,7 @@ from bookwyrm import activitystreams, forms, models
from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.settings import PAGE_LENGTH, STREAMS
from bookwyrm.suggested_users import suggested_users
from .helpers import get_user_from_username, privacy_filter
from .helpers import get_user_from_username
from .helpers import is_api_request, is_bookwyrm_request
@ -56,11 +56,15 @@ class DirectMessage(View):
def get(self, request, username=None):
"""like a feed but for dms only"""
# remove fancy subclasses of status, keep just good ol' notes
queryset = models.Status.objects.filter(
review__isnull=True,
comment__isnull=True,
quotation__isnull=True,
generatednote__isnull=True,
activities = (
models.Status.privacy_filter(request.user, privacy_levels=["direct"])
.filter(
review__isnull=True,
comment__isnull=True,
quotation__isnull=True,
generatednote__isnull=True,
)
.order_by("-published_date")
)
user = None
@ -70,11 +74,7 @@ class DirectMessage(View):
except Http404:
pass
if user:
queryset = queryset.filter(Q(user=user) | Q(mention_users=user))
activities = privacy_filter(
request.user, queryset, privacy_levels=["direct"]
).order_by("-published_date")
activities = activities.filter(Q(user=user) | Q(mention_users=user))
paginated = Paginator(activities, PAGE_LENGTH)
data = {
@ -109,9 +109,11 @@ class Status(View):
status.to_activity(pure=not is_bookwyrm_request(request))
)
visible_thread = privacy_filter(
request.user, models.Status.objects.filter(thread_id=status.thread_id)
).values_list("id", flat=True)
visible_thread = (
models.Status.privacy_filter(request.user)
.filter(thread_id=status.thread_id)
.values_list("id", flat=True)
)
visible_thread = list(visible_thread)
ancestors = models.Status.objects.select_subclasses().raw(

View file

@ -6,11 +6,10 @@ import dateutil.tz
from dateutil.parser import ParserError
from requests import HTTPError
from django.core.exceptions import FieldError
from django.db.models import Q
from django.http import Http404
from django.utils import translation
from bookwyrm import activitypub, models
from bookwyrm import activitypub, models, settings
from bookwyrm.connectors import ConnectorException, get_data
from bookwyrm.status import create_generated_note
from bookwyrm.utils import regex
@ -50,56 +49,6 @@ def is_bookwyrm_request(request):
return True
def privacy_filter(viewer, queryset, privacy_levels=None, following_only=False):
"""filter objects that have "user" and "privacy" fields"""
privacy_levels = privacy_levels or ["public", "unlisted", "followers", "direct"]
# if there'd a deleted field, exclude deleted items
try:
queryset = queryset.filter(deleted=False)
except FieldError:
pass
# exclude blocks from both directions
if not viewer.is_anonymous:
queryset = queryset.exclude(Q(user__blocked_by=viewer) | Q(user__blocks=viewer))
# you can't see followers only or direct messages if you're not logged in
if viewer.is_anonymous:
privacy_levels = [p for p in privacy_levels if not p in ["followers", "direct"]]
# filter to only privided privacy levels
queryset = queryset.filter(privacy__in=privacy_levels)
# only include statuses the user follows
if following_only:
queryset = queryset.exclude(
~Q( # remove everythign except
Q(user__followers=viewer)
| Q(user=viewer) # user following
| Q(mention_users=viewer) # is self # mentions user
),
)
# exclude followers-only statuses the user doesn't follow
elif "followers" in privacy_levels:
queryset = queryset.exclude(
~Q( # user isn't following and it isn't their own status
Q(user__followers=viewer) | Q(user=viewer)
),
privacy="followers", # and the status is followers only
)
# exclude direct messages not intended for the user
if "direct" in privacy_levels:
try:
queryset = queryset.exclude(
~Q(Q(user=viewer) | Q(mention_users=viewer)), privacy="direct"
)
except FieldError:
queryset = queryset.exclude(~Q(user=viewer), privacy="direct")
return queryset
def handle_remote_webfinger(query):
"""webfingerin' other servers"""
user = None
@ -196,3 +145,11 @@ def load_date_in_user_tz_as_utc(date_str: str, user: models.User) -> datetime:
return date.replace(tzinfo=user_tz).astimezone(dateutil.tz.UTC)
except ParserError:
return None
def set_language(user, response):
"""Updates a user's language"""
if user.preferred_language:
translation.activate(user.preferred_language)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user.preferred_language)
return response

View file

@ -18,7 +18,7 @@ from django.views.decorators.http import require_POST
from bookwyrm import book_search, forms, models
from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.settings import PAGE_LENGTH
from .helpers import is_api_request, privacy_filter
from .helpers import is_api_request
from .helpers import get_user_from_username
@ -30,9 +30,10 @@ class Lists(View):
"""display a book list"""
# hide lists with no approved books
lists = (
models.List.objects.annotate(
item_count=Count("listitem", filter=Q(listitem__approved=True))
models.List.privacy_filter(
request.user, privacy_levels=["public", "followers"]
)
.annotate(item_count=Count("listitem", filter=Q(listitem__approved=True)))
.filter(item_count__gt=0)
.select_related("user")
.prefetch_related("listitem_set")
@ -40,10 +41,6 @@ class Lists(View):
.distinct()
)
lists = privacy_filter(
request.user, lists, privacy_levels=["public", "followers"]
)
paginated = Paginator(lists, 12)
data = {
"lists": paginated.get_page(request.GET.get("page")),
@ -89,8 +86,7 @@ class UserLists(View):
def get(self, request, username):
"""display a book list"""
user = get_user_from_username(request.user, username)
lists = models.List.objects.filter(user=user)
lists = privacy_filter(request.user, lists)
lists = models.List.privacy_filter(request.user).filter(user=user)
paginated = Paginator(lists, 12)
data = {

View file

@ -11,6 +11,7 @@ from django.views.decorators.debug import sensitive_variables, sensitive_post_pa
from bookwyrm import forms, models
from bookwyrm.settings import DOMAIN
from bookwyrm.views.helpers import set_language
# pylint: disable=no-self-use
@ -55,8 +56,8 @@ class Login(View):
login(request, user)
user.update_active_date()
if request.POST.get("first_login"):
return redirect("get-started-profile")
return redirect(request.GET.get("next", "/"))
return set_language(user, redirect("get-started-profile"))
return set_language(user, redirect(request.GET.get("next", "/")))
# maybe the user is pending email confirmation
if models.User.objects.filter(

View file

@ -38,7 +38,7 @@ class PasswordResetRequest(View):
# create a new reset code
code = models.PasswordReset.objects.create(user=user)
password_reset_email(code)
data = {"message": _(f"A password reset link sent to {email}")}
data = {"message": _(f"A password reset link was sent to {email}")}
return TemplateResponse(request, "password_reset_request.html", data)

View file

@ -11,6 +11,7 @@ from django.utils.decorators import method_decorator
from django.views import View
from bookwyrm import forms
from bookwyrm.views.helpers import set_language
# pylint: disable=no-self-use
@ -33,9 +34,9 @@ class EditUser(View):
data = {"form": form, "user": request.user}
return TemplateResponse(request, "preferences/edit_user.html", data)
save_user_form(form)
user = save_user_form(form)
return redirect("user-feed", request.user.localname)
return set_language(user, redirect("user-feed", request.user.localname))
def save_user_form(form):

View file

@ -4,7 +4,8 @@ from django.contrib.syndication.views import Feed
from django.template.loader import get_template
from django.utils.translation import gettext_lazy as _
from .helpers import get_user_from_username, privacy_filter
from bookwyrm import models
from .helpers import get_user_from_username
# pylint: disable=no-self-use, unused-argument
class RssFeed(Feed):
@ -35,11 +36,10 @@ class RssFeed(Feed):
def items(self, obj):
"""the user's activity feed"""
return privacy_filter(
return models.Status.privacy_filter(
obj,
obj.status_set.select_subclasses(),
privacy_levels=["public", "unlisted"],
)
).filter(user=obj)
def item_link(self, item):
"""link to the status"""

View file

@ -13,7 +13,7 @@ from bookwyrm.connectors import connector_manager
from bookwyrm.book_search import search, format_search_result
from bookwyrm.settings import PAGE_LENGTH
from bookwyrm.utils import regex
from .helpers import is_api_request, privacy_filter
from .helpers import is_api_request
from .helpers import handle_remote_webfinger
@ -108,9 +108,8 @@ def user_search(query, viewer, *_):
def list_search(query, viewer, *_):
"""any relevent lists?"""
return (
privacy_filter(
models.List.privacy_filter(
viewer,
models.List.objects,
privacy_levels=["public", "followers"],
)
.annotate(

View file

@ -17,7 +17,6 @@ from bookwyrm import forms, models
from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.settings import PAGE_LENGTH
from .helpers import is_api_request, get_user_from_username
from .helpers import privacy_filter
# pylint: disable=no-self-use
@ -33,7 +32,7 @@ class Shelf(View):
if is_self:
shelves = user.shelf_set.all()
else:
shelves = privacy_filter(request.user, user.shelf_set).all()
shelves = models.Shelf.privacy_filter(request.user).filter(user=user).all()
# get the shelf and make sure the logged in user should be able to see it
if shelf_identifier:
@ -58,16 +57,17 @@ class Shelf(View):
if is_api_request(request):
return ActivitypubResponse(shelf.to_activity(**request.GET))
reviews = models.Review.objects.filter(
reviews = models.Review.objects
if not is_self:
reviews = models.Review.privacy_filter(request.user)
reviews = reviews.filter(
user=user,
rating__isnull=False,
book__id=OuterRef("id"),
deleted=False,
).order_by("-published_date")
if not is_self:
reviews = privacy_filter(request.user, reviews)
books = books.annotate(
rating=Subquery(reviews.values("rating")[:1]),
shelved_date=F("shelfbook__shelved_date"),

View file

@ -12,7 +12,6 @@ from bookwyrm import models
from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.settings import PAGE_LENGTH
from .helpers import get_user_from_username, is_api_request
from .helpers import privacy_filter
# pylint: disable=no-self-use
@ -56,10 +55,10 @@ class User(View):
# user's posts
activities = (
privacy_filter(
models.Status.privacy_filter(
request.user,
user.status_set.select_subclasses(),
)
.filter(user=user)
.select_related(
"user",
"reply_parent",

View file

@ -9,7 +9,7 @@ from django.utils import timezone
from django.views.decorators.http import require_GET
from bookwyrm import models
from bookwyrm.settings import DOMAIN, VERSION, MEDIA_FULL_URL
from bookwyrm.settings import DOMAIN, VERSION, MEDIA_FULL_URL, STATIC_FULL_URL
@require_GET
@ -93,8 +93,7 @@ def instance_info(_):
status_count = models.Status.objects.filter(user__local=True, deleted=False).count()
site = models.SiteSettings.get()
logo_path = site.logo or "images/logo.png"
logo = f"{MEDIA_FULL_URL}{logo_path}"
logo = get_image_url(site.logo, "logo.png")
return JsonResponse(
{
"uri": DOMAIN,
@ -134,8 +133,14 @@ def host_meta(request):
def opensearch(request):
"""Open Search xml spec"""
site = models.SiteSettings.get()
logo_path = site.favicon or "images/favicon.png"
logo = f"{MEDIA_FULL_URL}{logo_path}"
image = get_image_url(site.favicon, "favicon.png")
return TemplateResponse(
request, "opensearch.xml", {"image": logo, "DOMAIN": DOMAIN}
request, "opensearch.xml", {"image": image, "DOMAIN": DOMAIN}
)
def get_image_url(obj, fallback):
"""helper for loading the full path to an image"""
if obj:
return f"{MEDIA_FULL_URL}{obj}"
return f"{STATIC_FULL_URL}images/{fallback}"

6
bw-dev
View file

@ -105,6 +105,9 @@ case "$CMD" in
collectstatic)
runweb python manage.py collectstatic --no-input
;;
add_locale)
runweb django-admin makemessages --no-wrap --ignore=venv -l $@
;;
makemessages)
runweb django-admin makemessages --no-wrap --ignore=venv --all $@
;;
@ -167,7 +170,8 @@ case "$CMD" in
echo " test [path]"
echo " pytest [path]"
echo " collectstatic"
echo " makemessages [locale]"
echo " add_locale [locale]"
echo " makemessages"
echo " compilemessages [locale]"
echo " build"
echo " clean"

3
crowdin.yml Normal file
View file

@ -0,0 +1,3 @@
files:
- source: /locale/en_US/LC_MESSAGES/django.po
translation: /locale/%locale_with_underscore%/LC_MESSAGES/django.po

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff