forked from mirrors/bookwyrm
Merge branch 'main' into book-file-links
This commit is contained in:
commit
37e03e8993
37 changed files with 1879 additions and 1636 deletions
|
@ -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))
|
||||
|
||||
|
|
|
@ -144,6 +144,7 @@ class EditUserForm(CustomForm):
|
|||
"default_post_privacy",
|
||||
"discoverable",
|
||||
"preferred_timezone",
|
||||
"preferred_language",
|
||||
]
|
||||
help_texts = {f: None for f in fields}
|
||||
|
||||
|
|
30
bookwyrm/migrations/0106_user_preferred_language.py
Normal file
30
bookwyrm/migrations/0106_user_preferred_language.py
Normal 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,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
6
bw-dev
|
@ -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
3
crowdin.yml
Normal 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
Loading…
Reference in a new issue