diff --git a/bookwyrm/activitypub/book.py b/bookwyrm/activitypub/book.py index 5db0dc3ac..a53222053 100644 --- a/bookwyrm/activitypub/book.py +++ b/bookwyrm/activitypub/book.py @@ -22,8 +22,6 @@ class BookData(ActivityObject): aasin: Optional[str] = None isfdb: Optional[str] = None lastEditedBy: Optional[str] = None - links: list[str] = field(default_factory=list) - fileLinks: list[str] = field(default_factory=list) # pylint: disable=invalid-name @@ -45,6 +43,8 @@ class Book(BookData): firstPublishedDate: str = "" publishedDate: str = "" + fileLinks: list[str] = field(default_factory=list) + cover: Optional[Document] = None type: str = "Book" diff --git a/bookwyrm/models/bookwyrm_export_job.py b/bookwyrm/models/bookwyrm_export_job.py index 80912b9e3..1f6085e0c 100644 --- a/bookwyrm/models/bookwyrm_export_job.py +++ b/bookwyrm/models/bookwyrm_export_job.py @@ -1,5 +1,6 @@ """Export user account to tar.gz file for import into another Bookwyrm instance""" +import dataclasses import logging from uuid import uuid4 @@ -8,12 +9,11 @@ from django.db.models import Q from django.core.serializers.json import DjangoJSONEncoder from django.core.files.base import ContentFile -from bookwyrm.models import AnnualGoal, ReadThrough, ShelfBook, Shelf, List, ListItem +from bookwyrm.models import AnnualGoal, ReadThrough, ShelfBook, List, ListItem from bookwyrm.models import Review, Comment, Quotation -from bookwyrm.models import Edition, Book +from bookwyrm.models import Edition from bookwyrm.models import UserFollows, User, UserBlocks from bookwyrm.models.job import ParentJob, ParentTask -from bookwyrm.settings import DOMAIN from bookwyrm.tasks import app, IMPORTS from bookwyrm.utils.tar import BookwyrmTarFile @@ -63,7 +63,7 @@ def tar_export(json_data: str, user, file): if getattr(user, "avatar", False): tar.add_image(user.avatar, filename="avatar") - editions, books = get_books_for_user(user) # pylint: disable=unused-variable + editions = get_books_for_user(user) for book in editions: if getattr(book, "cover", False): tar.add_image(book.cover) @@ -71,138 +71,162 @@ def tar_export(json_data: str, user, file): file.close() -def json_export(user): # pylint: disable=too-many-locals, too-many-statements +def json_export( + user, +): # pylint: disable=too-many-locals, too-many-statements, too-many-branches """Generate an export for a user""" - # user - exported_user = {} + + # User as AP object + exported_user = user.to_activity() + # I don't love this but it prevents a JSON encoding error + # when there is no user image + if isinstance( + exported_user["icon"], + dataclasses._MISSING_TYPE, # pylint: disable=protected-access + ): + exported_user["icon"] = {} + else: + # change the URL to be relative to the JSON file + file_type = exported_user["icon"]["url"].rsplit(".", maxsplit=1)[-1] + filename = f"avatar.{file_type}" + exported_user["icon"]["url"] = filename + + # Additional settings - can't be serialized as AP vals = [ - "username", - "name", - "summary", - "manually_approves_followers", - "hide_follows", "show_goal", - "show_suggested_users", - "discoverable", "preferred_timezone", "default_post_privacy", + "show_suggested_users", ] + exported_user["settings"] = {} for k in vals: - exported_user[k] = getattr(user, k) + exported_user["settings"][k] = getattr(user, k) - if getattr(user, "avatar", False): - exported_user["avatar"] = f'https://{DOMAIN}{getattr(user, "avatar").url}' - - # reading goals + # Reading goals - can't be serialized as AP reading_goals = AnnualGoal.objects.filter(user=user).distinct() - goals_list = [] - # TODO: either error checking should be more sophisticated - # or maybe we don't need this try/except - try: - for goal in reading_goals: - goals_list.append( - {"goal": goal.goal, "year": goal.year, "privacy": goal.privacy} - ) - except Exception: # pylint: disable=broad-except - pass + exported_user["goals"] = [] + for goal in reading_goals: + exported_user["goals"].append( + {"goal": goal.goal, "year": goal.year, "privacy": goal.privacy} + ) - try: - readthroughs = ReadThrough.objects.filter(user=user).distinct().values() - readthroughs = list(readthroughs) - except Exception: # pylint: disable=broad-except - readthroughs = [] + # Reading history - can't be serialized as AP + readthroughs = ReadThrough.objects.filter(user=user).distinct().values() + readthroughs = list(readthroughs) - # books - editions, books = get_books_for_user(user) - final_books = [] + # Books + editions = get_books_for_user(user) + exported_user["books"] = [] + + for edition in editions: + book = {} + book["work"] = edition.parent_work.to_activity() + book["edition"] = edition.to_activity() + + if book["edition"].get("cover"): + # change the URL to be relative to the JSON file + filename = book["edition"]["cover"]["url"].rsplit("/", maxsplit=1)[-1] + book["edition"]["cover"]["url"] = f"covers/{filename}" - for book in books.values(): - edition = editions.filter(id=book["id"]) - book["edition"] = edition.values()[0] # authors - book["authors"] = list(edition.first().authors.all().values()) - # readthroughs + book["authors"] = [] + for author in edition.authors.all(): + book["authors"].append(author.to_activity()) + + # Shelves this book is on + # Every ShelfItem is this book so we don't other serializing + book["shelves"] = [] + shelf_books = ( + ShelfBook.objects.select_related("shelf") + .filter(user=user, book=edition) + .distinct() + ) + + for shelfbook in shelf_books: + book["shelves"].append(shelfbook.shelf.to_activity()) + + # Lists and ListItems + # ListItems include "notes" and "approved" so we need them + # even though we know it's this book + book["lists"] = [] + list_items = ListItem.objects.filter(book=edition, user=user).distinct() + + for item in list_items: + list_info = item.book_list.to_activity() + list_info[ + "privacy" + ] = item.book_list.privacy # this isn't serialized so we add it + list_info["list_item"] = item.to_activity() + book["lists"].append(list_info) + + # Statuses + # Can't use select_subclasses here because + # we need to filter on the "book" value, + # which is not available on an ordinary Status + for status in ["comments", "quotations", "reviews"]: + book[status] = [] + + comments = Comment.objects.filter(user=user, book=edition).all() + for status in comments: + obj = status.to_activity() + obj["progress"] = status.progress + obj["progress_mode"] = status.progress_mode + book["comments"].append(obj) + + quotes = Quotation.objects.filter(user=user, book=edition).all() + for status in quotes: + obj = status.to_activity() + obj["position"] = status.position + obj["endposition"] = status.endposition + obj["position_mode"] = status.position_mode + book["quotations"].append(obj) + + reviews = Review.objects.filter(user=user, book=edition).all() + for status in reviews: + obj = status.to_activity() + book["reviews"].append(obj) + + # readthroughs can't be serialized to activity book_readthroughs = ( - ReadThrough.objects.filter(user=user, book=book["id"]).distinct().values() + ReadThrough.objects.filter(user=user, book=edition).distinct().values() ) book["readthroughs"] = list(book_readthroughs) - # shelves - shelf_books = ShelfBook.objects.filter(user=user, book=book["id"]).distinct() - shelves_from_books = Shelf.objects.filter(shelfbook__in=shelf_books, user=user) - - book["shelves"] = list(shelves_from_books.values()) - book["shelf_books"] = {} - - for shelf in shelves_from_books: - shelf_contents = ShelfBook.objects.filter(user=user, shelf=shelf).distinct() - - book["shelf_books"][shelf.identifier] = list(shelf_contents.values()) - - # book lists - book_lists = List.objects.filter(books__in=[book["id"]], user=user).distinct() - book["lists"] = list(book_lists.values()) - book["list_items"] = {} - for blist in book_lists: - list_items = ListItem.objects.filter(book_list=blist).distinct() - book["list_items"][blist.name] = list(list_items.values()) - - # reviews - reviews = Review.objects.filter(user=user, book=book["id"]).distinct() - - book["reviews"] = list(reviews.values()) - - # comments - comments = Comment.objects.filter(user=user, book=book["id"]).distinct() - - book["comments"] = list(comments.values()) - - # quotes - quotes = Quotation.objects.filter(user=user, book=book["id"]).distinct() - - book["quotes"] = list(quotes.values()) # append everything - final_books.append(book) + exported_user["books"].append(book) - # saved book lists + # saved book lists - just the remote id saved_lists = List.objects.filter(id__in=user.saved_lists.all()).distinct() - saved_lists = [l.remote_id for l in saved_lists] + exported_user["saved_lists"] = [l.remote_id for l in saved_lists] - # follows + # follows - just the remote id follows = UserFollows.objects.filter(user_subject=user).distinct() following = User.objects.filter(userfollows_user_object__in=follows).distinct() - follows = [f.remote_id for f in following] + exported_user["follows"] = [f.remote_id for f in following] - # blocks + # blocks - just the remote id blocks = UserBlocks.objects.filter(user_subject=user).distinct() blocking = User.objects.filter(userblocks_user_object__in=blocks).distinct() - blocks = [b.remote_id for b in blocking] + exported_user["blocks"] = [b.remote_id for b in blocking] - data = { - "user": exported_user, - "goals": goals_list, - "books": final_books, - "saved_lists": saved_lists, - "follows": follows, - "blocked_users": blocks, - } - - return DjangoJSONEncoder().encode(data) + return DjangoJSONEncoder().encode(exported_user) def get_books_for_user(user): - """Get all the books and editions related to a user - :returns: tuple of editions, books - """ + """Get all the books and editions related to a user""" - editions = Edition.objects.filter( - Q(shelves__user=user) - | Q(readthrough__user=user) - | Q(review__user=user) - | Q(list__user=user) - | Q(comment__user=user) - | Q(quotation__user=user) - ).distinct() - books = Book.objects.filter(id__in=editions).distinct() - return editions, books + editions = ( + Edition.objects.select_related("parent_work") + .filter( + Q(shelves__user=user) + | Q(readthrough__user=user) + | Q(review__user=user) + | Q(list__user=user) + | Q(comment__user=user) + | Q(quotation__user=user) + ) + .distinct() + ) + + return editions diff --git a/bookwyrm/models/bookwyrm_import_job.py b/bookwyrm/models/bookwyrm_import_job.py index 16dad1bfc..461f2cf0f 100644 --- a/bookwyrm/models/bookwyrm_import_job.py +++ b/bookwyrm/models/bookwyrm_import_job.py @@ -1,13 +1,11 @@ """Import a user from another Bookwyrm instance""" -from functools import reduce import json import logging -import operator from django.db.models import FileField, JSONField, CharField -from django.db.models import Q -from django.utils.dateparse import parse_datetime +from django.utils import timezone +from django.utils.html import strip_tags from django.contrib.postgres.fields import ArrayField as DjangoArrayField from bookwyrm import activitypub @@ -47,9 +45,9 @@ def start_import_task(**kwargs): job.import_data = json.loads(tar.read("archive.json").decode("utf-8")) if "include_user_profile" in job.required: - update_user_profile(job.user, tar, job.import_data.get("user")) + update_user_profile(job.user, tar, job.import_data) if "include_user_settings" in job.required: - update_user_settings(job.user, job.import_data.get("user")) + update_user_settings(job.user, job.import_data) if "include_goals" in job.required: update_goals(job.user, job.import_data.get("goals")) if "include_saved_lists" in job.required: @@ -57,7 +55,7 @@ def start_import_task(**kwargs): if "include_follows" in job.required: upsert_follows(job.user, job.import_data.get("follows")) if "include_blocks" in job.required: - upsert_user_blocks(job.user, job.import_data.get("blocked_users")) + upsert_user_blocks(job.user, job.import_data.get("blocks")) process_books(job, tar) @@ -70,10 +68,12 @@ def start_import_task(**kwargs): def process_books(job, tar): - """process user import data related to books""" + """ + Process user import data related to books + We always import the books even if not assigning + them to shelves, lists etc + """ - # create the books. We need to merge Book and Edition instances - # and also check whether these books already exist in the DB books = job.import_data.get("books") for data in books: @@ -85,308 +85,193 @@ def process_books(job, tar): if "include_readthroughs" in job.required: upsert_readthroughs(data.get("readthroughs"), job.user, book.id) - if "include_reviews" in job.required: - get_or_create_statuses( - job.user, models.Review, data.get("reviews"), book.id - ) - if "include_comments" in job.required: - get_or_create_statuses( - job.user, models.Comment, data.get("comments"), book.id + upsert_statuses( + job.user, models.Comment, data.get("comments"), book.remote_id + ) + if "include_quotations" in job.required: + upsert_statuses( + job.user, models.Quotation, data.get("quotations"), book.remote_id ) - if "include_quotes" in job.required: - get_or_create_statuses( - job.user, models.Quotation, data.get("quotes"), book.id + if "include_reviews" in job.required: + upsert_statuses( + job.user, models.Review, data.get("reviews"), book.remote_id ) + if "include_lists" in job.required: - upsert_lists(job.user, data.get("lists"), data.get("list_items"), book.id) + upsert_lists(job.user, data.get("lists"), book.id) def get_or_create_edition(book_data, tar): - """Take a JSON string of book and edition data, - find or create the edition in the database and + """Take a JSON string of work and edition data, + find or create the edition and work in the database and return an edition instance""" - cover_path = book_data.get( - "cover", None - ) # we use this further down but need to assign a var before cleaning - - clean_book = clean_values(book_data) - book = clean_book.copy() # don't mutate the original book data - - # prefer edition values only if they are not null - edition = clean_values(book["edition"]) - for key in edition.keys(): - if key not in book.keys() or ( - key in book.keys() and (edition[key] not in [None, ""]) - ): - book[key] = edition[key] - - existing = find_existing(models.Edition, book) + edition = book_data.get("edition") + existing = models.Edition.find_existing(edition) if existing: return existing - # the book is not in the local database, so we have to do this the hard way - local_authors = get_or_create_authors(book["authors"]) + # make sure we have the authors in the local DB + # replace the old author ids in the edition JSON + edition["authors"] = [] + for author in book_data.get("authors"): + parsed_author = activitypub.parse(author) + instance = parsed_author.to_model( + model=models.Author, save=True, overwrite=True + ) - # get rid of everything that's not strictly in a Book - # or is many-to-many so can't be set directly - associated_values = [ - "edition", - "authors", - "readthroughs", - "shelves", - "shelf_books", - "lists", - "list_items", - "reviews", - "comments", - "quotes", - ] + edition["authors"].append(instance.remote_id) - for val in associated_values: - del book[val] + # we will add the cover later from the tar + # don't try to load it from the old server + cover = edition.get("cover", {}) + cover_path = cover.get("url", None) + edition["cover"] = {} - # now we can save the book as an Edition - new_book = models.Edition.objects.create(**book) - new_book.authors.set(local_authors) # now we can add authors with set() + # first we need the parent work to exist + work = book_data.get("work") + work["editions"] = [] + parsed_work = activitypub.parse(work) + work_instance = parsed_work.to_model(model=models.Work, save=True, overwrite=True) - # get cover from original book_data because we lost it in clean_values + # now we have a work we can add it to the edition + # and create the edition model instance + edition["work"] = work_instance.remote_id + parsed_edition = activitypub.parse(edition) + book = parsed_edition.to_model(model=models.Edition, save=True, overwrite=True) + + # set the cover image from the tar if cover_path: - tar.write_image_to_file(cover_path, new_book.cover) + tar.write_image_to_file(cover_path, book.cover) - # NOTE: clean_values removes "last_edited_by" - # because it's a user ID from the old database - # if this is required, bookwyrm_export_job will - # need to bring in the user who edited it. - - # create parent - work = models.Work.objects.create(title=book["title"]) - work.authors.set(local_authors) - new_book.parent_work = work - - new_book.save(broadcast=False) - return new_book - - -def clean_values(data): - """clean values we don't want when creating new instances""" - - values = [ - "id", - "pk", - "remote_id", - "cover", - "preview_image", - "last_edited_by", - "last_edited_by_id", - "user", - "book_list", - "shelf_book", - "parent_work_id", - ] - - common = data.keys() & values - new_data = data - for val in common: - del new_data[val] - return new_data - - -def find_existing(cls, data): - """Given a book or author, find any existing model instances""" - - identifiers = [ - "openlibrary_key", - "inventaire_id", - "librarything_key", - "goodreads_key", - "asin", - "isfdb", - "isbn_10", - "isbn_13", - "oclc_number", - "origin_id", - "viaf", - "wikipedia_link", - "isni", - "gutenberg_id", - ] - - match_fields = [] - for i in identifiers: - if data.get(i) not in [None, ""]: - match_fields.append({i: data.get(i)}) - - if len(match_fields) > 0: - match = cls.objects.filter(reduce(operator.or_, (Q(**f) for f in match_fields))) - return match.first() - return None - - -def get_or_create_authors(data): - """Take a JSON string of authors find or create the authors - in the database and return a list of author instances""" - - authors = [] - for author in data: - clean = clean_values(author) - existing = find_existing(models.Author, clean) - if existing: - authors.append(existing) - else: - new = models.Author.objects.create(**clean) - authors.append(new) - return authors + return book def upsert_readthroughs(data, user, book_id): - """Take a JSON string of readthroughs, find or create the - instances in the database and return a list of saved instances""" + """Take a JSON string of readthroughs and + find or create the instances in the database""" - for read_thru in data: - start_date = ( - parse_datetime(read_thru["start_date"]) - if read_thru["start_date"] is not None - else None - ) - finish_date = ( - parse_datetime(read_thru["finish_date"]) - if read_thru["finish_date"] is not None - else None - ) - stopped_date = ( - parse_datetime(read_thru["stopped_date"]) - if read_thru["stopped_date"] is not None - else None - ) - readthrough = { - "user": user, - "book": models.Edition.objects.get(id=book_id), - "progress": read_thru["progress"], - "progress_mode": read_thru["progress_mode"], - "start_date": start_date, - "finish_date": finish_date, - "stopped_date": stopped_date, - "is_active": read_thru["is_active"], - } + for read_through in data: - existing = models.ReadThrough.objects.filter(**readthrough).exists() + obj = {} + keys = [ + "progress_mode", + "start_date", + "finish_date", + "stopped_date", + "is_active", + ] + for key in keys: + obj[key] = read_through[key] + obj["user_id"] = user.id + obj["book_id"] = book_id + + existing = models.ReadThrough.objects.filter(**obj).first() if not existing: - models.ReadThrough.objects.create(**readthrough) + models.ReadThrough.objects.create(**obj) -def get_or_create_statuses(user, cls, data, book_id): +def upsert_statuses(user, cls, data, book_remote_id): """Take a JSON string of a status and find or create the instances in the database""" - for book_status in data: + for status in data: - keys = [ - "content", - "raw_content", - "content_warning", - "privacy", - "sensitive", - "published_date", - "reading_status", - "name", - "rating", - "quote", - "raw_quote", + # update ids and remove replies + status["attributedTo"] = user.remote_id + status["to"] = update_followers_address(user, status["to"]) + status["cc"] = update_followers_address(user, status["cc"]) + status[ + "replies" + ] = {} # this parses incorrectly but we can't set it without knowing the new id + status["inReplyToBook"] = book_remote_id + + # save new status or do update it if it already exists + parsed = activitypub.parse(status) + instance = parsed.to_model(model=cls, save=True, overwrite=True) + + print(instance.id, instance.privacy) + + for val in [ "progress", "progress_mode", "position", + "endposition", "position_mode", - ] - common = book_status.keys() & keys - status = {k: book_status[k] for k in common} - status["published_date"] = parse_datetime(book_status["published_date"]) - if "rating" in common: - status["rating"] = float(book_status["rating"]) - book = models.Edition.objects.get(id=book_id) - exists = cls.objects.filter(**status, book=book, user=user).exists() - if not exists: - cls.objects.create(**status, book=book, user=user) + ]: + if status.get(val): + print(val, status[val]) + instance.val = status[val] + instance.save() -def upsert_lists(user, lists, items, book_id): - """Take a list and ListItems as JSON and - create DB entries if they don't already exist""" +def upsert_lists(user, lists, book_id): + """Take a list of objects each containing + a list and list item as AP objects + + Because we are creating new IDs we can't assume the id + will exist or be accurate, so we only use to_model for + adding new items after checking whether they exist . + + """ book = models.Edition.objects.get(id=book_id) - for lst in lists: - book_list = models.List.objects.filter(name=lst["name"], user=user).first() - if not book_list: - book_list = models.List.objects.create( + for blist in lists: + booklist = models.List.objects.filter(name=blist["name"], user=user).first() + if not booklist: + + blist["owner"] = user.remote_id + parsed = activitypub.parse(blist) + booklist = parsed.to_model(model=models.List, save=True, overwrite=True) + + booklist.privacy = blist["privacy"] + booklist.save() + + item = models.ListItem.objects.filter(book=book, book_list=booklist).exists() + if not item: + count = booklist.books.count() + models.ListItem.objects.create( + book=book, + book_list=booklist, user=user, - name=lst["name"], - description=lst["description"], - curation=lst["curation"], - privacy=lst["privacy"], + notes=blist["list_item"]["notes"], + approved=blist["list_item"]["approved"], + order=count + 1, ) - # If the list exists but the ListItem doesn't don't try to add it - # with the same order as an existing item - count = models.ListItem.objects.filter(book_list=book_list).count() - - for i in items[lst["name"]]: - if not models.ListItem.objects.filter( - book=book, book_list=book_list, user=user - ).exists(): - models.ListItem.objects.create( - book=book, - book_list=book_list, - user=user, - notes=i["notes"], - order=i["order"] + count, - ) - def upsert_shelves(book, user, book_data): - """Take shelf and ShelfBooks JSON objects and create + """Take shelf JSON objects and create DB entries if they don't already exist""" shelves = book_data["shelves"] - for shelf in shelves: + book_shelf = models.Shelf.objects.filter(name=shelf["name"], user=user).first() + if not book_shelf: - book_shelf = models.Shelf.objects.create( - name=shelf["name"], - user=user, - identifier=shelf["identifier"], - description=shelf["description"], - editable=shelf["editable"], - privacy=shelf["privacy"], + book_shelf = models.Shelf.objects.create(name=shelf["name"], user=user) + + # add the book as a ShelfBook if needed + if not models.ShelfBook.objects.filter( + book=book, shelf=book_shelf, user=user + ).exists(): + models.ShelfBook.objects.create( + book=book, shelf=book_shelf, user=user, shelved_date=timezone.now() ) - for shelfbook in book_data["shelf_books"][book_shelf.identifier]: - - shelved_date = parse_datetime(shelfbook["shelved_date"]) - - if not models.ShelfBook.objects.filter( - book=book, shelf=book_shelf, user=user - ).exists(): - models.ShelfBook.objects.create( - book=book, - shelf=book_shelf, - user=user, - shelved_date=shelved_date, - ) - def update_user_profile(user, tar, data): """update the user's profile from import data""" - name = data.get("name") - username = data.get("username").split("@")[0] + name = data.get("name", None) + username = data.get("preferredUsername") user.name = name if name else username - user.summary = data.get("summary") + user.summary = strip_tags(data.get("summary", None)) user.save(update_fields=["name", "summary"]) - - if data.get("avatar") is not None: + if data["icon"].get("url"): avatar_filename = next(filter(lambda n: n.startswith("avatar"), tar.getnames())) tar.write_image_to_file(avatar_filename, user.avatar) @@ -394,18 +279,28 @@ def update_user_profile(user, tar, data): def update_user_settings(user, data): """update the user's settings from import data""" - update_fields = [ - "manually_approves_followers", - "hide_follows", - "show_goal", - "show_suggested_users", - "discoverable", - "preferred_timezone", - "default_post_privacy", + update_fields = ["manually_approves_followers", "hide_follows", "discoverable"] + + ap_fields = [ + ("manuallyApprovesFollowers", "manually_approves_followers"), + ("hideFollows", "hide_follows"), + ("discoverable", "discoverable"), ] - for field in update_fields: - setattr(user, field, data[field]) + for (ap_field, bw_field) in ap_fields: + setattr(user, bw_field, data[ap_field]) + + bw_fields = [ + "show_goal", + "show_suggested_users", + "default_post_privacy", + "preferred_timezone", + ] + + for field in bw_fields: + update_fields.append(field) + setattr(user, field, data["settings"][field]) + user.save(update_fields=update_fields) @@ -421,7 +316,7 @@ def update_goals(user, data): """update the user's goals from import data""" for goal in data: - # edit the existing goal if there is one instead of making a new one + # edit the existing goal if there is one existing = models.AnnualGoal.objects.filter( year=goal["year"], user=user ).first() @@ -513,3 +408,14 @@ def upsert_user_blocks_task(job_id): return upsert_user_blocks( parent_job.user, parent_job.import_data.get("blocked_users") ) + + +def update_followers_address(user, field): + """statuses to or cc followers need to have the followers + address updated to the new local user""" + + for i, audience in enumerate(field): + if audience.rsplit("/")[-1] == "followers": + field[i] = user.followers_url + + return field diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index e0aefea0a..f9cbee8d8 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -261,9 +261,7 @@ def notify_user_on_user_export_complete( """we exported your user details! aren't you proud of us""" update_fields = update_fields or [] if not instance.complete or "complete" not in update_fields: - print("RETURNING", instance.status) return - print("NOTIFYING") Notification.objects.create( user=instance.user, notification_type=Notification.USER_EXPORT, diff --git a/bookwyrm/templates/import/import_user.html b/bookwyrm/templates/import/import_user.html index 29081df00..681ed6756 100644 --- a/bookwyrm/templates/import/import_user.html +++ b/bookwyrm/templates/import/import_user.html @@ -132,7 +132,7 @@ {% trans "Book reviews" %}