Revert formatting changes
This commit is contained in:
parent
64ea602f02
commit
1e0d547db5
3 changed files with 696 additions and 625 deletions
|
@ -1,94 +1,97 @@
|
||||||
""" Access the activity streams stored in Redis """
|
""" access the activity streams stored in redis """
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from bookwyrm import models
|
|
||||||
from bookwyrm . redis_store import RedisStore , r
|
|
||||||
from bookwyrm . tasks import app , STREAMS , IMPORT_TRIGGERED
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import signals, Q
|
from django.db.models import signals, Q
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from bookwyrm import models
|
||||||
|
from bookwyrm.redis_store import RedisStore, r
|
||||||
|
from bookwyrm.tasks import app, STREAMS, IMPORT_TRIGGERED
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ActivityStream(RedisStore):
|
class ActivityStream(RedisStore):
|
||||||
"""A category of activity stream (like home, local, books)"""
|
"""a category of activity stream (like home, local, books)"""
|
||||||
|
|
||||||
def stream_id(self, user_id):
|
def stream_id(self, user_id):
|
||||||
"""The redis key for this user's instance of this stream"""
|
"""the redis key for this user's instance of this stream"""
|
||||||
return f"{user_id}-{self.key}"
|
return f"{user_id}-{self.key}"
|
||||||
|
|
||||||
def unread_id(self, user_id):
|
def unread_id(self, user_id):
|
||||||
"""The redis key for this user's unread count for this stream"""
|
"""the redis key for this user's unread count for this stream"""
|
||||||
stream_id = self.stream_id(user_id)
|
stream_id = self.stream_id(user_id)
|
||||||
return f"{stream_id}-unread"
|
return f"{stream_id}-unread"
|
||||||
|
|
||||||
def unread_by_status_type_id(self, user_id):
|
def unread_by_status_type_id(self, user_id):
|
||||||
"""The redis key for this user's unread count for this stream"""
|
"""the redis key for this user's unread count for this stream"""
|
||||||
stream_id = self.stream_id(user_id)
|
stream_id = self.stream_id(user_id)
|
||||||
return f"{stream_id}-unread-by-type"
|
return f"{stream_id}-unread-by-type"
|
||||||
|
|
||||||
def get_rank(self, obj):
|
def get_rank(self, obj):
|
||||||
"""Statuses are sorted by date published"""
|
"""statuses are sorted by date published"""
|
||||||
return obj.published_date.timestamp()
|
return obj.published_date.timestamp()
|
||||||
|
|
||||||
def add_status(self, status, increment_unread=False):
|
def add_status(self, status, increment_unread=False):
|
||||||
"""Add a status to users' feeds"""
|
"""add a status to users' feeds"""
|
||||||
audience = self.get_audience(status)
|
audience = self.get_audience(status)
|
||||||
# The pipeline contains all the add-to-stream activities
|
# the pipeline contains all the add-to-stream activities
|
||||||
pipeline = self.add_object_to_stores(
|
pipeline = self.add_object_to_stores(
|
||||||
status ,
|
status, self.get_stores_for_users(audience), execute=False
|
||||||
self . get_stores_for_users (audience) ,
|
|
||||||
execute = False ,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if increment_unread:
|
if increment_unread:
|
||||||
for user_id in audience:
|
for user_id in audience:
|
||||||
# Add to the unread status count
|
# add to the unread status count
|
||||||
pipeline.incr(self.unread_id(user_id))
|
pipeline.incr(self.unread_id(user_id))
|
||||||
# Add to the unread status count for status type
|
# add to the unread status count for status type
|
||||||
pipeline.hincrby(
|
pipeline.hincrby(
|
||||||
self . unread_by_status_type_id (user_id) ,
|
self.unread_by_status_type_id(user_id), get_status_type(status), 1
|
||||||
get_status_type (status) ,
|
|
||||||
1 ,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# And go!
|
# and go!
|
||||||
pipeline.execute()
|
pipeline.execute()
|
||||||
|
|
||||||
def add_user_statuses(self, viewer, user):
|
def add_user_statuses(self, viewer, user):
|
||||||
"""Add a user's statuses to another user's feed"""
|
"""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)
|
# only add the statuses that the viewer should be able to see (ie, not dms)
|
||||||
statuses = models.Status.privacy_filter(viewer).filter(user=user)
|
statuses = models.Status.privacy_filter(viewer).filter(user=user)
|
||||||
self.bulk_add_objects_to_store(statuses, self.stream_id(viewer.id))
|
self.bulk_add_objects_to_store(statuses, self.stream_id(viewer.id))
|
||||||
|
|
||||||
def remove_user_statuses(self, viewer, user):
|
def remove_user_statuses(self, viewer, user):
|
||||||
"""Remove a user's status from another user's feed"""
|
"""remove a user's status from another user's feed"""
|
||||||
# Remove all so that followers only statuses are removed
|
# remove all so that followers only statuses are removed
|
||||||
statuses = user.status_set.all()
|
statuses = user.status_set.all()
|
||||||
self.bulk_remove_objects_from_store(statuses, self.stream_id(viewer.id))
|
self.bulk_remove_objects_from_store(statuses, self.stream_id(viewer.id))
|
||||||
|
|
||||||
def get_activity_stream(self, user):
|
def get_activity_stream(self, user):
|
||||||
"""Load the statuses to be displayed"""
|
"""load the statuses to be displayed"""
|
||||||
# Clear unreads for this feed
|
# clear unreads for this feed
|
||||||
r.set(self.unread_id(user.id), 0)
|
r.set(self.unread_id(user.id), 0)
|
||||||
r.delete(self.unread_by_status_type_id(user.id))
|
r.delete(self.unread_by_status_type_id(user.id))
|
||||||
|
|
||||||
statuses = self.get_store(self.stream_id(user.id))
|
statuses = self.get_store(self.stream_id(user.id))
|
||||||
return models . Status . objects . select_subclasses ()
|
return (
|
||||||
|
models.Status.objects.select_subclasses()
|
||||||
.filter(id__in=statuses)
|
.filter(id__in=statuses)
|
||||||
. select_related ( "user" , "reply_parent" , "comment__book" , "review__book" , "quotation__book" )
|
.select_related(
|
||||||
|
"user",
|
||||||
|
"reply_parent",
|
||||||
|
"comment__book",
|
||||||
|
"review__book",
|
||||||
|
"quotation__book",
|
||||||
|
)
|
||||||
.prefetch_related("mention_books", "mention_users")
|
.prefetch_related("mention_books", "mention_users")
|
||||||
.order_by("-published_date")
|
.order_by("-published_date")
|
||||||
|
)
|
||||||
|
|
||||||
def get_unread_count(self, user):
|
def get_unread_count(self, user):
|
||||||
"""Get the unread status count for this user's feed"""
|
"""get the unread status count for this user's feed"""
|
||||||
return int(r.get(self.unread_id(user.id)) or 0)
|
return int(r.get(self.unread_id(user.id)) or 0)
|
||||||
|
|
||||||
def get_unread_count_by_status_type(self, user):
|
def get_unread_count_by_status_type(self, user):
|
||||||
"""Get the unread status count for this user's feed's status types"""
|
"""get the unread status count for this user's feed's status types"""
|
||||||
status_types = r.hgetall(self.unread_by_status_type_id(user.id))
|
status_types = r.hgetall(self.unread_by_status_type_id(user.id))
|
||||||
return {
|
return {
|
||||||
str(key.decode("utf-8")): int(value) or 0
|
str(key.decode("utf-8")): int(value) or 0
|
||||||
|
@ -96,54 +99,59 @@ class ActivityStream (RedisStore) :
|
||||||
}
|
}
|
||||||
|
|
||||||
def populate_streams(self, user):
|
def populate_streams(self, user):
|
||||||
"""Go from zero to a timeline"""
|
"""go from zero to a timeline"""
|
||||||
self.populate_store(self.stream_id(user.id))
|
self.populate_store(self.stream_id(user.id))
|
||||||
|
|
||||||
def _get_audience ( self , status) :
|
def _get_audience(self, status): # pylint: disable=no-self-use
|
||||||
"""Given a status, what users should see it, excluding the author"""
|
"""given a status, what users should see it, excluding the author"""
|
||||||
# Direct messages don't appear in feeds, direct comments/reviews/etc do
|
# direct messages don't appear in feeds, direct comments/reviews/etc do
|
||||||
if status.privacy == "direct" and status.status_type == "Note":
|
if status.privacy == "direct" and status.status_type == "Note":
|
||||||
return models.User.objects.none()
|
return models.User.objects.none()
|
||||||
|
|
||||||
# Everybody who could plausibly see this status
|
# everybody who could plausibly see this status
|
||||||
audience = models . User . objects
|
audience = models.User.objects.filter(
|
||||||
. filter ( is_active = True , local = True ) # We only create feeds for users of this instance
|
is_active=True,
|
||||||
. exclude (
|
local=True, # we only create feeds for users of this instance
|
||||||
Q ( id__in = status . user . blocks . all () ) |
|
).exclude(
|
||||||
Q ( blocks = status . user) # Not blocked
|
Q(id__in=status.user.blocks.all()) | Q(blocks=status.user) # not blocked
|
||||||
)
|
)
|
||||||
|
|
||||||
# Only visible to the poster and mentioned users
|
# only visible to the poster and mentioned users
|
||||||
if status.privacy == "direct":
|
if status.privacy == "direct":
|
||||||
audience = audience . filter ( Q ( id__in = status . mention_users . all () ) ) # If the user is mentioned
|
audience = audience.filter(
|
||||||
|
Q(id__in=status.mention_users.all()) # if the user is mentioned
|
||||||
|
)
|
||||||
|
|
||||||
# Don't show replies to statuses the user can't see
|
# don't show replies to statuses the user can't see
|
||||||
elif status.reply_parent and status.reply_parent.privacy == "followers":
|
elif status.reply_parent and status.reply_parent.privacy == "followers":
|
||||||
audience = audience.filter(
|
audience = audience.filter(
|
||||||
Q ( id = status . reply_parent . user . id ) | # If the user is the OG author
|
Q(id=status.reply_parent.user.id) # if the user is the OG author
|
||||||
( Q ( following = status . user ) & Q ( following = status . reply_parent . user ) ) # If the user is following both authors
|
| (
|
||||||
|
Q(following=status.user) & Q(following=status.reply_parent.user)
|
||||||
|
) # if the user is following both authors
|
||||||
)
|
)
|
||||||
|
|
||||||
# Only visible to the poster's followers and tagged users
|
# only visible to the poster's followers and tagged users
|
||||||
elif status.privacy == "followers":
|
elif status.privacy == "followers":
|
||||||
audience = audience . filter ( Q ( following = status . user ) ) # If the user is following the author
|
audience = audience.filter(
|
||||||
|
Q(following=status.user) # if the user is following the author
|
||||||
|
)
|
||||||
return audience.distinct("id")
|
return audience.distinct("id")
|
||||||
|
|
||||||
def get_audience(self, status):
|
def get_audience(self, status):
|
||||||
"""Given a status, what users should see it"""
|
"""given a status, what users should see it"""
|
||||||
audience = self._get_audience(status).values_list("id", flat=True)
|
audience = self._get_audience(status).values_list("id", flat=True)
|
||||||
status_author = models . User . objects
|
status_author = models.User.objects.filter(
|
||||||
. filter ( is_active = True , local = True , id = status . user . id )
|
is_active=True, local=True, id=status.user.id
|
||||||
. values_list ( "id" , flat = True )
|
).values_list("id", flat=True)
|
||||||
return list(set(audience) | set(status_author))
|
return list(set(audience) | set(status_author))
|
||||||
|
|
||||||
def get_stores_for_users(self, user_ids):
|
def get_stores_for_users(self, user_ids):
|
||||||
"""Convert a list of user ids into Redis store ids"""
|
"""convert a list of user ids into redis store ids"""
|
||||||
return [self.stream_id(user_id) for user_id in user_ids]
|
return [self.stream_id(user_id) for user_id in user_ids]
|
||||||
|
|
||||||
def get_statuses_for_user ( self , user ) :
|
def get_statuses_for_user(self, user): # pylint: disable=no-self-use
|
||||||
"""Given a user, what statuses should they see on this stream"""
|
"""given a user, what statuses should they see on this stream"""
|
||||||
return models.Status.privacy_filter(
|
return models.Status.privacy_filter(
|
||||||
user,
|
user,
|
||||||
privacy_levels=["public", "unlisted", "followers"],
|
privacy_levels=["public", "unlisted", "followers"],
|
||||||
|
@ -155,91 +163,104 @@ class ActivityStream (RedisStore) :
|
||||||
|
|
||||||
|
|
||||||
class HomeStream(ActivityStream):
|
class HomeStream(ActivityStream):
|
||||||
"""Users you follow"""
|
"""users you follow"""
|
||||||
|
|
||||||
key = "home"
|
key = "home"
|
||||||
|
|
||||||
def get_audience(self, status):
|
def get_audience(self, status):
|
||||||
audience = super()._get_audience(status)
|
audience = super()._get_audience(status)
|
||||||
# If the user is following the author
|
# if the user is following the author
|
||||||
audience = audience.filter(following=status.user).values_list("id", flat=True)
|
audience = audience.filter(following=status.user).values_list("id", flat=True)
|
||||||
# If the user is the post's author
|
# if the user is the post's author
|
||||||
status_author = models . User . objects
|
status_author = models.User.objects.filter(
|
||||||
. filter ( is_active = True , local = True , id = status . user . id )
|
is_active=True, local=True, id=status.user.id
|
||||||
. values_list ( "id" , flat = True )
|
).values_list("id", flat=True)
|
||||||
return list(set(audience) | set(status_author))
|
return list(set(audience) | set(status_author))
|
||||||
|
|
||||||
def get_statuses_for_user(self, user):
|
def get_statuses_for_user(self, user):
|
||||||
return models.Status.privacy_filter(
|
return models.Status.privacy_filter(
|
||||||
user,
|
user,
|
||||||
privacy_levels=["public", "unlisted", "followers"],
|
privacy_levels=["public", "unlisted", "followers"],
|
||||||
) . exclude ( ~ Q ( # Remove everything except
|
).exclude(
|
||||||
Q ( user__followers = user ) | # User following
|
~Q( # remove everything except
|
||||||
Q ( user = user ) | # Is self
|
Q(user__followers=user) # user following
|
||||||
Q ( mention_users = user ) # Mentions user
|
| Q(user=user) # is self
|
||||||
) )
|
| Q(mention_users=user) # mentions user
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LocalStream(ActivityStream):
|
class LocalStream(ActivityStream):
|
||||||
"""Users you follow"""
|
"""users you follow"""
|
||||||
|
|
||||||
key = "local"
|
key = "local"
|
||||||
|
|
||||||
def get_audience(self, status):
|
def get_audience(self, status):
|
||||||
# This stream wants no part in non-public statuses
|
# this stream wants no part in non-public statuses
|
||||||
if status.privacy != "public" or not status.user.local:
|
if status.privacy != "public" or not status.user.local:
|
||||||
return []
|
return []
|
||||||
return super().get_audience(status)
|
return super().get_audience(status)
|
||||||
|
|
||||||
def get_statuses_for_user(self, user):
|
def get_statuses_for_user(self, user):
|
||||||
# All public statuses by a local user
|
# all public statuses by a local user
|
||||||
return models . Status
|
return models.Status.privacy_filter(
|
||||||
. privacy_filter ( user , privacy_levels = [ "public" ] )
|
user,
|
||||||
. filter ( user__local = True )
|
privacy_levels=["public"],
|
||||||
|
).filter(user__local=True)
|
||||||
|
|
||||||
|
|
||||||
class BooksStream(ActivityStream):
|
class BooksStream(ActivityStream):
|
||||||
"""Books on your shelves"""
|
"""books on your shelves"""
|
||||||
|
|
||||||
key = "books"
|
key = "books"
|
||||||
|
|
||||||
def _get_audience(self, status):
|
def _get_audience(self, status):
|
||||||
"""Anyone with the mentioned book on their shelves"""
|
"""anyone with the mentioned book on their shelves"""
|
||||||
work = status . book . parent_work if hasattr ( status , "book" )
|
work = (
|
||||||
|
status.book.parent_work
|
||||||
|
if hasattr(status, "book")
|
||||||
else status.mention_books.first().parent_work
|
else status.mention_books.first().parent_work
|
||||||
|
)
|
||||||
|
|
||||||
audience = super()._get_audience(status)
|
audience = super()._get_audience(status)
|
||||||
return audience.filter(shelfbook__book__parent_work=work)
|
return audience.filter(shelfbook__book__parent_work=work)
|
||||||
|
|
||||||
def get_audience(self, status):
|
def get_audience(self, status):
|
||||||
# Only show public statuses on the books feed, and only statuses that mention books
|
# only show public statuses on the books feed,
|
||||||
if (
|
# and only statuses that mention books
|
||||||
status . privacy != "public" or
|
if status.privacy != "public" or not (
|
||||||
not ( status . mention_books . exists () or hasattr ( status , "book" ) )
|
status.mention_books.exists() or hasattr(status, "book")
|
||||||
):
|
):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return super().get_audience(status)
|
return super().get_audience(status)
|
||||||
|
|
||||||
def get_statuses_for_user(self, user):
|
def get_statuses_for_user(self, user):
|
||||||
"""Any public status that mentions the user's books"""
|
"""any public status that mentions the user's books"""
|
||||||
books = user . shelfbook_set
|
books = user.shelfbook_set.values_list(
|
||||||
. values_list ( "book__parent_work__id" , flat = True )
|
"book__parent_work__id", flat=True
|
||||||
. distinct ()
|
).distinct()
|
||||||
return models . Status
|
return (
|
||||||
. privacy_filter ( user , privacy_levels = [ "public" ] )
|
models.Status.privacy_filter(
|
||||||
|
user,
|
||||||
|
privacy_levels=["public"],
|
||||||
|
)
|
||||||
.filter(
|
.filter(
|
||||||
Q ( comment__book__parent_work__id__in = books ) |
|
Q(comment__book__parent_work__id__in=books)
|
||||||
Q ( quotation__book__parent_work__id__in = books ) |
|
| Q(quotation__book__parent_work__id__in=books)
|
||||||
Q ( review__book__parent_work__id__in = books ) |
|
| Q(review__book__parent_work__id__in=books)
|
||||||
Q ( mention_books__parent_work__id__in = books )
|
| Q(mention_books__parent_work__id__in=books)
|
||||||
)
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
def add_book_statuses(self, user, book):
|
def add_book_statuses(self, user, book):
|
||||||
"""Add statuses about a book to a user's feed"""
|
"""add statuses about a book to a user's feed"""
|
||||||
work = book.parent_work
|
work = book.parent_work
|
||||||
statuses = models . Status . privacy_filter ( user , privacy_levels = [ "public" ] )
|
statuses = models.Status.privacy_filter(
|
||||||
|
user,
|
||||||
|
privacy_levels=["public"],
|
||||||
|
)
|
||||||
|
|
||||||
book_comments = statuses.filter(Q(comment__book__parent_work=work))
|
book_comments = statuses.filter(Q(comment__book__parent_work=work))
|
||||||
book_quotations = statuses.filter(Q(quotation__book__parent_work=work))
|
book_quotations = statuses.filter(Q(quotation__book__parent_work=work))
|
||||||
|
@ -252,9 +273,12 @@ class BooksStream (ActivityStream) :
|
||||||
self.bulk_add_objects_to_store(book_mentions, self.stream_id(user.id))
|
self.bulk_add_objects_to_store(book_mentions, self.stream_id(user.id))
|
||||||
|
|
||||||
def remove_book_statuses(self, user, book):
|
def remove_book_statuses(self, user, book):
|
||||||
"""Add statuses about a book to a user's feed"""
|
"""add statuses about a book to a user's feed"""
|
||||||
work = book.parent_work
|
work = book.parent_work
|
||||||
statuses = models . Status . privacy_filter ( user , privacy_levels = [ "public" ] )
|
statuses = models.Status.privacy_filter(
|
||||||
|
user,
|
||||||
|
privacy_levels=["public"],
|
||||||
|
)
|
||||||
|
|
||||||
book_comments = statuses.filter(Q(comment__book__parent_work=work))
|
book_comments = statuses.filter(Q(comment__book__parent_work=work))
|
||||||
book_quotations = statuses.filter(Q(quotation__book__parent_work=work))
|
book_quotations = statuses.filter(Q(quotation__book__parent_work=work))
|
||||||
|
@ -267,7 +291,7 @@ class BooksStream (ActivityStream) :
|
||||||
self.bulk_remove_objects_from_store(book_mentions, self.stream_id(user.id))
|
self.bulk_remove_objects_from_store(book_mentions, self.stream_id(user.id))
|
||||||
|
|
||||||
|
|
||||||
# Determine which streams are enabled in settings.py
|
# determine which streams are enabled in settings.py
|
||||||
streams = {
|
streams = {
|
||||||
"home": HomeStream(),
|
"home": HomeStream(),
|
||||||
"local": LocalStream(),
|
"local": LocalStream(),
|
||||||
|
@ -276,9 +300,10 @@ streams = {
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.post_save)
|
@receiver(signals.post_save)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
def add_status_on_create(sender, instance, created, *args, **kwargs):
|
def add_status_on_create(sender, instance, created, *args, **kwargs):
|
||||||
"""Add newly created statuses to activity feeds"""
|
"""add newly created statuses to activity feeds"""
|
||||||
# We're only interested in new statuses
|
# we're only interested in new statuses
|
||||||
if not issubclass(sender, models.Status):
|
if not issubclass(sender, models.Status):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -286,32 +311,33 @@ def add_status_on_create ( sender , instance , created , * args , ** kwargs ) :
|
||||||
remove_status_task.delay(instance.id)
|
remove_status_task.delay(instance.id)
|
||||||
return
|
return
|
||||||
|
|
||||||
# We don't want to create multiple add_status_tasks for each status, and because the transactions are atomic,
|
# We don't want to create multiple add_status_tasks for each status, and because
|
||||||
# on_commit won't run until the status is ready to add.
|
# the transactions are atomic, on_commit won't run until the status is ready to add.
|
||||||
if not created:
|
if not created:
|
||||||
return
|
return
|
||||||
|
|
||||||
# When creating new things, gotta wait on the transaction
|
# when creating new things, gotta wait on the transaction
|
||||||
transaction . on_commit ( lambda : add_status_on_create_command ( sender , instance , created ) )
|
transaction.on_commit(
|
||||||
|
lambda: add_status_on_create_command(sender, instance, created)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_status_on_create_command(sender, instance, created):
|
def add_status_on_create_command(sender, instance, created):
|
||||||
"""Runs this code only after the database commit completes"""
|
"""runs this code only after the database commit completes"""
|
||||||
# Boosts trigger 'saves" twice, so don't bother duplicating the task
|
# boosts trigger 'saves" twice, so don't bother duplicating the task
|
||||||
if sender == models.Boost and not created:
|
if sender == models.Boost and not created:
|
||||||
return
|
return
|
||||||
|
|
||||||
priority = STREAMS
|
priority = STREAMS
|
||||||
# Check if this is an old status, de-prioritize if so
|
# check if this is an old status, de-prioritize if so
|
||||||
# (this will happen if federation is very slow, or, more expectedly, on csv import)
|
# (this will happen if federation is very slow, or, more expectedly, on csv import)
|
||||||
if (
|
if instance.published_date < timezone.now() - timedelta(
|
||||||
instance . published_date < timezone . now () - timedelta ( days = 1 ) or
|
days=1
|
||||||
instance.created_date < instance.published_date - timedelta(days=1)
|
) or instance.created_date < instance.published_date - timedelta(days=1):
|
||||||
) :
|
# a backdated status from a local user is an import, don't add it
|
||||||
# A backdated status from a local user is an import, don't add it
|
|
||||||
if instance.user.local:
|
if instance.user.local:
|
||||||
return
|
return
|
||||||
# An out of date remote status is a low priority but should be added
|
# an out of date remote status is a low priority but should be added
|
||||||
priority = IMPORT_TRIGGERED
|
priority = IMPORT_TRIGGERED
|
||||||
|
|
||||||
add_status_task.apply_async(
|
add_status_task.apply_async(
|
||||||
|
@ -325,57 +351,56 @@ def add_status_on_create_command ( sender , instance , created ) :
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.post_delete, sender=models.Boost)
|
@receiver(signals.post_delete, sender=models.Boost)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
def remove_boost_on_delete(sender, instance, *args, **kwargs):
|
def remove_boost_on_delete(sender, instance, *args, **kwargs):
|
||||||
"""Boosts are deleted"""
|
"""boosts are deleted"""
|
||||||
# Remove the boost
|
# remove the boost
|
||||||
remove_status_task.delay(instance.id)
|
remove_status_task.delay(instance.id)
|
||||||
# Re-add the original status
|
# re-add the original status
|
||||||
add_status_task.delay(instance.boosted_status.id)
|
add_status_task.delay(instance.boosted_status.id)
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.post_save, sender=models.UserFollows)
|
@receiver(signals.post_save, sender=models.UserFollows)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
def add_statuses_on_follow(sender, instance, created, *args, **kwargs):
|
def add_statuses_on_follow(sender, instance, created, *args, **kwargs):
|
||||||
"""Add a newly followed user's statuses to feeds"""
|
"""add a newly followed user's statuses to feeds"""
|
||||||
if not created or not instance.user_subject.local:
|
if not created or not instance.user_subject.local:
|
||||||
return
|
return
|
||||||
add_user_statuses_task.delay(
|
add_user_statuses_task.delay(
|
||||||
instance . user_subject . id ,
|
instance.user_subject.id, instance.user_object.id, stream_list=["home"]
|
||||||
instance . user_object . id ,
|
|
||||||
stream_list = [ "home" ] ,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.post_delete, sender=models.UserFollows)
|
@receiver(signals.post_delete, sender=models.UserFollows)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
def remove_statuses_on_unfollow(sender, instance, *args, **kwargs):
|
def remove_statuses_on_unfollow(sender, instance, *args, **kwargs):
|
||||||
"""Remove statuses from a feed on unfollow"""
|
"""remove statuses from a feed on unfollow"""
|
||||||
if not instance.user_subject.local:
|
if not instance.user_subject.local:
|
||||||
return
|
return
|
||||||
remove_user_statuses_task.delay(
|
remove_user_statuses_task.delay(
|
||||||
instance . user_subject . id ,
|
instance.user_subject.id, instance.user_object.id, stream_list=["home"]
|
||||||
instance . user_object . id ,
|
|
||||||
stream_list = [ "home" ] ,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.post_save, sender=models.UserBlocks)
|
@receiver(signals.post_save, sender=models.UserBlocks)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
def remove_statuses_on_block(sender, instance, *args, **kwargs):
|
def remove_statuses_on_block(sender, instance, *args, **kwargs):
|
||||||
"""Remove statuses from all feeds on block"""
|
"""remove statuses from all feeds on block"""
|
||||||
# Blocks apply ot all feeds
|
# blocks apply ot all feeds
|
||||||
if instance.user_subject.local:
|
if instance.user_subject.local:
|
||||||
remove_user_statuses_task.delay(
|
remove_user_statuses_task.delay(
|
||||||
instance . user_subject . id ,
|
instance.user_subject.id, instance.user_object.id
|
||||||
instance . user_object . id ,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# And in both directions
|
# and in both directions
|
||||||
if instance.user_object.local:
|
if instance.user_object.local:
|
||||||
remove_user_statuses_task.delay(
|
remove_user_statuses_task.delay(
|
||||||
instance . user_object . id ,
|
instance.user_object.id, instance.user_subject.id
|
||||||
instance . user_subject . id ,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.post_delete, sender=models.UserBlocks)
|
@receiver(signals.post_delete, sender=models.UserBlocks)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
def add_statuses_on_unblock(sender, instance, *args, **kwargs):
|
def add_statuses_on_unblock(sender, instance, *args, **kwargs):
|
||||||
"""add statuses back to all feeds on unblock"""
|
"""add statuses back to all feeds on unblock"""
|
||||||
# make sure there isn't a block in the other direction
|
# make sure there isn't a block in the other direction
|
||||||
|
@ -405,6 +430,7 @@ def add_statuses_on_unblock(sender, instance, *args, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.post_save, sender=models.User)
|
@receiver(signals.post_save, sender=models.User)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
def populate_streams_on_account_create(sender, instance, created, *args, **kwargs):
|
def populate_streams_on_account_create(sender, instance, created, *args, **kwargs):
|
||||||
"""build a user's feeds when they join"""
|
"""build a user's feeds when they join"""
|
||||||
if not created or not instance.local:
|
if not created or not instance.local:
|
||||||
|
@ -421,6 +447,7 @@ def populate_streams_on_account_create_command(instance_id):
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.pre_save, sender=models.ShelfBook)
|
@receiver(signals.pre_save, sender=models.ShelfBook)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
def add_statuses_on_shelve(sender, instance, *args, **kwargs):
|
def add_statuses_on_shelve(sender, instance, *args, **kwargs):
|
||||||
"""update books stream when user shelves a book"""
|
"""update books stream when user shelves a book"""
|
||||||
if not instance.user.local:
|
if not instance.user.local:
|
||||||
|
@ -436,6 +463,7 @@ def add_statuses_on_shelve(sender, instance, *args, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.post_delete, sender=models.ShelfBook)
|
@receiver(signals.post_delete, sender=models.ShelfBook)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
def remove_statuses_on_unshelve(sender, instance, *args, **kwargs):
|
def remove_statuses_on_unshelve(sender, instance, *args, **kwargs):
|
||||||
"""update books stream when user unshelves a book"""
|
"""update books stream when user unshelves a book"""
|
||||||
if not instance.user.local:
|
if not instance.user.local:
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
"""Do further startup configuration and initialization"""
|
"""Do further startup configuration and initialization"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import urllib
|
import urllib
|
||||||
|
import logging
|
||||||
|
|
||||||
from bookwyrm import settings
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
from bookwyrm import settings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -26,7 +23,7 @@ def download_file ( url , destination) :
|
||||||
logger.error("Failed to download file %s: %s", url, err)
|
logger.error("Failed to download file %s: %s", url, err)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
logger.error("Couldn't open font file %s for writing: %s", destination, err)
|
logger.error("Couldn't open font file %s for writing: %s", destination, err)
|
||||||
except Exception as err :
|
except Exception as err: # pylint:disable=broad-except
|
||||||
logger.error("Unknown error in file download: %s", err)
|
logger.error("Unknown error in file download: %s", err)
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,7 +34,7 @@ class BookwyrmConfig (AppConfig) :
|
||||||
verbose_name = "BookWyrm"
|
verbose_name = "BookWyrm"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
"""Set up preview image files, if desired"""
|
"""set up OTLP and preview image files, if desired"""
|
||||||
if settings.ENABLE_PREVIEW_IMAGES and settings.FONTS:
|
if settings.ENABLE_PREVIEW_IMAGES and settings.FONTS:
|
||||||
# Download any fonts that we don't have yet
|
# Download any fonts that we don't have yet
|
||||||
logger.debug("Downloading fonts..")
|
logger.debug("Downloading fonts..")
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
""" Bookwyrm settings and configuration """
|
""" bookwyrm settings and configuration """
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import requests
|
|
||||||
|
|
||||||
from django . core . exceptions import ImproperlyConfigured
|
|
||||||
from django . utils . translation import gettext_lazy as _
|
|
||||||
from environs import Env
|
|
||||||
from typing import AnyStr
|
from typing import AnyStr
|
||||||
|
|
||||||
|
from environs import Env
|
||||||
|
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=line-too-long
|
||||||
|
|
||||||
env = Env()
|
env = Env()
|
||||||
env.read_env("/etc/bookwyrm/config.env", recurse=False)
|
env.read_env("/etc/bookwyrm/config.env", recurse=False)
|
||||||
DOMAIN = env("DOMAIN")
|
DOMAIN = env("DOMAIN")
|
||||||
|
@ -29,12 +29,13 @@ RELEASE_API = env (
|
||||||
|
|
||||||
PAGE_LENGTH = env.int("PAGE_LENGTH", 15)
|
PAGE_LENGTH = env.int("PAGE_LENGTH", 15)
|
||||||
DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English")
|
DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English")
|
||||||
# TODO: Extend maximum age to 1 year once termination of active sessions is implemented (see bookwyrm-social#2278, bookwyrm-social#3082).
|
# TODO: extend maximum age to 1 year once termination of active sessions
|
||||||
|
# is implemented (see bookwyrm-social#2278, bookwyrm-social#3082).
|
||||||
SESSION_COOKIE_AGE = env.int("SESSION_COOKIE_AGE", 3600 * 24 * 30) # 1 month
|
SESSION_COOKIE_AGE = env.int("SESSION_COOKIE_AGE", 3600 * 24 * 30) # 1 month
|
||||||
|
|
||||||
JS_CACHE = "8a89cad7"
|
JS_CACHE = "8a89cad7"
|
||||||
|
|
||||||
# Email
|
# email
|
||||||
EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")
|
EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")
|
||||||
EMAIL_HOST = env("EMAIL_HOST")
|
EMAIL_HOST = env("EMAIL_HOST")
|
||||||
EMAIL_PORT = env.int("EMAIL_PORT", 587)
|
EMAIL_PORT = env.int("EMAIL_PORT", 587)
|
||||||
|
@ -48,7 +49,9 @@ EMAIL_SENDER = f"{EMAIL_SENDER_NAME}@{EMAIL_SENDER_DOMAIN}"
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
BASE_DIR: AnyStr = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR: AnyStr = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
LOCALE_PATHS = [ os . path . join ( BASE_DIR , "locale" ) ]
|
LOCALE_PATHS = [
|
||||||
|
os.path.join(BASE_DIR, "locale"),
|
||||||
|
]
|
||||||
LANGUAGE_COOKIE_NAME = env.str("LANGUAGE_COOKIE_NAME", "django_language")
|
LANGUAGE_COOKIE_NAME = env.str("LANGUAGE_COOKIE_NAME", "django_language")
|
||||||
|
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static"))
|
STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static"))
|
||||||
|
@ -77,11 +80,11 @@ FONT_DIR = os . path . join ( STATIC_ROOT , "fonts" )
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: Don't run with debug turned on in production
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = env.bool("DEBUG", True)
|
DEBUG = env.bool("DEBUG", True)
|
||||||
USE_HTTPS = env.bool("USE_HTTPS", not DEBUG)
|
USE_HTTPS = env.bool("USE_HTTPS", not DEBUG)
|
||||||
|
|
||||||
# SECURITY WARNING: Keep the secret key used in production secret
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = env("SECRET_KEY")
|
SECRET_KEY = env("SECRET_KEY")
|
||||||
if not DEBUG and SECRET_KEY == "7(2w1sedok=aznpq)ta1mc4i%4h=xx@hxwx*o57ctsuml0x%fr":
|
if not DEBUG and SECRET_KEY == "7(2w1sedok=aznpq)ta1mc4i%4h=xx@hxwx*o57ctsuml0x%fr":
|
||||||
raise ImproperlyConfigured("You must change the SECRET_KEY env variable")
|
raise ImproperlyConfigured("You must change the SECRET_KEY env variable")
|
||||||
|
@ -145,25 +148,39 @@ TEMPLATES = [
|
||||||
|
|
||||||
LOG_LEVEL = env("LOG_LEVEL", "INFO").upper()
|
LOG_LEVEL = env("LOG_LEVEL", "INFO").upper()
|
||||||
# Override aspects of the default handler to our taste
|
# Override aspects of the default handler to our taste
|
||||||
# See https://docs.djangoproject.com/en/3.2/topics/logging/#default-logging-configuration for a reference to the defaults we're overriding
|
# See https://docs.djangoproject.com/en/3.2/topics/logging/#default-logging-configuration
|
||||||
# It seems that in order to override anything you have to include its entire dependency tree (handlers and filters) which makes this a bit verbose
|
# for a reference to the defaults we're overriding
|
||||||
|
#
|
||||||
|
# It seems that in order to override anything you have to include its
|
||||||
|
# entire dependency tree (handlers and filters) which makes this a
|
||||||
|
# bit verbose
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"disable_existing_loggers": False,
|
"disable_existing_loggers": False,
|
||||||
"filters": {
|
"filters": {
|
||||||
# These are copied from the default configuration, required for implementing mail_admins below
|
# These are copied from the default configuration, required for
|
||||||
"require_debug_false" : { "()" : "django.utils.log.RequireDebugFalse" } ,
|
# implementing mail_admins below
|
||||||
"require_debug_true": { "()" : "django.utils.log.RequireDebugTrue" } ,
|
"require_debug_false": {
|
||||||
"ignore_missing_variable" : { "()" : "bookwyrm.utils.log.IgnoreVariableDoesNotExist" } ,
|
"()": "django.utils.log.RequireDebugFalse",
|
||||||
|
},
|
||||||
|
"require_debug_true": {
|
||||||
|
"()": "django.utils.log.RequireDebugTrue",
|
||||||
|
},
|
||||||
|
"ignore_missing_variable": {
|
||||||
|
"()": "bookwyrm.utils.log.IgnoreVariableDoesNotExist",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"handlers": {
|
"handlers": {
|
||||||
# Overrides the default handler to make it log to console regardless of the DEBUG setting (default is to not log to console if DEBUG=False)
|
# Overrides the default handler to make it log to console
|
||||||
|
# regardless of the DEBUG setting (default is to not log to
|
||||||
|
# console if DEBUG=False)
|
||||||
"console": {
|
"console": {
|
||||||
"level": LOG_LEVEL,
|
"level": LOG_LEVEL,
|
||||||
"filters": ["ignore_missing_variable"],
|
"filters": ["ignore_missing_variable"],
|
||||||
"class": "logging.StreamHandler",
|
"class": "logging.StreamHandler",
|
||||||
},
|
},
|
||||||
# This is copied as-is from the default logger, and is required for the Django section below
|
# This is copied as-is from the default logger, and is
|
||||||
|
# required for the django section below
|
||||||
"mail_admins": {
|
"mail_admins": {
|
||||||
"level": "ERROR",
|
"level": "ERROR",
|
||||||
"filters": ["require_debug_false"],
|
"filters": ["require_debug_false"],
|
||||||
|
@ -171,13 +188,16 @@ LOGGING = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"loggers": {
|
"loggers": {
|
||||||
# Install our new console handler for Django's logger, and override the log level while we're at it
|
# Install our new console handler for Django's logger, and
|
||||||
|
# override the log level while we're at it
|
||||||
"django": {
|
"django": {
|
||||||
"handlers": ["console", "mail_admins"],
|
"handlers": ["console", "mail_admins"],
|
||||||
"level": LOG_LEVEL,
|
"level": LOG_LEVEL,
|
||||||
},
|
},
|
||||||
"django.utils.autoreload" : { "level" : "INFO" } ,
|
"django.utils.autoreload": {
|
||||||
# Add a Bookwyrm-specific logger
|
"level": "INFO",
|
||||||
|
},
|
||||||
|
# Add a bookwyrm-specific logger
|
||||||
"bookwyrm": {
|
"bookwyrm": {
|
||||||
"handlers": ["console"],
|
"handlers": ["console"],
|
||||||
"level": LOG_LEVEL,
|
"level": LOG_LEVEL,
|
||||||
|
@ -192,16 +212,16 @@ STATICFILES_FINDERS = [
|
||||||
]
|
]
|
||||||
|
|
||||||
SASS_PROCESSOR_INCLUDE_FILE_PATTERN = r"^.+\.[s]{0,1}(?:a|c)ss$"
|
SASS_PROCESSOR_INCLUDE_FILE_PATTERN = r"^.+\.[s]{0,1}(?:a|c)ss$"
|
||||||
# When debug is disabled, make sure to compile themes once with `./bw-dev compile_themes`
|
# when debug is disabled, make sure to compile themes once with `./bw-dev compile_themes`
|
||||||
SASS_PROCESSOR_ENABLED = DEBUG
|
SASS_PROCESSOR_ENABLED = DEBUG
|
||||||
|
|
||||||
# Minify CSS in production but not dev
|
# minify css is production but not dev
|
||||||
if not DEBUG:
|
if not DEBUG:
|
||||||
SASS_OUTPUT_STYLE = "compressed"
|
SASS_OUTPUT_STYLE = "compressed"
|
||||||
|
|
||||||
WSGI_APPLICATION = "bookwyrm.wsgi.application"
|
WSGI_APPLICATION = "bookwyrm.wsgi.application"
|
||||||
|
|
||||||
# Redis/activity streams settings
|
# redis/activity streams settings
|
||||||
REDIS_ACTIVITY_HOST = env("REDIS_ACTIVITY_HOST", "localhost")
|
REDIS_ACTIVITY_HOST = env("REDIS_ACTIVITY_HOST", "localhost")
|
||||||
REDIS_ACTIVITY_PORT = env.int("REDIS_ACTIVITY_PORT", 6379)
|
REDIS_ACTIVITY_PORT = env.int("REDIS_ACTIVITY_PORT", 6379)
|
||||||
REDIS_ACTIVITY_PASSWORD = requests.utils.quote(env("REDIS_ACTIVITY_PASSWORD", ""))
|
REDIS_ACTIVITY_PASSWORD = requests.utils.quote(env("REDIS_ACTIVITY_PASSWORD", ""))
|
||||||
|
@ -218,9 +238,9 @@ STREAMS = [
|
||||||
]
|
]
|
||||||
|
|
||||||
# Search configuration
|
# Search configuration
|
||||||
# Total time in seconds that the instance will spend searching connectors
|
# total time in seconds that the instance will spend searching connectors
|
||||||
SEARCH_TIMEOUT = env.int("SEARCH_TIMEOUT", 8)
|
SEARCH_TIMEOUT = env.int("SEARCH_TIMEOUT", 8)
|
||||||
# Timeout for a query to an individual connector
|
# timeout for a query to an individual connector
|
||||||
QUERY_TIMEOUT = env.int("INTERACTIVE_QUERY_TIMEOUT", env.int("QUERY_TIMEOUT", 5))
|
QUERY_TIMEOUT = env.int("INTERACTIVE_QUERY_TIMEOUT", env.int("QUERY_TIMEOUT", 5))
|
||||||
|
|
||||||
# Redis cache backend
|
# Redis cache backend
|
||||||
|
@ -271,10 +291,18 @@ AUTH_USER_MODEL = "bookwyrm.User"
|
||||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{ "NAME" : "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" } ,
|
{
|
||||||
{ "NAME" : "django.contrib.auth.password_validation.MinimumLengthValidator" } ,
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
{ "NAME" : "django.contrib.auth.password_validation.CommonPasswordValidator" } ,
|
},
|
||||||
{ "NAME" : "django.contrib.auth.password_validation.NumericPasswordValidator" } ,
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -403,11 +431,15 @@ if USE_S3 :
|
||||||
# Content Security Policy
|
# Content Security Policy
|
||||||
CSP_DEFAULT_SRC = [
|
CSP_DEFAULT_SRC = [
|
||||||
"'self'",
|
"'self'",
|
||||||
f"{AWS_S3_URL_PROTOCOL}//{AWS_S3_CUSTOM_DOMAIN}" if AWS_S3_CUSTOM_DOMAIN else None ,
|
f"{AWS_S3_URL_PROTOCOL}//{AWS_S3_CUSTOM_DOMAIN}"
|
||||||
|
if AWS_S3_CUSTOM_DOMAIN
|
||||||
|
else None,
|
||||||
] + CSP_ADDITIONAL_HOSTS
|
] + CSP_ADDITIONAL_HOSTS
|
||||||
CSP_SCRIPT_SRC = [
|
CSP_SCRIPT_SRC = [
|
||||||
"'self'",
|
"'self'",
|
||||||
f"{AWS_S3_URL_PROTOCOL}//{AWS_S3_CUSTOM_DOMAIN}" if AWS_S3_CUSTOM_DOMAIN else None ,
|
f"{AWS_S3_URL_PROTOCOL}//{AWS_S3_CUSTOM_DOMAIN}"
|
||||||
|
if AWS_S3_CUSTOM_DOMAIN
|
||||||
|
else None,
|
||||||
] + CSP_ADDITIONAL_HOSTS
|
] + CSP_ADDITIONAL_HOSTS
|
||||||
elif USE_AZURE:
|
elif USE_AZURE:
|
||||||
# Azure settings
|
# Azure settings
|
||||||
|
@ -431,16 +463,20 @@ elif USE_AZURE :
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
"BACKEND" : None , # Not implemented yet
|
"BACKEND": None, # not implemented yet
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
# Azure Static settings
|
# Azure Static settings
|
||||||
STATIC_LOCATION = "static"
|
STATIC_LOCATION = "static"
|
||||||
STATIC_URL = ( f"{PROTOCOL}://{AZURE_CUSTOM_DOMAIN}/{AZURE_CONTAINER}/{STATIC_LOCATION}/" )
|
STATIC_URL = (
|
||||||
|
f"{PROTOCOL}://{AZURE_CUSTOM_DOMAIN}/{AZURE_CONTAINER}/{STATIC_LOCATION}/"
|
||||||
|
)
|
||||||
STATIC_FULL_URL = STATIC_URL
|
STATIC_FULL_URL = STATIC_URL
|
||||||
# Azure Media settings
|
# Azure Media settings
|
||||||
MEDIA_LOCATION = "images"
|
MEDIA_LOCATION = "images"
|
||||||
MEDIA_URL = ( f"{PROTOCOL}://{AZURE_CUSTOM_DOMAIN}/{AZURE_CONTAINER}/{MEDIA_LOCATION}/" )
|
MEDIA_URL = (
|
||||||
|
f"{PROTOCOL}://{AZURE_CUSTOM_DOMAIN}/{AZURE_CONTAINER}/{MEDIA_LOCATION}/"
|
||||||
|
)
|
||||||
MEDIA_FULL_URL = MEDIA_URL
|
MEDIA_FULL_URL = MEDIA_URL
|
||||||
# Content Security Policy
|
# Content Security Policy
|
||||||
CSP_DEFAULT_SRC = ["'self'", AZURE_CUSTOM_DOMAIN] + CSP_ADDITIONAL_HOSTS
|
CSP_DEFAULT_SRC = ["'self'", AZURE_CUSTOM_DOMAIN] + CSP_ADDITIONAL_HOSTS
|
||||||
|
@ -456,7 +492,9 @@ else :
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||||
"OPTIONS" : { "location" : "exports" } ,
|
"OPTIONS": {
|
||||||
|
"location": "exports",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
# Static settings
|
# Static settings
|
||||||
|
@ -471,6 +509,11 @@ else :
|
||||||
|
|
||||||
CSP_INCLUDE_NONCE_IN = ["script-src"]
|
CSP_INCLUDE_NONCE_IN = ["script-src"]
|
||||||
|
|
||||||
|
OTEL_EXPORTER_OTLP_ENDPOINT = env("OTEL_EXPORTER_OTLP_ENDPOINT", None)
|
||||||
|
OTEL_EXPORTER_OTLP_HEADERS = env("OTEL_EXPORTER_OTLP_HEADERS", None)
|
||||||
|
OTEL_SERVICE_NAME = env("OTEL_SERVICE_NAME", None)
|
||||||
|
OTEL_EXPORTER_CONSOLE = env.bool("OTEL_EXPORTER_CONSOLE", False)
|
||||||
|
|
||||||
TWO_FACTOR_LOGIN_MAX_SECONDS = env.int("TWO_FACTOR_LOGIN_MAX_SECONDS", 60)
|
TWO_FACTOR_LOGIN_MAX_SECONDS = env.int("TWO_FACTOR_LOGIN_MAX_SECONDS", 60)
|
||||||
TWO_FACTOR_LOGIN_VALIDITY_WINDOW = env.int("TWO_FACTOR_LOGIN_VALIDITY_WINDOW", 2)
|
TWO_FACTOR_LOGIN_VALIDITY_WINDOW = env.int("TWO_FACTOR_LOGIN_VALIDITY_WINDOW", 2)
|
||||||
|
|
||||||
|
@ -478,9 +521,12 @@ HTTP_X_FORWARDED_PROTO = env . bool ( "SECURE_PROXY_SSL_HEADER" , False )
|
||||||
if HTTP_X_FORWARDED_PROTO:
|
if HTTP_X_FORWARDED_PROTO:
|
||||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||||
|
|
||||||
# Instance Actor for signing GET requests to "secure mode" Mastodon servers.
|
# Instance Actor for signing GET requests to "secure mode"
|
||||||
# Do not change this setting unless you already have an existing user with the same username - in which case you should change it
|
# Mastodon servers.
|
||||||
|
# Do not change this setting unless you already have an existing
|
||||||
|
# user with the same username - in which case you should change it!
|
||||||
INSTANCE_ACTOR_USERNAME = "bookwyrm.instance.actor"
|
INSTANCE_ACTOR_USERNAME = "bookwyrm.instance.actor"
|
||||||
|
|
||||||
# We only allow specifying DATA_UPLOAD_MAX_MEMORY_SIZE in MiB from .env (note the difference in variable names).
|
# We only allow specifying DATA_UPLOAD_MAX_MEMORY_SIZE in MiB from .env
|
||||||
|
# (note the difference in variable names).
|
||||||
DATA_UPLOAD_MAX_MEMORY_SIZE = env.int("DATA_UPLOAD_MAX_MEMORY_MiB", 100) << 20
|
DATA_UPLOAD_MAX_MEMORY_SIZE = env.int("DATA_UPLOAD_MAX_MEMORY_MiB", 100) << 20
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue