Remove opentelemetry-related code from apps.py, formatting
This commit is contained in:
parent
0702731c7f
commit
64ea602f02
2 changed files with 308 additions and 340 deletions
|
@ -1,406 +1,381 @@
|
||||||
""" access the activity streams stored in redis """
|
""" Access the activity streams stored in Redis """
|
||||||
from datetime import timedelta
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.db import transaction
|
|
||||||
from django.db.models import signals, Q
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
from bookwyrm.redis_store import RedisStore, r
|
from bookwyrm . redis_store import RedisStore , r
|
||||||
from bookwyrm.tasks import app, STREAMS, IMPORT_TRIGGERED
|
from bookwyrm . tasks import app , STREAMS , IMPORT_TRIGGERED
|
||||||
|
from datetime import timedelta
|
||||||
|
from django . dispatch import receiver
|
||||||
|
from django . db import transaction
|
||||||
|
from django . db . models import signals , Q
|
||||||
|
from django . utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ActivityStream (RedisStore) :
|
||||||
|
"""A category of activity stream (like home, local, books)"""
|
||||||
|
|
||||||
class ActivityStream(RedisStore):
|
def stream_id (self , user_id ) :
|
||||||
"""a category of activity stream (like home, local, books)"""
|
"""The redis key for this user's instance of this stream"""
|
||||||
|
|
||||||
def stream_id(self, user_id):
|
|
||||||
"""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, self.get_stores_for_users(audience), execute=False
|
status ,
|
||||||
|
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), get_status_type(status), 1
|
self . unread_by_status_type_id (user_id) ,
|
||||||
|
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 (
|
return models . Status . objects . select_subclasses ()
|
||||||
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(
|
. prefetch_related ( "mention_books" , "mention_users" )
|
||||||
"user",
|
. order_by ("-published_date")
|
||||||
"reply_parent",
|
|
||||||
"comment__book",
|
|
||||||
"review__book",
|
|
||||||
"quotation__book",
|
|
||||||
)
|
|
||||||
.prefetch_related("mention_books", "mention_users")
|
|
||||||
.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
|
||||||
for key, value in status_types.items()
|
for key , value in status_types . items ()
|
||||||
}
|
}
|
||||||
|
|
||||||
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): # pylint: disable=no-self-use
|
def _get_audience ( self , status) :
|
||||||
"""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.filter(
|
audience = models . User . objects
|
||||||
is_active=True,
|
. filter ( is_active = True , local = True ) # We only create feeds for users of this instance
|
||||||
local=True, # we only create feeds for users of this instance
|
. exclude (
|
||||||
).exclude(
|
Q ( id__in = status . user . blocks . all () ) |
|
||||||
Q(id__in=status.user.blocks.all()) | Q(blocks=status.user) # not blocked
|
Q ( blocks = status . user) # Not blocked
|
||||||
|
)
|
||||||
|
|
||||||
|
# Only visible to the poster and mentioned users
|
||||||
|
if status . privacy == "direct" :
|
||||||
|
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
|
||||||
|
elif status . reply_parent and status . reply_parent . privacy == "followers" :
|
||||||
|
audience = audience . filter (
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
# Only visible to the poster's followers and tagged users
|
||||||
|
elif status . privacy == "followers" :
|
||||||
|
audience = audience . filter ( Q ( following = status . user ) ) # If the user is following the author
|
||||||
|
|
||||||
|
return audience . distinct ("id")
|
||||||
|
|
||||||
|
def get_audience ( self , status ) :
|
||||||
|
"""Given a status, what users should see it"""
|
||||||
|
audience = self . _get_audience (status) . values_list ( "id" , flat = True )
|
||||||
|
status_author = models . User . objects
|
||||||
|
. filter ( is_active = True , local = True , id = status . user . id )
|
||||||
|
. values_list ( "id" , flat = True )
|
||||||
|
return list ( set (audience) | set (status_author) )
|
||||||
|
|
||||||
|
def get_stores_for_users ( self , user_ids ) :
|
||||||
|
"""Convert a list of user ids into Redis store ids"""
|
||||||
|
return [ self . stream_id (user_id) for user_id in user_ids ]
|
||||||
|
|
||||||
|
def get_statuses_for_user ( self , user ) :
|
||||||
|
"""Given a user, what statuses should they see on this stream"""
|
||||||
|
return models . Status . privacy_filter (
|
||||||
|
user ,
|
||||||
|
privacy_levels = [ "public" , "unlisted" , "followers" ] ,
|
||||||
)
|
)
|
||||||
|
|
||||||
# only visible to the poster and mentioned users
|
def get_objects_for_store ( self , store ) :
|
||||||
if status.privacy == "direct":
|
user = models . User . objects . get ( id = store . split ("-") [0] )
|
||||||
audience = audience.filter(
|
return self . get_statuses_for_user (user)
|
||||||
Q(id__in=status.mention_users.all()) # if the user is mentioned
|
|
||||||
)
|
|
||||||
|
|
||||||
# don't show replies to statuses the user can't see
|
|
||||||
elif status.reply_parent and status.reply_parent.privacy == "followers":
|
|
||||||
audience = audience.filter(
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
# only visible to the poster's followers and tagged users
|
|
||||||
elif status.privacy == "followers":
|
|
||||||
audience = audience.filter(
|
|
||||||
Q(following=status.user) # if the user is following the author
|
|
||||||
)
|
|
||||||
return audience.distinct("id")
|
|
||||||
|
|
||||||
def get_audience(self, status):
|
|
||||||
"""given a status, what users should see it"""
|
|
||||||
audience = self._get_audience(status).values_list("id", flat=True)
|
|
||||||
status_author = models.User.objects.filter(
|
|
||||||
is_active=True, local=True, id=status.user.id
|
|
||||||
).values_list("id", flat=True)
|
|
||||||
return list(set(audience) | set(status_author))
|
|
||||||
|
|
||||||
def get_stores_for_users(self, user_ids):
|
|
||||||
"""convert a list of user ids into redis store ids"""
|
|
||||||
return [self.stream_id(user_id) for user_id in user_ids]
|
|
||||||
|
|
||||||
def get_statuses_for_user(self, user): # pylint: disable=no-self-use
|
|
||||||
"""given a user, what statuses should they see on this stream"""
|
|
||||||
return models.Status.privacy_filter(
|
|
||||||
user,
|
|
||||||
privacy_levels=["public", "unlisted", "followers"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_objects_for_store(self, store):
|
|
||||||
user = models.User.objects.get(id=store.split("-")[0])
|
|
||||||
return self.get_statuses_for_user(user)
|
|
||||||
|
|
||||||
|
|
||||||
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.filter(
|
status_author = models . User . objects
|
||||||
is_active=True, local=True, id=status.user.id
|
. filter ( 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(
|
) . exclude ( ~ Q ( # Remove everything except
|
||||||
~Q( # remove everything except
|
Q ( user__followers = user ) | # User following
|
||||||
Q(user__followers=user) # user following
|
Q ( user = user ) | # Is self
|
||||||
| Q(user=user) # is self
|
Q ( mention_users = user ) # Mentions user
|
||||||
| 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.privacy_filter(
|
return models . Status
|
||||||
user,
|
. privacy_filter ( user , privacy_levels = [ "public" ] )
|
||||||
privacy_levels=["public"],
|
. filter ( user__local = True )
|
||||||
).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 = (
|
work = status . book . parent_work if hasattr ( status , "book" )
|
||||||
status.book.parent_work
|
else status . mention_books . first () . parent_work
|
||||||
if hasattr(status, "book")
|
|
||||||
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,
|
# Only show public statuses on the books feed, and only statuses that mention books
|
||||||
# and only statuses that mention books
|
if (
|
||||||
if status.privacy != "public" or not (
|
status . privacy != "public" or
|
||||||
status.mention_books.exists() or hasattr(status, "book")
|
not ( 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.values_list(
|
books = user . shelfbook_set
|
||||||
"book__parent_work__id", flat=True
|
. values_list ( "book__parent_work__id" , flat = True )
|
||||||
).distinct()
|
. distinct ()
|
||||||
return (
|
return models . Status
|
||||||
models.Status.privacy_filter(
|
. privacy_filter ( user , privacy_levels = [ "public" ] )
|
||||||
user,
|
. filter (
|
||||||
privacy_levels=["public"],
|
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 )
|
||||||
)
|
)
|
||||||
.filter(
|
. distinct ()
|
||||||
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()
|
|
||||||
)
|
|
||||||
|
|
||||||
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(
|
statuses = models . Status . privacy_filter ( user , privacy_levels = [ "public" ] )
|
||||||
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 ) )
|
||||||
book_reviews = statuses.filter(Q(review__book__parent_work=work))
|
book_reviews = statuses . filter ( Q ( review__book__parent_work = work ) )
|
||||||
book_mentions = statuses.filter(Q(mention_books__parent_work=work))
|
book_mentions = statuses . filter ( Q ( mention_books__parent_work = work ) )
|
||||||
|
|
||||||
self.bulk_add_objects_to_store(book_comments, self.stream_id(user.id))
|
self . bulk_add_objects_to_store ( book_comments , self . stream_id ( user . id ) )
|
||||||
self.bulk_add_objects_to_store(book_quotations, self.stream_id(user.id))
|
self . bulk_add_objects_to_store ( book_quotations , self . stream_id ( user . id ) )
|
||||||
self.bulk_add_objects_to_store(book_reviews, self.stream_id(user.id))
|
self . bulk_add_objects_to_store ( book_reviews , self . stream_id ( user . id ) )
|
||||||
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(
|
statuses = models . Status . privacy_filter ( user , privacy_levels = [ "public" ] )
|
||||||
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 ) )
|
||||||
book_reviews = statuses.filter(Q(review__book__parent_work=work))
|
book_reviews = statuses . filter ( Q ( review__book__parent_work = work ) )
|
||||||
book_mentions = statuses.filter(Q(mention_books__parent_work=work))
|
book_mentions = statuses . filter ( Q ( mention_books__parent_work = work ) )
|
||||||
|
|
||||||
self.bulk_remove_objects_from_store(book_comments, self.stream_id(user.id))
|
self . bulk_remove_objects_from_store ( book_comments , self . stream_id ( user . id ) )
|
||||||
self.bulk_remove_objects_from_store(book_quotations, self.stream_id(user.id))
|
self . bulk_remove_objects_from_store ( book_quotations , self . stream_id ( user . id ) )
|
||||||
self.bulk_remove_objects_from_store(book_reviews, self.stream_id(user.id))
|
self . bulk_remove_objects_from_store ( book_reviews , self . stream_id ( user . id ) )
|
||||||
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 () ,
|
||||||
"books": BooksStream(),
|
"books" : BooksStream () ,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@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
|
||||||
|
|
||||||
if instance.deleted:
|
if instance . deleted :
|
||||||
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
|
# We don't want to create multiple add_status_tasks for each status, and because the transactions are atomic,
|
||||||
# the transactions are atomic, on_commit won't run until the status is ready to add.
|
# 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(
|
transaction . on_commit ( lambda : add_status_on_create_command ( sender , instance , created ) )
|
||||||
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 instance.published_date < timezone.now() - timedelta(
|
if (
|
||||||
days=1
|
instance . published_date < timezone . now () - timedelta ( days = 1 ) or
|
||||||
) or instance.created_date < instance.published_date - timedelta(days=1):
|
instance.created_date < instance.published_date - timedelta(days=1)
|
||||||
# a backdated status from a local user is an import, don't add it
|
) :
|
||||||
if instance.user.local:
|
# A backdated status from a local user is an import, don't add it
|
||||||
|
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 (
|
||||||
args=(instance.id,),
|
args = ( instance . id , ) ,
|
||||||
kwargs={"increment_unread": created},
|
kwargs = { "increment_unread" : created } ,
|
||||||
queue=priority,
|
queue = priority ,
|
||||||
)
|
)
|
||||||
|
|
||||||
if sender == models.Boost:
|
if sender == models . Boost :
|
||||||
handle_boost_task.delay(instance.id)
|
handle_boost_task . delay ( instance . id )
|
||||||
|
|
||||||
|
|
||||||
@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_object.id, stream_list=["home"]
|
instance . user_subject . id ,
|
||||||
|
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_object.id, stream_list=["home"]
|
instance . user_subject . id ,
|
||||||
|
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_subject.id
|
instance . user_object . 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
|
||||||
|
@ -430,7 +405,6 @@ 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:
|
||||||
|
@ -447,7 +421,6 @@ 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:
|
||||||
|
@ -463,7 +436,6 @@ 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,55 +1,51 @@
|
||||||
"""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 django.apps import AppConfig
|
|
||||||
|
|
||||||
from bookwyrm import settings
|
from bookwyrm import settings
|
||||||
|
from django . apps import AppConfig
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def download_file(url, destination):
|
|
||||||
|
logger = logging . getLogger (__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def download_file ( url , destination) :
|
||||||
"""Downloads a file to the given path"""
|
"""Downloads a file to the given path"""
|
||||||
try:
|
try :
|
||||||
# Ensure our destination directory exists
|
# Ensure our destination directory exists
|
||||||
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
os . makedirs ( os . path . dirname (destination) , exist_ok = True )
|
||||||
with urllib.request.urlopen(url) as stream:
|
with urllib . request . urlopen (url) as stream :
|
||||||
with open(destination, "b+w") as outfile:
|
with open ( destination , "b+w" ) as outfile :
|
||||||
outfile.write(stream.read())
|
outfile . write ( stream . read () )
|
||||||
except (urllib.error.HTTPError, urllib.error.URLError) as err:
|
except ( urllib . error . HTTPError , urllib . error . URLError ) as err :
|
||||||
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: # pylint:disable=broad-except
|
except Exception as err :
|
||||||
logger.error("Unknown error in file download: %s", err)
|
logger . error ( "Unknown error in file download: %s" , err )
|
||||||
|
|
||||||
|
|
||||||
class BookwyrmConfig(AppConfig):
|
class BookwyrmConfig (AppConfig) :
|
||||||
"""Handles additional configuration"""
|
"""Handles additional configuration"""
|
||||||
|
|
||||||
name = "bookwyrm"
|
name = "bookwyrm"
|
||||||
verbose_name = "BookWyrm"
|
verbose_name = "BookWyrm"
|
||||||
|
|
||||||
def ready(self):
|
def ready (self) :
|
||||||
"""set up OTLP and preview image files, if desired"""
|
"""Set up preview image files, if desired"""
|
||||||
if settings.OTEL_EXPORTER_OTLP_ENDPOINT or settings.OTEL_EXPORTER_CONSOLE:
|
if settings . ENABLE_PREVIEW_IMAGES and settings . FONTS :
|
||||||
# pylint: disable=import-outside-toplevel
|
|
||||||
from bookwyrm.telemetry import open_telemetry
|
|
||||||
|
|
||||||
open_telemetry.instrumentDjango()
|
|
||||||
open_telemetry.instrumentPostgres()
|
|
||||||
|
|
||||||
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..")
|
||||||
for name, config in settings.FONTS.items():
|
for name , config in settings . FONTS . items () :
|
||||||
font_path = os.path.join(
|
font_path = os . path . join (
|
||||||
settings.FONT_DIR, config["directory"], config["filename"]
|
settings . FONT_DIR , config [ "directory" ] , config [ "filename" ]
|
||||||
)
|
)
|
||||||
|
|
||||||
if "url" in config and not os.path.exists(font_path):
|
if "url" in config and not os . path . exists (font_path) :
|
||||||
logger.info("Just a sec, downloading %s", name)
|
logger . info ( "Just a sec, downloading %s" , name )
|
||||||
download_file(config["url"], font_path)
|
download_file ( config [ "url" ] , font_path )
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue