diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index 08c618b87..94fcd41e1 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -1,406 +1,381 @@ -""" 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 +""" 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 bookwyrm . redis_store import RedisStore , r +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): - """a category of activity stream (like home, local, books)""" - - def stream_id(self, user_id): - """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}" - def unread_id(self, user_id): - """the redis key for this user's unread count for this stream""" - stream_id = self.stream_id(user_id) + def unread_id ( self , user_id ) : + """The redis key for this user's unread count for this stream""" + stream_id = self . stream_id (user_id) return f"{stream_id}-unread" - def unread_by_status_type_id(self, user_id): - """the redis key for this user's unread count for this stream""" - stream_id = self.stream_id(user_id) + def unread_by_status_type_id ( self , user_id ) : + """The redis key for this user's unread count for this stream""" + stream_id = self . stream_id (user_id) return f"{stream_id}-unread-by-type" - def get_rank(self, obj): - """statuses are sorted by date published""" - return obj.published_date.timestamp() + def get_rank ( self , obj ) : + """Statuses are sorted by date published""" + return obj . published_date . timestamp () - def add_status(self, status, increment_unread=False): - """add a status to users' feeds""" - audience = self.get_audience(status) - # the pipeline contains all the add-to-stream activities - pipeline = self.add_object_to_stores( - status, self.get_stores_for_users(audience), execute=False + def add_status ( self , status , increment_unread = False ) : + """Add a status to users' feeds""" + audience = self . get_audience (status) + # The pipeline contains all the add-to-stream activities + pipeline = self . add_object_to_stores ( + status , + self . get_stores_for_users (audience) , + execute = False , ) - if increment_unread: - for user_id in audience: - # add to the unread status count - pipeline.incr(self.unread_id(user_id)) - # add to the unread status count for status type - pipeline.hincrby( - self.unread_by_status_type_id(user_id), get_status_type(status), 1 + if increment_unread : + for user_id in audience : + # Add to the unread status count + pipeline . incr ( self . unread_id (user_id) ) + # Add to the unread status count for status type + pipeline . hincrby ( + self . unread_by_status_type_id (user_id) , + get_status_type (status) , + 1 , ) - # and go! - pipeline.execute() + # And go! + pipeline . execute () - 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 = models.Status.privacy_filter(viewer).filter(user=user) - self.bulk_add_objects_to_store(statuses, self.stream_id(viewer.id)) + 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 = models . Status . privacy_filter (viewer) . filter ( user = user ) + self . bulk_add_objects_to_store ( statuses , self . stream_id ( viewer . id ) ) - def remove_user_statuses(self, viewer, user): - """remove a user's status from another user's feed""" - # remove all so that followers only statuses are removed - statuses = user.status_set.all() - self.bulk_remove_objects_from_store(statuses, self.stream_id(viewer.id)) + def remove_user_statuses ( self , viewer , user ) : + """Remove a user's status from another user's feed""" + # Remove all so that followers only statuses are removed + statuses = user . status_set . all () + self . bulk_remove_objects_from_store ( statuses , self . stream_id ( viewer . id ) ) - def get_activity_stream(self, user): - """load the statuses to be displayed""" - # clear unreads for this feed - r.set(self.unread_id(user.id), 0) - r.delete(self.unread_by_status_type_id(user.id)) + def get_activity_stream ( self , user ) : + """Load the statuses to be displayed""" + # Clear unreads for this feed + r . set ( self . unread_id ( user . id ) , 0 ) + r . delete ( self . unread_by_status_type_id ( user . id ) ) - statuses = self.get_store(self.stream_id(user.id)) - return ( - models.Status.objects.select_subclasses() - .filter(id__in=statuses) - .select_related( - "user", - "reply_parent", - "comment__book", - "review__book", - "quotation__book", - ) - .prefetch_related("mention_books", "mention_users") - .order_by("-published_date") - ) + statuses = self.get_store ( self . stream_id ( user . id ) ) + return models . Status . objects . select_subclasses () + . filter ( id__in = statuses ) + . select_related ( "user" , "reply_parent" , "comment__book" , "review__book" , "quotation__book" ) + . prefetch_related ( "mention_books" , "mention_users" ) + . order_by ("-published_date") - def get_unread_count(self, user): - """get the unread status count for this user's feed""" - return int(r.get(self.unread_id(user.id)) or 0) + def get_unread_count ( self , user ) : + """Get the unread status count for this user's feed""" + return int ( r . get ( self . unread_id ( user . id ) ) or 0 ) - def get_unread_count_by_status_type(self, user): - """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)) + def get_unread_count_by_status_type ( self , user ) : + """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 ) ) return { - str(key.decode("utf-8")): int(value) or 0 - for key, value in status_types.items() + str ( key . decode ("utf-8") ) : int (value) or 0 + for key , value in status_types . items () } - def populate_streams(self, user): - """go from zero to a timeline""" - self.populate_store(self.stream_id(user.id)) + def populate_streams ( self , user) : + """Go from zero to a timeline""" + self . populate_store ( self . stream_id ( user . id ) ) - def _get_audience(self, status): # pylint: disable=no-self-use - """given a status, what users should see it, excluding the author""" - # direct messages don't appear in feeds, direct comments/reviews/etc do - if status.privacy == "direct" and status.status_type == "Note": - return models.User.objects.none() + def _get_audience ( self , status) : + """Given a status, what users should see it, excluding the author""" + # Direct messages don't appear in feeds, direct comments/reviews/etc do + if status . privacy == "direct" and status . status_type == "Note" : + return models . User . objects . none () - # everybody who could plausibly see this status - audience = models.User.objects.filter( - is_active=True, - local=True, # we only create feeds for users of this instance - ).exclude( - Q(id__in=status.user.blocks.all()) | Q(blocks=status.user) # not blocked + # Everybody who could plausibly see this status + audience = models . User . objects + . filter ( is_active = True , local = True ) # We only create feeds for users of this instance + . exclude ( + Q ( id__in = status . user . blocks . all () ) | + 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 - 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): # 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) + 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): - """users you follow""" +class HomeStream (ActivityStream) : + """Users you follow""" key = "home" - def get_audience(self, status): - audience = super()._get_audience(status) - # if the user is following the author - audience = audience.filter(following=status.user).values_list("id", flat=True) - # if the user is the post's author - 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_audience ( self , status ) : + audience = super () . _get_audience (status) + # If the user is following the author + audience = audience . filter ( following = status . user ) . values_list ( "id" , flat = True ) + # If the user is the post's author + 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_statuses_for_user(self, user): - return models.Status.privacy_filter( - user, - privacy_levels=["public", "unlisted", "followers"], - ).exclude( - ~Q( # remove everything except - Q(user__followers=user) # user following - | Q(user=user) # is self - | Q(mention_users=user) # mentions user - ), - ) + def get_statuses_for_user ( self , user ) : + return models . Status . privacy_filter ( + user , + privacy_levels = [ "public" , "unlisted" , "followers" ] , + ) . exclude ( ~ Q ( # Remove everything except + Q ( user__followers = user ) | # User following + Q ( user = user ) | # Is self + Q ( mention_users = user ) # Mentions user + ) ) -class LocalStream(ActivityStream): - """users you follow""" +class LocalStream (ActivityStream) : + """Users you follow""" key = "local" - def get_audience(self, status): - # this stream wants no part in non-public statuses - if status.privacy != "public" or not status.user.local: + def get_audience ( self , status ) : + # This stream wants no part in non-public statuses + if status . privacy != "public" or not status . user . local : return [] - return super().get_audience(status) + return super () . get_audience (status) - def get_statuses_for_user(self, user): - # all public statuses by a local user - return models.Status.privacy_filter( - user, - privacy_levels=["public"], - ).filter(user__local=True) + def get_statuses_for_user ( self , user ) : + # All public statuses by a local user + return models . Status + . privacy_filter ( user , privacy_levels = [ "public" ] ) + . filter ( user__local = True ) -class BooksStream(ActivityStream): - """books on your shelves""" +class BooksStream (ActivityStream) : + """Books on your shelves""" key = "books" - def _get_audience(self, status): - """anyone with the mentioned book on their shelves""" - work = ( - status.book.parent_work - if hasattr(status, "book") - else status.mention_books.first().parent_work - ) + def _get_audience ( self , status ) : + """Anyone with the mentioned book on their shelves""" + work = status . book . parent_work if hasattr ( status , "book" ) + else status . mention_books . first () . parent_work - audience = super()._get_audience(status) - return audience.filter(shelfbook__book__parent_work=work) + audience = super () . _get_audience (status) + return audience . filter ( shelfbook__book__parent_work = work ) - def get_audience(self, status): - # only show public statuses on the books feed, - # and only statuses that mention books - if status.privacy != "public" or not ( - status.mention_books.exists() or hasattr(status, "book") - ): + def get_audience ( self , status ) : + # Only show public statuses on the books feed, and only statuses that mention books + if ( + status . privacy != "public" or + not ( status . mention_books . exists () or hasattr ( status , "book" ) ) + ) : return [] - return super().get_audience(status) + return super () . get_audience (status) - def get_statuses_for_user(self, user): - """any public status that mentions the user's books""" - books = user.shelfbook_set.values_list( - "book__parent_work__id", flat=True - ).distinct() - return ( - models.Status.privacy_filter( - user, - privacy_levels=["public"], + def get_statuses_for_user ( self , user ) : + """Any public status that mentions the user's books""" + books = user . shelfbook_set + . values_list ( "book__parent_work__id" , flat = True ) + . distinct () + 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 ) ) - .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() - ) + . distinct () - def add_book_statuses(self, user, book): - """add statuses about a book to a user's feed""" - work = book.parent_work - statuses = models.Status.privacy_filter( - user, - privacy_levels=["public"], - ) + def add_book_statuses ( self , user , book ) : + """Add statuses about a book to a user's feed""" + work = book . parent_work + statuses = models . Status . privacy_filter ( user , privacy_levels = [ "public" ] ) - book_comments = statuses.filter(Q(comment__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_mentions = statuses.filter(Q(mention_books__parent_work=work)) + book_comments = statuses . filter ( Q ( comment__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_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_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_mentions, 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_reviews , 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): - """add statuses about a book to a user's feed""" - work = book.parent_work - statuses = models.Status.privacy_filter( - user, - privacy_levels=["public"], - ) + def remove_book_statuses ( self , user , book ) : + """Add statuses about a book to a user's feed""" + work = book . parent_work + statuses = models . Status . privacy_filter ( user , privacy_levels = [ "public" ] ) - book_comments = statuses.filter(Q(comment__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_mentions = statuses.filter(Q(mention_books__parent_work=work)) + book_comments = statuses . filter ( Q ( comment__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_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_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_mentions, 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_reviews , 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 = { - "home": HomeStream(), - "local": LocalStream(), - "books": BooksStream(), + "home" : HomeStream () , + "local" : LocalStream () , + "books" : BooksStream () , } -@receiver(signals.post_save) -# pylint: disable=unused-argument -def add_status_on_create(sender, instance, created, *args, **kwargs): - """add newly created statuses to activity feeds""" - # we're only interested in new statuses - if not issubclass(sender, models.Status): +@ receiver ( signals . post_save ) +def add_status_on_create ( sender , instance , created , * args , ** kwargs ) : + """Add newly created statuses to activity feeds""" + # We're only interested in new statuses + if not issubclass ( sender , models . Status ) : return - if instance.deleted: - remove_status_task.delay(instance.id) + if instance . deleted : + remove_status_task . delay ( instance . id ) return - # We don't want to create multiple add_status_tasks for each status, and because - # the transactions are atomic, on_commit won't run until the status is ready to add. - if not created: + # We don't want to create multiple add_status_tasks for each status, and because the transactions are atomic, + # on_commit won't run until the status is ready to add. + if not created : return - # when creating new things, gotta wait on the transaction - transaction.on_commit( - lambda: add_status_on_create_command(sender, instance, created) - ) + # When creating new things, gotta wait on the transaction + transaction . on_commit ( lambda : 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""" - # boosts trigger 'saves" twice, so don't bother duplicating the task - if sender == models.Boost and not created: +def add_status_on_create_command ( sender , instance , created ) : + """Runs this code only after the database commit completes""" + # Boosts trigger 'saves" twice, so don't bother duplicating the task + if sender == models . Boost and not created : return 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) - if instance.published_date < timezone.now() - 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 - if instance.user.local: + if ( + instance . published_date < timezone . now () - 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 + if instance . user . local : 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 - add_status_task.apply_async( - args=(instance.id,), - kwargs={"increment_unread": created}, - queue=priority, + add_status_task . apply_async ( + args = ( instance . id , ) , + kwargs = { "increment_unread" : created } , + queue = priority , ) - if sender == models.Boost: - handle_boost_task.delay(instance.id) + if sender == models . Boost : + handle_boost_task . delay ( instance . id ) -@receiver(signals.post_delete, sender=models.Boost) -# pylint: disable=unused-argument -def remove_boost_on_delete(sender, instance, *args, **kwargs): - """boosts are deleted""" - # remove the boost - remove_status_task.delay(instance.id) - # re-add the original status - add_status_task.delay(instance.boosted_status.id) +@ receiver ( signals . post_delete , sender = models . Boost ) +def remove_boost_on_delete ( sender , instance , * args , ** kwargs ) : + """Boosts are deleted""" + # Remove the boost + remove_status_task . delay ( instance . id ) + # Re-add the original status + add_status_task . delay ( instance . boosted_status . id ) -@receiver(signals.post_save, sender=models.UserFollows) -# pylint: disable=unused-argument -def add_statuses_on_follow(sender, instance, created, *args, **kwargs): - """add a newly followed user's statuses to feeds""" - if not created or not instance.user_subject.local: +@ receiver ( signals . post_save , sender = models . UserFollows ) +def add_statuses_on_follow ( sender , instance , created , * args , ** kwargs ) : + """Add a newly followed user's statuses to feeds""" + if not created or not instance . user_subject . local : return - add_user_statuses_task.delay( - instance.user_subject.id, instance.user_object.id, stream_list=["home"] + add_user_statuses_task . delay ( + instance . user_subject . id , + instance . user_object . id , + stream_list = [ "home" ] , ) -@receiver(signals.post_delete, sender=models.UserFollows) -# pylint: disable=unused-argument -def remove_statuses_on_unfollow(sender, instance, *args, **kwargs): - """remove statuses from a feed on unfollow""" - if not instance.user_subject.local: +@ receiver ( signals . post_delete , sender = models . UserFollows ) +def remove_statuses_on_unfollow ( sender , instance , * args , ** kwargs ) : + """Remove statuses from a feed on unfollow""" + if not instance . user_subject . local : return - remove_user_statuses_task.delay( - instance.user_subject.id, instance.user_object.id, stream_list=["home"] + remove_user_statuses_task . delay ( + instance . user_subject . id , + instance . user_object . id , + stream_list = [ "home" ] , ) -@receiver(signals.post_save, sender=models.UserBlocks) -# pylint: disable=unused-argument -def remove_statuses_on_block(sender, instance, *args, **kwargs): - """remove statuses from all feeds on block""" - # blocks apply ot all feeds - if instance.user_subject.local: - remove_user_statuses_task.delay( - instance.user_subject.id, instance.user_object.id +@ receiver ( signals . post_save , sender = models . UserBlocks ) +def remove_statuses_on_block ( sender , instance , * args , ** kwargs ) : + """Remove statuses from all feeds on block""" + # Blocks apply ot all feeds + if instance . user_subject . local : + remove_user_statuses_task . delay ( + instance . user_subject . id , + instance . user_object . id , ) - # and in both directions - if instance.user_object.local: - remove_user_statuses_task.delay( - instance.user_object.id, instance.user_subject.id + # And in both directions + if instance . user_object . local : + remove_user_statuses_task . delay ( + instance . user_object . id , + instance . user_subject . id , ) @receiver(signals.post_delete, sender=models.UserBlocks) -# pylint: disable=unused-argument def add_statuses_on_unblock(sender, instance, *args, **kwargs): """add statuses back to all feeds on unblock""" # 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) -# pylint: disable=unused-argument def populate_streams_on_account_create(sender, instance, created, *args, **kwargs): """build a user's feeds when they join""" 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) -# pylint: disable=unused-argument def add_statuses_on_shelve(sender, instance, *args, **kwargs): """update books stream when user shelves a book""" 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) -# pylint: disable=unused-argument def remove_statuses_on_unshelve(sender, instance, *args, **kwargs): """update books stream when user unshelves a book""" if not instance.user.local: diff --git a/bookwyrm/apps.py b/bookwyrm/apps.py index d5384bb7b..13d17b450 100644 --- a/bookwyrm/apps.py +++ b/bookwyrm/apps.py @@ -1,55 +1,51 @@ """Do further startup configuration and initialization""" + + +import logging import os import urllib -import logging - -from django.apps import AppConfig from bookwyrm import settings - -logger = logging.getLogger(__name__) +from django . apps import AppConfig -def download_file(url, destination): + +logger = logging . getLogger (__name__) + + +def download_file ( url , destination) : """Downloads a file to the given path""" - try: + try : # Ensure our destination directory exists - os.makedirs(os.path.dirname(destination), exist_ok=True) - with urllib.request.urlopen(url) as stream: - with open(destination, "b+w") as outfile: - outfile.write(stream.read()) - except (urllib.error.HTTPError, urllib.error.URLError) as err: - logger.error("Failed to download file %s: %s", url, err) - except OSError as err: - logger.error("Couldn't open font file %s for writing: %s", destination, err) - except Exception as err: # pylint:disable=broad-except - logger.error("Unknown error in file download: %s", err) + os . makedirs ( os . path . dirname (destination) , exist_ok = True ) + with urllib . request . urlopen (url) as stream : + with open ( destination , "b+w" ) as outfile : + outfile . write ( stream . read () ) + except ( urllib . error . HTTPError , urllib . error . URLError ) as err : + logger . error ( "Failed to download file %s: %s" , url , err ) + except OSError as err : + logger . error ( "Couldn't open font file %s for writing: %s" , destination , err ) + except Exception as err : + logger . error ( "Unknown error in file download: %s" , err ) -class BookwyrmConfig(AppConfig): +class BookwyrmConfig (AppConfig) : """Handles additional configuration""" name = "bookwyrm" verbose_name = "BookWyrm" - def ready(self): - """set up OTLP and preview image files, if desired""" - if settings.OTEL_EXPORTER_OTLP_ENDPOINT or settings.OTEL_EXPORTER_CONSOLE: - # 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: + def ready (self) : + """Set up preview image files, if desired""" + if settings . ENABLE_PREVIEW_IMAGES and settings . FONTS : # Download any fonts that we don't have yet - logger.debug("Downloading fonts..") - for name, config in settings.FONTS.items(): - font_path = os.path.join( - settings.FONT_DIR, config["directory"], config["filename"] + logger . debug ("Downloading fonts..") + for name , config in settings . FONTS . items () : + font_path = os . path . join ( + settings . FONT_DIR , config [ "directory" ] , config [ "filename" ] ) - if "url" in config and not os.path.exists(font_path): - logger.info("Just a sec, downloading %s", name) - download_file(config["url"], font_path) + if "url" in config and not os . path . exists (font_path) : + logger . info ( "Just a sec, downloading %s" , name ) + download_file ( config [ "url" ] , font_path )