mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-12-23 08:36:32 +00:00
Merge pull request #1600 from bookwyrm-social/import-field-names
Refactors import process
This commit is contained in:
commit
afbc742f47
95 changed files with 1920 additions and 1074 deletions
|
@ -7,7 +7,7 @@ from django.utils import timezone
|
|||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.redis_store import RedisStore, r
|
||||
from bookwyrm.tasks import app
|
||||
from bookwyrm.tasks import app, LOW, MEDIUM, HIGH
|
||||
|
||||
|
||||
class ActivityStream(RedisStore):
|
||||
|
@ -277,7 +277,18 @@ def add_status_on_create(sender, instance, created, *args, **kwargs):
|
|||
|
||||
def add_status_on_create_command(sender, instance, created):
|
||||
"""runs this code only after the database commit completes"""
|
||||
add_status_task.delay(instance.id, increment_unread=created)
|
||||
priority = HIGH
|
||||
# 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)
|
||||
one_day = 60 * 60 * 24
|
||||
if (instance.created_date - instance.published_date).seconds > one_day:
|
||||
priority = LOW
|
||||
|
||||
add_status_task.apply_async(
|
||||
args=(instance.id,),
|
||||
kwargs={"increment_unread": created},
|
||||
queue=priority,
|
||||
)
|
||||
|
||||
if sender == models.Boost:
|
||||
handle_boost_task.delay(instance.id)
|
||||
|
@ -409,7 +420,7 @@ def remove_statuses_on_unshelve(sender, instance, *args, **kwargs):
|
|||
# ---- TASKS
|
||||
|
||||
|
||||
@app.task(queue="low_priority")
|
||||
@app.task(queue=LOW)
|
||||
def add_book_statuses_task(user_id, book_id):
|
||||
"""add statuses related to a book on shelve"""
|
||||
user = models.User.objects.get(id=user_id)
|
||||
|
@ -417,7 +428,7 @@ def add_book_statuses_task(user_id, book_id):
|
|||
BooksStream().add_book_statuses(user, book)
|
||||
|
||||
|
||||
@app.task(queue="low_priority")
|
||||
@app.task(queue=LOW)
|
||||
def remove_book_statuses_task(user_id, book_id):
|
||||
"""remove statuses about a book from a user's books feed"""
|
||||
user = models.User.objects.get(id=user_id)
|
||||
|
@ -425,7 +436,7 @@ def remove_book_statuses_task(user_id, book_id):
|
|||
BooksStream().remove_book_statuses(user, book)
|
||||
|
||||
|
||||
@app.task(queue="medium_priority")
|
||||
@app.task(queue=MEDIUM)
|
||||
def populate_stream_task(stream, user_id):
|
||||
"""background task for populating an empty activitystream"""
|
||||
user = models.User.objects.get(id=user_id)
|
||||
|
@ -433,7 +444,7 @@ def populate_stream_task(stream, user_id):
|
|||
stream.populate_streams(user)
|
||||
|
||||
|
||||
@app.task(queue="medium_priority")
|
||||
@app.task(queue=MEDIUM)
|
||||
def remove_status_task(status_ids):
|
||||
"""remove a status from any stream it might be in"""
|
||||
# this can take an id or a list of ids
|
||||
|
@ -446,7 +457,7 @@ def remove_status_task(status_ids):
|
|||
stream.remove_object_from_related_stores(status)
|
||||
|
||||
|
||||
@app.task(queue="high_priority")
|
||||
@app.task(queue=HIGH)
|
||||
def add_status_task(status_id, increment_unread=False):
|
||||
"""add a status to any stream it should be in"""
|
||||
status = models.Status.objects.get(id=status_id)
|
||||
|
@ -458,7 +469,7 @@ def add_status_task(status_id, increment_unread=False):
|
|||
stream.add_status(status, increment_unread=increment_unread)
|
||||
|
||||
|
||||
@app.task(queue="medium_priority")
|
||||
@app.task(queue=MEDIUM)
|
||||
def remove_user_statuses_task(viewer_id, user_id, stream_list=None):
|
||||
"""remove all statuses by a user from a viewer's stream"""
|
||||
stream_list = [streams[s] for s in stream_list] if stream_list else streams.values()
|
||||
|
@ -468,7 +479,7 @@ def remove_user_statuses_task(viewer_id, user_id, stream_list=None):
|
|||
stream.remove_user_statuses(viewer, user)
|
||||
|
||||
|
||||
@app.task(queue="medium_priority")
|
||||
@app.task(queue=MEDIUM)
|
||||
def add_user_statuses_task(viewer_id, user_id, stream_list=None):
|
||||
"""add all statuses by a user to a viewer's stream"""
|
||||
stream_list = [streams[s] for s in stream_list] if stream_list else streams.values()
|
||||
|
@ -478,7 +489,7 @@ def add_user_statuses_task(viewer_id, user_id, stream_list=None):
|
|||
stream.add_user_statuses(viewer, user)
|
||||
|
||||
|
||||
@app.task(queue="medium_priority")
|
||||
@app.task(queue=MEDIUM)
|
||||
def handle_boost_task(boost_id):
|
||||
"""remove the original post and other, earlier boosts"""
|
||||
instance = models.Status.objects.get(id=boost_id)
|
||||
|
|
|
@ -82,6 +82,8 @@ def search_identifiers(query, *filters, return_first=False):
|
|||
*filters, reduce(operator.or_, (Q(**f) for f in or_filters))
|
||||
).distinct()
|
||||
if results.count() <= 1:
|
||||
if return_first:
|
||||
return results.first()
|
||||
return results
|
||||
|
||||
# when there are multiple editions of the same work, pick the default.
|
||||
|
@ -124,6 +126,7 @@ def search_title_author(query, min_confidence, *filters, return_first=False):
|
|||
result = default
|
||||
else:
|
||||
result = editions.first()
|
||||
|
||||
if return_first:
|
||||
return result
|
||||
list_results.append(result)
|
||||
|
|
|
@ -7,10 +7,3 @@ class GoodreadsImporter(Importer):
|
|||
For a more complete example of overriding see librarything_import.py"""
|
||||
|
||||
service = "Goodreads"
|
||||
|
||||
def parse_fields(self, entry):
|
||||
"""handle the specific fields in goodreads csvs"""
|
||||
entry.update({"import_source": self.service})
|
||||
# add missing 'Date Started' field
|
||||
entry.update({"Date Started": None})
|
||||
return entry
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.models import ImportJob, ImportItem
|
||||
from bookwyrm.tasks import app
|
||||
from bookwyrm.tasks import app, LOW
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -15,33 +15,90 @@ logger = logging.getLogger(__name__)
|
|||
class Importer:
|
||||
"""Generic class for csv data import from an outside service"""
|
||||
|
||||
service = "Unknown"
|
||||
service = "Import"
|
||||
delimiter = ","
|
||||
encoding = "UTF-8"
|
||||
mandatory_fields = ["Title", "Author"]
|
||||
|
||||
# these are from Goodreads
|
||||
row_mappings_guesses = [
|
||||
("id", ["id", "book id"]),
|
||||
("title", ["title"]),
|
||||
("authors", ["author", "authors", "primary author"]),
|
||||
("isbn_10", ["isbn10", "isbn"]),
|
||||
("isbn_13", ["isbn13", "isbn", "isbns"]),
|
||||
("shelf", ["shelf", "exclusive shelf", "read status"]),
|
||||
("review_name", ["review name"]),
|
||||
("review_body", ["my review", "review"]),
|
||||
("rating", ["my rating", "rating", "star rating"]),
|
||||
("date_added", ["date added", "entry date", "added"]),
|
||||
("date_started", ["date started", "started"]),
|
||||
("date_finished", ["date finished", "last date read", "date read", "finished"]),
|
||||
]
|
||||
date_fields = ["date_added", "date_started", "date_finished"]
|
||||
shelf_mapping_guesses = {
|
||||
"to-read": ["to-read"],
|
||||
"read": ["read"],
|
||||
"reading": ["currently-reading", "reading"],
|
||||
}
|
||||
|
||||
def create_job(self, user, csv_file, include_reviews, privacy):
|
||||
"""check over a csv and creates a database entry for the job"""
|
||||
csv_reader = csv.DictReader(csv_file, delimiter=self.delimiter)
|
||||
rows = enumerate(list(csv_reader))
|
||||
job = ImportJob.objects.create(
|
||||
user=user, include_reviews=include_reviews, privacy=privacy
|
||||
user=user,
|
||||
include_reviews=include_reviews,
|
||||
privacy=privacy,
|
||||
mappings=self.create_row_mappings(csv_reader.fieldnames),
|
||||
source=self.service,
|
||||
)
|
||||
for index, entry in enumerate(
|
||||
list(csv.DictReader(csv_file, delimiter=self.delimiter))
|
||||
):
|
||||
if not all(x in entry for x in self.mandatory_fields):
|
||||
raise ValueError("Author and title must be in data.")
|
||||
entry = self.parse_fields(entry)
|
||||
self.save_item(job, index, entry)
|
||||
|
||||
for index, entry in rows:
|
||||
self.create_item(job, index, entry)
|
||||
return job
|
||||
|
||||
def save_item(self, job, index, data): # pylint: disable=no-self-use
|
||||
"""creates and saves an import item"""
|
||||
ImportItem(job=job, index=index, data=data).save()
|
||||
def update_legacy_job(self, job):
|
||||
"""patch up a job that was in the old format"""
|
||||
items = job.items
|
||||
headers = list(items.first().data.keys())
|
||||
job.mappings = self.create_row_mappings(headers)
|
||||
job.updated_date = timezone.now()
|
||||
job.save()
|
||||
|
||||
def parse_fields(self, entry):
|
||||
"""updates csv data with additional info"""
|
||||
entry.update({"import_source": self.service})
|
||||
return entry
|
||||
for item in items.all():
|
||||
normalized = self.normalize_row(item.data, job.mappings)
|
||||
normalized["shelf"] = self.get_shelf(normalized)
|
||||
item.normalized_data = normalized
|
||||
item.save()
|
||||
|
||||
def create_row_mappings(self, headers):
|
||||
"""guess what the headers mean"""
|
||||
mappings = {}
|
||||
for (key, guesses) in self.row_mappings_guesses:
|
||||
value = [h for h in headers if h.lower() in guesses]
|
||||
value = value[0] if len(value) else None
|
||||
if value:
|
||||
headers.remove(value)
|
||||
mappings[key] = value
|
||||
return mappings
|
||||
|
||||
def create_item(self, job, index, data):
|
||||
"""creates and saves an import item"""
|
||||
normalized = self.normalize_row(data, job.mappings)
|
||||
normalized["shelf"] = self.get_shelf(normalized)
|
||||
ImportItem(job=job, index=index, data=data, normalized_data=normalized).save()
|
||||
|
||||
def get_shelf(self, normalized_row):
|
||||
"""determine which shelf to use"""
|
||||
shelf_name = normalized_row["shelf"]
|
||||
shelf = [
|
||||
s for (s, gs) in self.shelf_mapping_guesses.items() if shelf_name in gs
|
||||
]
|
||||
return shelf[0] if shelf else None
|
||||
|
||||
def normalize_row(self, entry, mappings): # pylint: disable=no-self-use
|
||||
"""use the dataclass to create the formatted row of data"""
|
||||
return {k: entry.get(v) for k, v in mappings.items()}
|
||||
|
||||
def create_retry_job(self, user, original_job, items):
|
||||
"""retry items that didn't import"""
|
||||
|
@ -49,55 +106,65 @@ class Importer:
|
|||
user=user,
|
||||
include_reviews=original_job.include_reviews,
|
||||
privacy=original_job.privacy,
|
||||
# TODO: allow users to adjust mappings
|
||||
mappings=original_job.mappings,
|
||||
retry=True,
|
||||
)
|
||||
for item in items:
|
||||
self.save_item(job, item.index, item.data)
|
||||
# this will re-normalize the raw data
|
||||
self.create_item(job, item.index, item.data)
|
||||
return job
|
||||
|
||||
def start_import(self, job):
|
||||
def start_import(self, job): # pylint: disable=no-self-use
|
||||
"""initalizes a csv import job"""
|
||||
result = import_data.delay(self.service, job.id)
|
||||
result = start_import_task.delay(job.id)
|
||||
job.task_id = result.id
|
||||
job.save()
|
||||
|
||||
|
||||
@app.task(queue="low_priority")
|
||||
def import_data(source, job_id):
|
||||
"""does the actual lookup work in a celery task"""
|
||||
def start_import_task(job_id):
|
||||
"""trigger the child tasks for each row"""
|
||||
job = ImportJob.objects.get(id=job_id)
|
||||
# these are sub-tasks so that one big task doesn't use up all the memory in celery
|
||||
for item in job.items.values_list("id", flat=True).all():
|
||||
import_item_task.delay(item)
|
||||
|
||||
|
||||
@app.task(queue="low_priority")
|
||||
def import_item_task(item_id):
|
||||
"""resolve a row into a book"""
|
||||
item = models.ImportItem.objects.get(id=item_id)
|
||||
try:
|
||||
for item in job.items.all():
|
||||
try:
|
||||
item.resolve()
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
logger.exception(err)
|
||||
item.fail_reason = _("Error loading book")
|
||||
item.save()
|
||||
continue
|
||||
item.resolve()
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
item.fail_reason = _("Error loading book")
|
||||
item.save()
|
||||
item.update_job()
|
||||
raise err
|
||||
|
||||
if item.book or item.book_guess:
|
||||
item.save()
|
||||
if item.book:
|
||||
# shelves book and handles reviews
|
||||
handle_imported_book(item)
|
||||
else:
|
||||
item.fail_reason = _("Could not find a match for book")
|
||||
|
||||
if item.book:
|
||||
# shelves book and handles reviews
|
||||
handle_imported_book(
|
||||
source, job.user, item, job.include_reviews, job.privacy
|
||||
)
|
||||
else:
|
||||
item.fail_reason = _("Could not find a match for book")
|
||||
item.save()
|
||||
finally:
|
||||
job.complete = True
|
||||
job.save()
|
||||
item.save()
|
||||
item.update_job()
|
||||
|
||||
|
||||
def handle_imported_book(source, user, item, include_reviews, privacy):
|
||||
def handle_imported_book(item):
|
||||
"""process a csv and then post about it"""
|
||||
job = item.job
|
||||
user = job.user
|
||||
if isinstance(item.book, models.Work):
|
||||
item.book = item.book.default_edition
|
||||
if not item.book:
|
||||
item.fail_reason = _("Error loading book")
|
||||
item.save()
|
||||
return
|
||||
if not isinstance(item.book, models.Edition):
|
||||
item.book = item.book.edition
|
||||
|
||||
existing_shelf = models.ShelfBook.objects.filter(book=item.book, user=user).exists()
|
||||
|
||||
|
@ -105,9 +172,9 @@ def handle_imported_book(source, user, item, include_reviews, privacy):
|
|||
if item.shelf and not existing_shelf:
|
||||
desired_shelf = models.Shelf.objects.get(identifier=item.shelf, user=user)
|
||||
shelved_date = item.date_added or timezone.now()
|
||||
models.ShelfBook.objects.create(
|
||||
models.ShelfBook(
|
||||
book=item.book, shelf=desired_shelf, user=user, shelved_date=shelved_date
|
||||
)
|
||||
).save(priority=LOW)
|
||||
|
||||
for read in item.reads:
|
||||
# check for an existing readthrough with the same dates
|
||||
|
@ -122,37 +189,52 @@ def handle_imported_book(source, user, item, include_reviews, privacy):
|
|||
read.user = user
|
||||
read.save()
|
||||
|
||||
if include_reviews and (item.rating or item.review):
|
||||
if job.include_reviews and (item.rating or item.review) and not item.linked_review:
|
||||
# we don't know the publication date of the review,
|
||||
# but "now" is a bad guess
|
||||
published_date_guess = item.date_read or item.date_added
|
||||
if item.review:
|
||||
# pylint: disable=consider-using-f-string
|
||||
review_title = (
|
||||
"Review of {!r} on {!r}".format(
|
||||
item.book.title,
|
||||
source,
|
||||
)
|
||||
if item.review
|
||||
else ""
|
||||
review_title = "Review of {!r} on {!r}".format(
|
||||
item.book.title,
|
||||
job.source,
|
||||
)
|
||||
review = models.Review(
|
||||
review = models.Review.objects.filter(
|
||||
user=user,
|
||||
book=item.book,
|
||||
name=review_title,
|
||||
content=item.review,
|
||||
rating=item.rating,
|
||||
published_date=published_date_guess,
|
||||
privacy=privacy,
|
||||
)
|
||||
).first()
|
||||
if not review:
|
||||
review = models.Review(
|
||||
user=user,
|
||||
book=item.book,
|
||||
name=review_title,
|
||||
content=item.review,
|
||||
rating=item.rating,
|
||||
published_date=published_date_guess,
|
||||
privacy=job.privacy,
|
||||
)
|
||||
review.save(software="bookwyrm", priority=LOW)
|
||||
else:
|
||||
# just a rating
|
||||
review = models.ReviewRating(
|
||||
review = models.ReviewRating.objects.filter(
|
||||
user=user,
|
||||
book=item.book,
|
||||
rating=item.rating,
|
||||
published_date=published_date_guess,
|
||||
privacy=privacy,
|
||||
)
|
||||
rating=item.rating,
|
||||
).first()
|
||||
if not review:
|
||||
review = models.ReviewRating(
|
||||
user=user,
|
||||
book=item.book,
|
||||
rating=item.rating,
|
||||
published_date=published_date_guess,
|
||||
privacy=job.privacy,
|
||||
)
|
||||
review.save(software="bookwyrm", priority=LOW)
|
||||
|
||||
# only broadcast this review to other bookwyrm instances
|
||||
review.save(software="bookwyrm")
|
||||
item.linked_review = review
|
||||
item.save()
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
""" handle reading a csv from librarything """
|
||||
""" handle reading a tsv from librarything """
|
||||
import re
|
||||
import math
|
||||
|
||||
from . import Importer
|
||||
|
||||
|
||||
|
@ -11,32 +9,18 @@ class LibrarythingImporter(Importer):
|
|||
service = "LibraryThing"
|
||||
delimiter = "\t"
|
||||
encoding = "ISO-8859-1"
|
||||
# mandatory_fields : fields matching the book title and author
|
||||
mandatory_fields = ["Title", "Primary Author"]
|
||||
|
||||
def parse_fields(self, entry):
|
||||
"""custom parsing for librarything"""
|
||||
data = {}
|
||||
data["import_source"] = self.service
|
||||
data["Book Id"] = entry["Book Id"]
|
||||
data["Title"] = entry["Title"]
|
||||
data["Author"] = entry["Primary Author"]
|
||||
data["ISBN13"] = entry["ISBN"]
|
||||
data["My Review"] = entry["Review"]
|
||||
if entry["Rating"]:
|
||||
data["My Rating"] = math.ceil(float(entry["Rating"]))
|
||||
else:
|
||||
data["My Rating"] = ""
|
||||
data["Date Added"] = re.sub(r"\[|\]", "", entry["Entry Date"])
|
||||
data["Date Started"] = re.sub(r"\[|\]", "", entry["Date Started"])
|
||||
data["Date Read"] = re.sub(r"\[|\]", "", entry["Date Read"])
|
||||
def normalize_row(self, entry, mappings): # pylint: disable=no-self-use
|
||||
"""use the dataclass to create the formatted row of data"""
|
||||
remove_brackets = lambda v: re.sub(r"\[|\]", "", v) if v else None
|
||||
normalized = {k: remove_brackets(entry.get(v)) for k, v in mappings.items()}
|
||||
isbn_13 = normalized["isbn_13"].split(", ")
|
||||
normalized["isbn_13"] = isbn_13[1] if len(isbn_13) > 0 else None
|
||||
return normalized
|
||||
|
||||
data["Exclusive Shelf"] = None
|
||||
if data["Date Read"]:
|
||||
data["Exclusive Shelf"] = "read"
|
||||
elif data["Date Started"]:
|
||||
data["Exclusive Shelf"] = "reading"
|
||||
else:
|
||||
data["Exclusive Shelf"] = "to-read"
|
||||
|
||||
return data
|
||||
def get_shelf(self, normalized_row):
|
||||
if normalized_row["date_finished"]:
|
||||
return "read"
|
||||
if normalized_row["date_started"]:
|
||||
return "reading"
|
||||
return "to-read"
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
""" handle reading a csv from librarything """
|
||||
import re
|
||||
|
||||
""" handle reading a csv from storygraph"""
|
||||
from . import Importer
|
||||
|
||||
|
||||
|
@ -8,26 +6,3 @@ class StorygraphImporter(Importer):
|
|||
"""csv downloads from librarything"""
|
||||
|
||||
service = "Storygraph"
|
||||
# mandatory_fields : fields matching the book title and author
|
||||
mandatory_fields = ["Title"]
|
||||
|
||||
def parse_fields(self, entry):
|
||||
"""custom parsing for storygraph"""
|
||||
data = {}
|
||||
data["import_source"] = self.service
|
||||
data["Title"] = entry["Title"]
|
||||
data["Author"] = entry["Authors"] if "Authors" in entry else entry["Author"]
|
||||
data["ISBN13"] = entry["ISBN"]
|
||||
data["My Review"] = entry["Review"]
|
||||
if entry["Star Rating"]:
|
||||
data["My Rating"] = float(entry["Star Rating"])
|
||||
else:
|
||||
data["My Rating"] = ""
|
||||
|
||||
data["Date Added"] = re.sub(r"[/]", "-", entry["Date Added"])
|
||||
data["Date Read"] = re.sub(r"[/]", "-", entry["Last Date Read"])
|
||||
|
||||
data["Exclusive Shelf"] = (
|
||||
{"read": "read", "currently-reading": "reading", "to-read": "to-read"}
|
||||
).get(entry["Read Status"], None)
|
||||
return data
|
||||
|
|
25
bookwyrm/migrations/0113_auto_20211110_2104.py
Normal file
25
bookwyrm/migrations/0113_auto_20211110_2104.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Generated by Django 3.2.5 on 2021-11-10 21:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0112_auto_20211022_0844"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="importitem",
|
||||
name="normalized_data",
|
||||
field=models.JSONField(default={}),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="importjob",
|
||||
name="mappings",
|
||||
field=models.JSONField(default={}),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
19
bookwyrm/migrations/0114_importjob_source.py
Normal file
19
bookwyrm/migrations/0114_importjob_source.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 3.2.5 on 2021-11-13 00:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0113_auto_20211110_2104"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="importjob",
|
||||
name="source",
|
||||
field=models.CharField(default="Import", max_length=100),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
24
bookwyrm/migrations/0115_importitem_linked_review.py
Normal file
24
bookwyrm/migrations/0115_importitem_linked_review.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 3.2.5 on 2021-11-13 19:35
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0114_importjob_source"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="importitem",
|
||||
name="linked_review",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="bookwyrm.review",
|
||||
),
|
||||
),
|
||||
]
|
23
bookwyrm/migrations/0116_auto_20211114_1734.py
Normal file
23
bookwyrm/migrations/0116_auto_20211114_1734.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 3.2.5 on 2021-11-14 17:34
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0115_importitem_linked_review"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="importjob",
|
||||
name="task_id",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="importjob",
|
||||
name="updated_date",
|
||||
field=models.DateTimeField(default=django.utils.timezone.now),
|
||||
),
|
||||
]
|
|
@ -20,7 +20,7 @@ from django.utils.http import http_date
|
|||
from bookwyrm import activitypub
|
||||
from bookwyrm.settings import USER_AGENT, PAGE_LENGTH
|
||||
from bookwyrm.signatures import make_signature, make_digest
|
||||
from bookwyrm.tasks import app
|
||||
from bookwyrm.tasks import app, MEDIUM
|
||||
from bookwyrm.models.fields import ImageField, ManyToManyField
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -29,7 +29,6 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
PropertyField = namedtuple("PropertyField", ("set_activity_from_field"))
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def set_activity_from_property_field(activity, obj, field):
|
||||
"""assign a model property value to the activity json"""
|
||||
|
@ -126,12 +125,15 @@ class ActivitypubMixin:
|
|||
# there OUGHT to be only one match
|
||||
return match.first()
|
||||
|
||||
def broadcast(self, activity, sender, software=None):
|
||||
def broadcast(self, activity, sender, software=None, queue=MEDIUM):
|
||||
"""send out an activity"""
|
||||
broadcast_task.delay(
|
||||
sender.id,
|
||||
json.dumps(activity, cls=activitypub.ActivityEncoder),
|
||||
self.get_recipients(software=software),
|
||||
broadcast_task.apply_async(
|
||||
args=(
|
||||
sender.id,
|
||||
json.dumps(activity, cls=activitypub.ActivityEncoder),
|
||||
self.get_recipients(software=software),
|
||||
),
|
||||
queue=queue,
|
||||
)
|
||||
|
||||
def get_recipients(self, software=None):
|
||||
|
@ -195,7 +197,7 @@ class ActivitypubMixin:
|
|||
class ObjectMixin(ActivitypubMixin):
|
||||
"""add this mixin for object models that are AP serializable"""
|
||||
|
||||
def save(self, *args, created=None, software=None, **kwargs):
|
||||
def save(self, *args, created=None, software=None, priority=MEDIUM, **kwargs):
|
||||
"""broadcast created/updated/deleted objects as appropriate"""
|
||||
broadcast = kwargs.get("broadcast", True)
|
||||
# this bonus kwarg would cause an error in the base save method
|
||||
|
@ -222,12 +224,14 @@ class ObjectMixin(ActivitypubMixin):
|
|||
# do we have a "pure" activitypub version of this for mastodon?
|
||||
if software != "bookwyrm" and hasattr(self, "pure_content"):
|
||||
pure_activity = self.to_create_activity(user, pure=True)
|
||||
self.broadcast(pure_activity, user, software="other")
|
||||
self.broadcast(
|
||||
pure_activity, user, software="other", queue=priority
|
||||
)
|
||||
# set bookwyrm so that that type is also sent
|
||||
software = "bookwyrm"
|
||||
# sends to BW only if we just did a pure version for masto
|
||||
activity = self.to_create_activity(user)
|
||||
self.broadcast(activity, user, software=software)
|
||||
self.broadcast(activity, user, software=software, queue=priority)
|
||||
except AttributeError:
|
||||
# janky as heck, this catches the mutliple inheritence chain
|
||||
# for boosts and ignores this auxilliary broadcast
|
||||
|
@ -251,7 +255,7 @@ class ObjectMixin(ActivitypubMixin):
|
|||
activity = self.to_delete_activity(user)
|
||||
else:
|
||||
activity = self.to_update_activity(user)
|
||||
self.broadcast(activity, user)
|
||||
self.broadcast(activity, user, queue=priority)
|
||||
|
||||
def to_create_activity(self, user, **kwargs):
|
||||
"""returns the object wrapped in a Create activity"""
|
||||
|
@ -374,9 +378,9 @@ class CollectionItemMixin(ActivitypubMixin):
|
|||
|
||||
activity_serializer = activitypub.CollectionItem
|
||||
|
||||
def broadcast(self, activity, sender, software="bookwyrm"):
|
||||
def broadcast(self, activity, sender, software="bookwyrm", queue=MEDIUM):
|
||||
"""only send book collection updates to other bookwyrm instances"""
|
||||
super().broadcast(activity, sender, software=software)
|
||||
super().broadcast(activity, sender, software=software, queue=queue)
|
||||
|
||||
@property
|
||||
def privacy(self):
|
||||
|
@ -395,7 +399,7 @@ class CollectionItemMixin(ActivitypubMixin):
|
|||
return []
|
||||
return [collection_field.user]
|
||||
|
||||
def save(self, *args, broadcast=True, **kwargs):
|
||||
def save(self, *args, broadcast=True, priority=MEDIUM, **kwargs):
|
||||
"""broadcast updated"""
|
||||
# first off, we want to save normally no matter what
|
||||
super().save(*args, **kwargs)
|
||||
|
@ -406,7 +410,7 @@ class CollectionItemMixin(ActivitypubMixin):
|
|||
|
||||
# adding an obj to the collection
|
||||
activity = self.to_add_activity(self.user)
|
||||
self.broadcast(activity, self.user)
|
||||
self.broadcast(activity, self.user, queue=priority)
|
||||
|
||||
def delete(self, *args, broadcast=True, **kwargs):
|
||||
"""broadcast a remove activity"""
|
||||
|
@ -439,12 +443,12 @@ class CollectionItemMixin(ActivitypubMixin):
|
|||
class ActivityMixin(ActivitypubMixin):
|
||||
"""add this mixin for models that are AP serializable"""
|
||||
|
||||
def save(self, *args, broadcast=True, **kwargs):
|
||||
def save(self, *args, broadcast=True, priority=MEDIUM, **kwargs):
|
||||
"""broadcast activity"""
|
||||
super().save(*args, **kwargs)
|
||||
user = self.user if hasattr(self, "user") else self.user_subject
|
||||
if broadcast and user.local:
|
||||
self.broadcast(self.to_activity(), user)
|
||||
self.broadcast(self.to_activity(), user, queue=priority)
|
||||
|
||||
def delete(self, *args, broadcast=True, **kwargs):
|
||||
"""nevermind, undo that activity"""
|
||||
|
@ -501,7 +505,7 @@ def unfurl_related_field(related_field, sort_field=None):
|
|||
return related_field.remote_id
|
||||
|
||||
|
||||
@app.task(queue="medium_priority")
|
||||
@app.task(queue=MEDIUM)
|
||||
def broadcast_task(sender_id, activity, recipients):
|
||||
"""the celery task for broadcast"""
|
||||
user_model = apps.get_model("bookwyrm.User", require_ready=True)
|
||||
|
|
|
@ -66,9 +66,10 @@ class BookDataModel(ObjectMixin, BookWyrmModel):
|
|||
self.remote_id = None
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def broadcast(self, activity, sender, software="bookwyrm"):
|
||||
# pylint: disable=arguments-differ
|
||||
def broadcast(self, activity, sender, software="bookwyrm", **kwargs):
|
||||
"""only send book data updates to other bookwyrm instances"""
|
||||
super().broadcast(activity, sender, software=software)
|
||||
super().broadcast(activity, sender, software=software, **kwargs)
|
||||
|
||||
|
||||
class Book(BookDataModel):
|
||||
|
|
|
@ -6,20 +6,14 @@ from django.db import models
|
|||
from django.utils import timezone
|
||||
|
||||
from bookwyrm.connectors import connector_manager
|
||||
from bookwyrm.models import ReadThrough, User, Book
|
||||
from bookwyrm.models import ReadThrough, User, Book, Edition
|
||||
from .fields import PrivacyLevels
|
||||
|
||||
|
||||
# Mapping goodreads -> bookwyrm shelf titles.
|
||||
GOODREADS_SHELVES = {
|
||||
"read": "read",
|
||||
"currently-reading": "reading",
|
||||
"to-read": "to-read",
|
||||
}
|
||||
|
||||
|
||||
def unquote_string(text):
|
||||
"""resolve csv quote weirdness"""
|
||||
if not text:
|
||||
return None
|
||||
match = re.match(r'="([^"]*)"', text)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
@ -41,14 +35,21 @@ class ImportJob(models.Model):
|
|||
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
created_date = models.DateTimeField(default=timezone.now)
|
||||
task_id = models.CharField(max_length=100, null=True)
|
||||
updated_date = models.DateTimeField(default=timezone.now)
|
||||
include_reviews = models.BooleanField(default=True)
|
||||
mappings = models.JSONField()
|
||||
complete = models.BooleanField(default=False)
|
||||
source = models.CharField(max_length=100)
|
||||
privacy = models.CharField(
|
||||
max_length=255, default="public", choices=PrivacyLevels.choices
|
||||
)
|
||||
retry = models.BooleanField(default=False)
|
||||
|
||||
@property
|
||||
def pending_items(self):
|
||||
"""items that haven't been processed yet"""
|
||||
return self.items.filter(fail_reason__isnull=True, book__isnull=True)
|
||||
|
||||
|
||||
class ImportItem(models.Model):
|
||||
"""a single line of a csv being imported"""
|
||||
|
@ -56,6 +57,7 @@ class ImportItem(models.Model):
|
|||
job = models.ForeignKey(ImportJob, on_delete=models.CASCADE, related_name="items")
|
||||
index = models.IntegerField()
|
||||
data = models.JSONField()
|
||||
normalized_data = models.JSONField()
|
||||
book = models.ForeignKey(Book, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
book_guess = models.ForeignKey(
|
||||
Book,
|
||||
|
@ -65,9 +67,26 @@ class ImportItem(models.Model):
|
|||
related_name="book_guess",
|
||||
)
|
||||
fail_reason = models.TextField(null=True)
|
||||
linked_review = models.ForeignKey(
|
||||
"Review", on_delete=models.SET_NULL, null=True, blank=True
|
||||
)
|
||||
|
||||
def update_job(self):
|
||||
"""let the job know when the items get work done"""
|
||||
job = self.job
|
||||
job.updated_date = timezone.now()
|
||||
job.save()
|
||||
if not job.pending_items.exists() and not job.complete:
|
||||
job.complete = True
|
||||
job.save(update_fields=["complete"])
|
||||
|
||||
def resolve(self):
|
||||
"""try various ways to lookup a book"""
|
||||
# we might be calling this after manually adding the book,
|
||||
# so no need to do searches
|
||||
if self.book:
|
||||
return
|
||||
|
||||
if self.isbn:
|
||||
self.book = self.get_book_from_isbn()
|
||||
else:
|
||||
|
@ -85,6 +104,10 @@ class ImportItem(models.Model):
|
|||
self.isbn, min_confidence=0.999
|
||||
)
|
||||
if search_result:
|
||||
# it's already in the right format
|
||||
if isinstance(search_result, Edition):
|
||||
return search_result
|
||||
# it's just a search result, book needs to be created
|
||||
# raises ConnectorException
|
||||
return search_result.connector.get_or_create_book(search_result.key)
|
||||
return None
|
||||
|
@ -96,6 +119,8 @@ class ImportItem(models.Model):
|
|||
search_term, min_confidence=0.1
|
||||
)
|
||||
if search_result:
|
||||
if isinstance(search_result, Edition):
|
||||
return (search_result, 1)
|
||||
# raises ConnectorException
|
||||
return (
|
||||
search_result.connector.get_or_create_book(search_result.key),
|
||||
|
@ -106,56 +131,62 @@ class ImportItem(models.Model):
|
|||
@property
|
||||
def title(self):
|
||||
"""get the book title"""
|
||||
return self.data["Title"]
|
||||
return self.normalized_data.get("title")
|
||||
|
||||
@property
|
||||
def author(self):
|
||||
"""get the book title"""
|
||||
return self.data["Author"]
|
||||
"""get the book's authors"""
|
||||
return self.normalized_data.get("authors")
|
||||
|
||||
@property
|
||||
def isbn(self):
|
||||
"""pulls out the isbn13 field from the csv line data"""
|
||||
return unquote_string(self.data["ISBN13"])
|
||||
return unquote_string(self.normalized_data.get("isbn_13")) or unquote_string(
|
||||
self.normalized_data.get("isbn_10")
|
||||
)
|
||||
|
||||
@property
|
||||
def shelf(self):
|
||||
"""the goodreads shelf field"""
|
||||
if self.data["Exclusive Shelf"]:
|
||||
return GOODREADS_SHELVES.get(self.data["Exclusive Shelf"])
|
||||
return None
|
||||
return self.normalized_data.get("shelf")
|
||||
|
||||
@property
|
||||
def review(self):
|
||||
"""a user-written review, to be imported with the book data"""
|
||||
return self.data["My Review"]
|
||||
return self.normalized_data.get("review_body")
|
||||
|
||||
@property
|
||||
def rating(self):
|
||||
"""x/5 star rating for a book"""
|
||||
if self.data.get("My Rating", None):
|
||||
return int(self.data["My Rating"])
|
||||
if self.normalized_data.get("rating"):
|
||||
return float(self.normalized_data.get("rating"))
|
||||
return None
|
||||
|
||||
@property
|
||||
def date_added(self):
|
||||
"""when the book was added to this dataset"""
|
||||
if self.data["Date Added"]:
|
||||
return timezone.make_aware(dateutil.parser.parse(self.data["Date Added"]))
|
||||
if self.normalized_data.get("date_added"):
|
||||
return timezone.make_aware(
|
||||
dateutil.parser.parse(self.normalized_data.get("date_added"))
|
||||
)
|
||||
return None
|
||||
|
||||
@property
|
||||
def date_started(self):
|
||||
"""when the book was started"""
|
||||
if "Date Started" in self.data and self.data["Date Started"]:
|
||||
return timezone.make_aware(dateutil.parser.parse(self.data["Date Started"]))
|
||||
if self.normalized_data.get("date_started"):
|
||||
return timezone.make_aware(
|
||||
dateutil.parser.parse(self.normalized_data.get("date_started"))
|
||||
)
|
||||
return None
|
||||
|
||||
@property
|
||||
def date_read(self):
|
||||
"""the date a book was completed"""
|
||||
if self.data["Date Read"]:
|
||||
return timezone.make_aware(dateutil.parser.parse(self.data["Date Read"]))
|
||||
if self.normalized_data.get("date_finished"):
|
||||
return timezone.make_aware(
|
||||
dateutil.parser.parse(self.normalized_data.get("date_finished"))
|
||||
)
|
||||
return None
|
||||
|
||||
@property
|
||||
|
@ -174,7 +205,9 @@ class ImportItem(models.Model):
|
|||
if start_date and start_date is not None and not self.date_read:
|
||||
return [ReadThrough(start_date=start_date)]
|
||||
if self.date_read:
|
||||
start_date = start_date if start_date < self.date_read else None
|
||||
start_date = (
|
||||
start_date if start_date and start_date < self.date_read else None
|
||||
)
|
||||
return [
|
||||
ReadThrough(
|
||||
start_date=start_date,
|
||||
|
@ -185,8 +218,10 @@ class ImportItem(models.Model):
|
|||
|
||||
def __repr__(self):
|
||||
# pylint: disable=consider-using-f-string
|
||||
return "<{!r}Item {!r}>".format(self.data["import_source"], self.data["Title"])
|
||||
return "<{!r} Item {!r}>".format(self.index, self.normalized_data.get("title"))
|
||||
|
||||
def __str__(self):
|
||||
# pylint: disable=consider-using-f-string
|
||||
return "{} by {}".format(self.data["Title"], self.data["Author"])
|
||||
return "{} by {}".format(
|
||||
self.normalized_data.get("title"), self.normalized_data.get("authors")
|
||||
)
|
||||
|
|
|
@ -157,9 +157,12 @@ def notify_user_on_unboost(sender, instance, *args, **kwargs):
|
|||
|
||||
@receiver(models.signals.post_save, sender=ImportJob)
|
||||
# pylint: disable=unused-argument
|
||||
def notify_user_on_import_complete(sender, instance, *args, **kwargs):
|
||||
def notify_user_on_import_complete(
|
||||
sender, instance, *args, update_fields=None, **kwargs
|
||||
):
|
||||
"""we imported your books! aren't you proud of us"""
|
||||
if not instance.complete:
|
||||
update_fields = update_fields or []
|
||||
if not instance.complete or "complete" not in update_fields:
|
||||
return
|
||||
Notification.objects.create(
|
||||
user=instance.user,
|
||||
|
|
|
@ -9,3 +9,8 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "celerywyrm.settings")
|
|||
app = Celery(
|
||||
"tasks", broker=settings.CELERY_BROKER_URL, backend=settings.CELERY_RESULT_BACKEND
|
||||
)
|
||||
|
||||
# priorities
|
||||
LOW = "low_priority"
|
||||
MEDIUM = "medium_priority"
|
||||
HIGH = "high_priority"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{% if user.is_authenticated %}
|
||||
<div class="column is-one-third">
|
||||
<section class="block">
|
||||
<h2 class="title is-4">{% trans "Your books" %}</h2>
|
||||
<h2 class="title is-4">{% trans "Your Books" %}</h2>
|
||||
{% if not suggested_books %}
|
||||
<p>{% trans "There are no books here right now! Try searching for a book to get started" %}</p>
|
||||
{% else %}
|
||||
|
|
|
@ -6,152 +6,223 @@
|
|||
{% block title %}{% trans "Import Status" %}{% endblock %}
|
||||
|
||||
{% block content %}{% spaceless %}
|
||||
<div class="block">
|
||||
<h1 class="title">{% trans "Import Status" %}</h1>
|
||||
<a href="{% url 'import' %}" class="has-text-weight-normal help subtitle is-link">{% trans "Back to imports" %}</a>
|
||||
|
||||
{% if task.failed %}
|
||||
<div class="notification is-danger">{% trans "TASK FAILED" %}</div>
|
||||
{% endif %}
|
||||
|
||||
<dl>
|
||||
<dt class="is-pulled-left mr-5">{% trans "Import started:" %}</dt>
|
||||
<dd>{{ job.created_date | naturaltime }}</dd>
|
||||
|
||||
{% if job.complete %}
|
||||
<dt class="is-pulled-left mr-5">{% trans "Import completed:" %}</dt>
|
||||
<dd>{{ task.date_done | naturaltime }}</dd>
|
||||
<header class="block">
|
||||
<h1 class="title">
|
||||
{% block page_title %}
|
||||
{% if job.retry %}
|
||||
{% trans "Retry Status" %}
|
||||
{% else %}
|
||||
{% trans "Import Status" %}
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</h1>
|
||||
|
||||
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="{% url 'import' %}">{% trans "Imports" %}</a></li>
|
||||
{% url 'import-status' job.id as path %}
|
||||
<li{% if request.path in path %} class="is-active"{% endif %}>
|
||||
<a href="{{ path }}" {% if request.path in path %}aria-current="page"{% endif %}>
|
||||
{% if job.retry %}
|
||||
{% trans "Retry Status" %}
|
||||
{% else %}
|
||||
{% trans "Import Status" %}
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% block breadcrumbs %}{% endblock %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="block">
|
||||
<dl>
|
||||
<dt class="is-pulled-left mr-5">{% trans "Import started:" %}</dt>
|
||||
<dd>{{ job.created_date | naturaltime }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
{% if not job.complete %}
|
||||
<p>
|
||||
{% trans "Import still in progress." %}
|
||||
<br/>
|
||||
{% trans "(Hit reload to update!)" %}
|
||||
</p>
|
||||
<div class="box is-processing">
|
||||
<div class="block">
|
||||
<span class="icon icon-spinner is-pulled-left" aria-hidden="true"></span>
|
||||
<span>{% trans "In progress" %}</span>
|
||||
<span class="is-pulled-right">
|
||||
<a href="#" class="button is-small">{% trans "Refresh" %}</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<progress
|
||||
class="progress is-success is-medium mr-2"
|
||||
role="progressbar"
|
||||
aria-min="0"
|
||||
value="{{ complete_count }}"
|
||||
aria-valuenow="{{ complete_count }}"
|
||||
max="{{ item_count }}"
|
||||
aria-valuemax="{{ item_count }}">
|
||||
{{ percent }} %
|
||||
</progress>
|
||||
<span>{{ percent }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if manual_review_count and not legacy %}
|
||||
<div class="notification">
|
||||
{% blocktrans trimmed count counter=manual_review_count with display_counter=manual_review_count|intcomma %}
|
||||
{{ display_counter }} item needs manual approval.
|
||||
{% plural %}
|
||||
{{ display_counter }} items need manual approval.
|
||||
{% endblocktrans %}
|
||||
<a href="{% url 'import-review' job.id %}">{% trans "Review items" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if job.complete and fail_count and not job.retry and not legacy %}
|
||||
<div class="notification is-warning">
|
||||
{% blocktrans trimmed count counter=fail_count with display_counter=fail_count|intcomma %}
|
||||
{{ display_counter }} item failed to import.
|
||||
{% plural %}
|
||||
{{ display_counter }} items failed to import.
|
||||
{% endblocktrans %}
|
||||
<a href="{% url 'import-troubleshoot' job.id %}">
|
||||
{% trans "View and troubleshoot failed items" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
<div class="block">
|
||||
{% block actions %}{% endblock %}
|
||||
<div class="table-container">
|
||||
<table class="table is-striped">
|
||||
<tr>
|
||||
<th>
|
||||
{% trans "Row" %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Title" %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "ISBN" %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Author" %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Shelf" %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Review" %}
|
||||
</th>
|
||||
{% block import_cols_headers %}
|
||||
<th>
|
||||
{% trans "Book" %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Status" %}
|
||||
</th>
|
||||
{% endblock %}
|
||||
</tr>
|
||||
{% if legacy %}
|
||||
<tr>
|
||||
<td colspan="8">
|
||||
<p>
|
||||
<em>{% trans "Import preview unavailable." %}</em>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
{% block index_col %}
|
||||
<td>
|
||||
{{ item.index }}
|
||||
</td>
|
||||
{% endblock %}
|
||||
<td>
|
||||
{{ item.normalized_data.title }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.isbn|default:'' }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.normalized_data.authors }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.normalized_data.shelf }}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.rating %}
|
||||
<p>{% include 'snippets/stars.html' with rating=item.rating %}</p>
|
||||
{% endif %}
|
||||
{% if item.review %}
|
||||
<p>{{ item.review|truncatechars:100 }}</p>
|
||||
{% endif %}
|
||||
{% if item.linked_review %}
|
||||
<a href="{{ item.linked_review.remote_id }}" target="_blank">{% trans "View imported review" %}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% block import_cols %}
|
||||
<td>
|
||||
{% if item.book %}
|
||||
<a href="{{ item.book.local_path }}">
|
||||
{% include 'snippets/book_cover.html' with book=item.book cover_class='is-h-s' size='small' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.book %}
|
||||
<span class="icon icon-check" aria-hidden="true"></span>
|
||||
<span class="is-sr-only-mobile">{% trans "Imported" %}</span>
|
||||
|
||||
{% elif item.fail_reason %}
|
||||
<span class="icon icon-x has-text-danger" aria-hidden="true"></span>
|
||||
<span class="is-sr-only-mobile">
|
||||
{% if item.book_guess %}
|
||||
{% trans "Needs manual review" %}
|
||||
{% else %}
|
||||
{{ item.fail_reason }}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% else %}
|
||||
<div class="is-flex">
|
||||
<span class="icon icon-dots-three" aria-hidden="true"></span>
|
||||
<span class="is-sr-only-mobile">{% trans "Pending" %}</span>
|
||||
{# retry option if an item appears to be hanging #}
|
||||
{% if job.created_date != job.updated_date and inactive_time > 24 %}
|
||||
<form class="ml-2" method="POST" action="{% url 'import-item-retry' job.id item.id %}" name="retry-{{ item.id }}">
|
||||
{% csrf_token %}
|
||||
<button class="button is-danger is-outlined is-small">{% trans "Retry" %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endblock %}
|
||||
</tr>
|
||||
{% block action_row %}{% endblock %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
{% if legacy %}
|
||||
<div class="content">
|
||||
<form name="update-import" method="POST" action="{% url 'import-status' job.id %}">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
{% trans "This import is in an old format that is no longer supported. If you would like to troubleshoot missing items from this import, click the button below to update the import format." %}
|
||||
</p>
|
||||
<button class="button">{% trans "Update import" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if failed_items %}
|
||||
<div class="block">
|
||||
<h2 class="title is-4">{% trans "Failed to load" %}</h2>
|
||||
{% if not job.retry %}
|
||||
<form name="retry" action="/import/{{ job.id }}" method="post" class="box">
|
||||
{% csrf_token %}
|
||||
|
||||
{% with failed_count=failed_items|length %}
|
||||
{% if failed_count > 10 %}
|
||||
<p class="block">
|
||||
<a href="#select-all-failed-imports">
|
||||
{% blocktrans %}Jump to the bottom of the list to select the {{ failed_count }} items which failed to import.{% endblocktrans %}
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<fieldset id="failed_imports">
|
||||
<ul>
|
||||
{% for item in failed_items %}
|
||||
<li class="mb-2 is-flex is-align-items-start">
|
||||
<input class="checkbox mt-1" type="checkbox" name="import_item" value="{{ item.id }}" id="import_item_{{ item.id }}">
|
||||
<label class="ml-1" for="import-item-{{ item.id }}">
|
||||
{% blocktrans with index=item.index title=item.data.Title author=item.data.Author %}Line {{ index }}: <strong>{{ title }}</strong> by {{ author }}{% endblocktrans %}
|
||||
<br/>
|
||||
{{ item.fail_reason }}.
|
||||
</label>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="mt-3">
|
||||
<a name="select-all-failed-imports"></a>
|
||||
|
||||
<label class="label is-inline">
|
||||
<input
|
||||
id="toggle-all-checkboxes-failed-imports"
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
data-action="toggle-all"
|
||||
data-target="failed_imports"
|
||||
/>
|
||||
{% trans "Select all" %}
|
||||
</label>
|
||||
|
||||
<button class="button is-block mt-3" type="submit">{% trans "Retry items" %}</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
{% else %}
|
||||
<ul>
|
||||
{% for item in failed_items %}
|
||||
<li class="pb-1">
|
||||
<p>
|
||||
Line {{ item.index }}:
|
||||
<strong>{{ item.data.Title }}</strong> by
|
||||
{{ item.data.Author }}
|
||||
</p>
|
||||
<p>
|
||||
{{ item.fail_reason }}.
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if not legacy %}
|
||||
<div>
|
||||
{% include 'snippets/pagination.html' with page=items %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="block">
|
||||
{% if job.complete %}
|
||||
<h2 class="title is-4">{% trans "Successfully imported" %}</h2>
|
||||
{% else %}
|
||||
<h2 class="title is-4">{% trans "Import Progress" %}</h2>
|
||||
{% endif %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>
|
||||
{% trans "Book" %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Title" %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Author" %}
|
||||
</th>
|
||||
<th>
|
||||
</th>
|
||||
</tr>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if item.book %}
|
||||
<a href="{{ item.book.local_path }}">
|
||||
{% include 'snippets/book_cover.html' with book=item.book cover_class='is-h-s' size='small' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.data.Title }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.data.Author }}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.book %}
|
||||
<span class="icon icon-check">
|
||||
<span class="is-sr-only">{% trans "Imported" %}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endspaceless %}{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
|
|
73
bookwyrm/templates/import/manual_review.html
Normal file
73
bookwyrm/templates/import/manual_review.html
Normal file
|
@ -0,0 +1,73 @@
|
|||
{% extends 'import/import_status.html' %}
|
||||
{% load i18n %}
|
||||
{% load utilities %}
|
||||
|
||||
{% block title %}{% trans "Import Troubleshooting" %}{% endblock %}
|
||||
|
||||
{% block page_title %}
|
||||
{% trans "Review items" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<li class="is-active">
|
||||
<a href="#" aria-current="page">{% trans "Review" %}</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block actions %}
|
||||
<div class="block">
|
||||
<div class="notification content">
|
||||
<p>
|
||||
{% trans "Approving a suggestion will permanently add the suggested book to your shelves and associate your reading dates, reviews, and ratings with that book." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block import_cols_headers %}
|
||||
{% endblock %}
|
||||
|
||||
{% block index_col %}
|
||||
<td rowspan="2">
|
||||
{{ item.index }}
|
||||
</td>
|
||||
{% endblock %}
|
||||
|
||||
{% block import_cols %}
|
||||
{% endblock %}
|
||||
|
||||
{% block action_row %}
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<div class="columns is-mobile">
|
||||
{% with guess=item.book_guess %}
|
||||
<div class="column is-narrow">
|
||||
<a href="{{ item.book.local_path }}" target="_blank">
|
||||
{% include 'snippets/book_cover.html' with book=guess cover_class='is-h-s' size='small' %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="column">
|
||||
{% include 'snippets/book_titleby.html' with book=guess %}
|
||||
<div class="content is-flex">
|
||||
<form class="pr-2" name="approve-{{ item.id }}" method="POST" action="{% url 'import-approve' job.id item.id %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="button is-success">
|
||||
<span class="icon icon-check m-0-mobile" aria-hidden="true"></span>
|
||||
<span class="is-sr-only-mobile">{% trans "Approve" %}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form name="delete-{{ item.id }}" method="POST" action="{% url 'import-delete' job.id item.id %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="button is-danger is-light is-outlined">
|
||||
<span class="icon icon-x m-0-mobile" aria-hidden="true"></span>
|
||||
<span class="is-sr-only-mobile">{% trans "Reject" %}</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endblock %}
|
36
bookwyrm/templates/import/troubleshoot.html
Normal file
36
bookwyrm/templates/import/troubleshoot.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
{% extends 'import/import_status.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Import Troubleshooting" %}{% endblock %}
|
||||
|
||||
{% block page_title %}
|
||||
{% trans "Failed items" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<li class="is-active">
|
||||
<a href="#" aria-current="page">{% trans "Troubleshooting" %}</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block actions %}
|
||||
<div class="block">
|
||||
<div class="notification content">
|
||||
<p>
|
||||
{% trans "Re-trying an import can fix missing items in cases such as:" %}
|
||||
</p>
|
||||
<ul>
|
||||
<li>{% trans "The book has been added to the instance since this import" %}</li>
|
||||
<li>{% trans "A transient error or timeout caused the external data source to be unavailable." %}</li>
|
||||
<li>{% trans "BookWyrm has been updated since this import with a bug fix" %}</li>
|
||||
</ul>
|
||||
<p>
|
||||
{% trans "Contact your admin or <a href='https://github.com/bookwyrm-social/bookwyrm/issues'>open an issue</a> if you are seeing unexpected failed items." %}
|
||||
</p>
|
||||
</div>
|
||||
<form name="retry" method="post" action="{% url 'import-troubleshoot' job.id %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="button">Retry all</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -146,7 +146,7 @@ class BaseActivity(TestCase):
|
|||
self.user.avatar.file # pylint: disable=pointless-statement
|
||||
|
||||
# this would trigger a broadcast because it's a local user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
activity.to_model(model=models.User, instance=self.user)
|
||||
self.assertIsNotNone(self.user.avatar.file)
|
||||
self.assertEqual(self.user.name, "New Name")
|
||||
|
@ -154,7 +154,7 @@ class BaseActivity(TestCase):
|
|||
|
||||
def test_to_model_many_to_many(self, *_):
|
||||
"""annoying that these all need special handling"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
status = models.Status.objects.create(
|
||||
content="test status",
|
||||
user=self.user,
|
||||
|
@ -186,7 +186,7 @@ class BaseActivity(TestCase):
|
|||
def test_to_model_one_to_many(self, *_):
|
||||
"""these are reversed relationships, where the secondary object
|
||||
keys the primary object but not vice versa"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
status = models.Status.objects.create(
|
||||
content="test status",
|
||||
user=self.user,
|
||||
|
@ -224,7 +224,7 @@ class BaseActivity(TestCase):
|
|||
@responses.activate
|
||||
def test_set_related_field(self, *_):
|
||||
"""celery task to add back-references to created objects"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
status = models.Status.objects.create(
|
||||
content="test status",
|
||||
user=self.user,
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.test import TestCase
|
|||
from bookwyrm import activitystreams, models
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
@patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
|
||||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.test import TestCase
|
|||
from bookwyrm import activitystreams, models
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
@patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
|
||||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.test import TestCase
|
|||
from bookwyrm import activitystreams, models
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
@patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
|
||||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.test import TestCase
|
|||
from bookwyrm import activitystreams, models
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
@patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
|
||||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.test import TestCase
|
|||
from bookwyrm import activitystreams, models
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
class ActivitystreamsSignals(TestCase):
|
||||
"""using redis to build activity streams"""
|
||||
|
||||
|
@ -53,11 +53,12 @@ class ActivitystreamsSignals(TestCase):
|
|||
status = models.Status.objects.create(
|
||||
user=self.remote_user, content="hi", privacy="public"
|
||||
)
|
||||
with patch("bookwyrm.activitystreams.add_status_task.delay") as mock:
|
||||
with patch("bookwyrm.activitystreams.add_status_task.apply_async") as mock:
|
||||
activitystreams.add_status_on_create_command(models.Status, status, False)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
args = mock.call_args[0]
|
||||
self.assertEqual(args[0], status.id)
|
||||
args = mock.call_args[1]
|
||||
self.assertEqual(args["args"][0], status.id)
|
||||
self.assertEqual(args["queue"], "high_priority")
|
||||
|
||||
def test_populate_streams_on_account_create(self, _):
|
||||
"""create streams for a user"""
|
||||
|
|
|
@ -34,7 +34,7 @@ class Activitystreams(TestCase):
|
|||
)
|
||||
work = models.Work.objects.create(title="test work")
|
||||
self.book = models.Edition.objects.create(title="test book", parent_work=work)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
self.status = models.Status.objects.create(
|
||||
content="hi", user=self.local_user
|
||||
)
|
||||
|
@ -133,7 +133,7 @@ class Activitystreams(TestCase):
|
|||
|
||||
@patch("bookwyrm.activitystreams.LocalStream.remove_object_from_related_stores")
|
||||
@patch("bookwyrm.activitystreams.BooksStream.remove_object_from_related_stores")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
def test_boost_to_another_timeline(self, *_):
|
||||
"""boost from a non-follower doesn't remove original status from feed"""
|
||||
status = models.Status.objects.create(user=self.local_user, content="hi")
|
||||
|
@ -155,7 +155,7 @@ class Activitystreams(TestCase):
|
|||
|
||||
@patch("bookwyrm.activitystreams.LocalStream.remove_object_from_related_stores")
|
||||
@patch("bookwyrm.activitystreams.BooksStream.remove_object_from_related_stores")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
def test_boost_to_another_timeline_remote(self, *_):
|
||||
"""boost from a remote non-follower doesn't remove original status from feed"""
|
||||
status = models.Status.objects.create(user=self.local_user, content="hi")
|
||||
|
@ -177,7 +177,7 @@ class Activitystreams(TestCase):
|
|||
|
||||
@patch("bookwyrm.activitystreams.LocalStream.remove_object_from_related_stores")
|
||||
@patch("bookwyrm.activitystreams.BooksStream.remove_object_from_related_stores")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
def test_boost_to_following_timeline(self, *_):
|
||||
"""add a boost and deduplicate the boosted status on the timeline"""
|
||||
self.local_user.following.add(self.another_user)
|
||||
|
@ -199,7 +199,7 @@ class Activitystreams(TestCase):
|
|||
|
||||
@patch("bookwyrm.activitystreams.LocalStream.remove_object_from_related_stores")
|
||||
@patch("bookwyrm.activitystreams.BooksStream.remove_object_from_related_stores")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
def test_boost_to_same_timeline(self, *_):
|
||||
"""add a boost and deduplicate the boosted status on the timeline"""
|
||||
status = models.Status.objects.create(user=self.local_user, content="hi")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
id,title,author,ISBN,rating,shelf,review,added
|
||||
38,Gideon the Ninth (The Locked Tomb #1),Tamsyn Muir,"9781250313195",,read,,2021-11-10
|
||||
48,Harrow the Ninth (The Locked Tomb #2),Tamsyn Muir,,3,read,,2021-11-10
|
||||
id,title,author,ISBN13,rating,shelf,review,added,finished
|
||||
38,Gideon the Ninth,Tamsyn Muir,"9781250313195",,read,,2021-11-10,2021-11-11
|
||||
48,Harrow the Ninth,Tamsyn Muir,,3,read,,2021-11-10
|
||||
23,Subcutanean,Aaron A. Reed,,,read,,2021-11-10
|
||||
10,Patisserie at Home,Mélanie Dupuis,"9780062445315",2,read,"mixed feelings",2021-11-10
|
||||
10,Patisserie at Home,Mélanie Dupuis,"9780062445315",2,read,"mixed feelings",2021-11-10,2021-11-11
|
||||
|
|
Can't render this file because it has a wrong number of fields in line 3.
|
|
@ -1,5 +0,0 @@
|
|||
Book Id,Title,Author,Author l-f,Additional Authors,ISBN,ISBN13,My Rating,Average Rating,Publisher,Binding,Number of Pages,Year Published,Original Publication Year,Date Read,Date Added,Bookshelves,Bookshelves with positions,Exclusive Shelf,My Review,Spoiler,Private Notes,Read Count,Recommended For,Recommended By,Owned Copies,Original Purchase Date,Original Purchase Location,Condition,Condition Description,BCID
|
||||
42036538,Gideon the Ninth (The Locked Tomb #1),Tamsyn Muir,"Muir, Tamsyn",,"=""1250313198""","=""9781250313195""",0,4.20,Tor,Hardcover,448,2019,2019,2020/10/25,2020/10/21,,,read,,,,1,,,0,,,,,
|
||||
52691223,Subcutanean,Aaron A. Reed,"Reed, Aaron A.",,"=""""","=""""",0,4.45,,Paperback,232,2020,,2020/03/06,2020/03/05,,,read,,,,1,,,0,,,,,
|
||||
28694510,Patisserie at Home,Mélanie Dupuis,"Dupuis, Mélanie",Anne Cazor,"=""0062445316""","=""9780062445315""",2,4.60,Harper Design,Hardcover,288,2016,,,2019/07/08,,,read,,,,2,,,0,,,,,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Book Id,Title,Author,Author l-f,Additional Authors,ISBN,ISBN13,My Rating,Average Rating,Publisher,Binding,Number of Pages,Year Published,Original Publication Year,Date Read,Date Added,Bookshelves,Bookshelves with positions,Exclusive Shelf,My Review,Spoiler,Private Notes,Read Count,Recommended For,Recommended By,Owned Copies,Original Purchase Date,Original Purchase Location,Condition,Condition Description,BCID
|
||||
42036538,Gideon the Ninth (The Locked Tomb #1),Tamsyn Muir,"Muir, Tamsyn",,"=""1250313198""","=""9781250313195""",0,4.20,Tor,Hardcover,448,2019,2019,2020/10/25,2020/10/21,,,read,,,,1,,,0,,,,,
|
||||
42036538,Gideon the Ninth (The Locked Tomb #1),Tamsyn Muir,"Muir, Tamsyn",,"=""1250313198""","=""9781250313195""",3,4.20,Tor,Hardcover,448,2019,2019,2020/10/25,2020/10/21,,,read,,,,1,,,0,,,,,
|
||||
52691223,Subcutanean,Aaron A. Reed,"Reed, Aaron A.",,"=""""","=""""",0,4.45,,Paperback,232,2020,,2020/03/06,2020/03/05,,,read,,,,1,,,0,,,,,
|
||||
28694510,Patisserie at Home,Mélanie Dupuis,"Dupuis, Mélanie",Anne Cazor,"=""0062445316""","=""9780062445315""",2,4.60,Harper Design,Hardcover,288,2016,,,2019/07/08,,,read,"mixed feelings",,,2,,,0,,,,,
|
||||
|
|
|
|
@ -1,4 +1,4 @@
|
|||
Book Id Title Sort Character Primary Author Primary Author Role Secondary Author Secondary Author Roles Publication Date Review Rating Comment Private Comment Summary Media Physical Description Weight Height Thickness Length Dimensions Page Count LCCN Acquired Date Started Date Read Barcode BCID Tags Collections Languages Original Languages LC Classification ISBN ISBNs Subjects Dewey Decimal Dewey Wording Other Call Number Copies Source Entry Date From Where OCLC Work id Lending Patron Lending Status Lending Start Lending End
|
||||
5498194 Marelle 1 Cortázar, Julio Gallimard (1979), Poche 1979 chef d'oeuvre 4.5 Marelle by Julio Cortázar (1979) Broché 590 p.; 7.24 inches 1.28 pounds 7.24 inches 1.26 inches 4.96 inches 7.24 x 4.96 x 1.26 inches 590 [2007-04-16] [2007-05-08] roman, espagnol, expérimental, bohème, philosophie Your library French Spanish PQ7797 .C7145 [2070291340] 2070291340, 9782070291342 Cortâazar, Julio. Rayuela 863 Literature > Spanish And Portuguese > Spanish fiction 1 Amazon.fr [2006-08-09] 57814
|
||||
5498194 Marelle 1 Cortazar, Julio Gallimard (1979), Poche 1979 chef d'oeuvre 4.5 Marelle by Julio Cortázar (1979) Broché 590 p.; 7.24 inches 1.28 pounds 7.24 inches 1.26 inches 4.96 inches 7.24 x 4.96 x 1.26 inches 590 [2007-04-16] [2007-05-08] roman, espagnol, expérimental, bohème, philosophie Your library French Spanish PQ7797 .C7145 [2070291340] 2070291340, 9782070291342 Cortâazar, Julio. Rayuela 863 Literature > Spanish And Portuguese > Spanish fiction 1 Amazon.fr [2006-08-09] 57814
|
||||
5015319 Le grand incendie de Londres: Récit, avec incises et bifurcations, 1985-1987 (Fiction & Cie) 1 Roubaud, Jacques Seuil (1989), Unknown Binding 1989 5 Le grand incendie de Londres: Récit, avec incises et bifurcations, 1985-1987 (Fiction & Cie) by Jacques Roubaud (1989) Broché 411 p.; 7.72 inches 0.88 pounds 7.72 inches 1.02 inches 5.43 inches 7.72 x 5.43 x 1.02 inches 411 Your library English PQ2678 .O77 [2020104725] 2020104725, 9782020104722 Autobiographical fiction|Roubaud, Jacques > Fiction 813 American And Canadian > Fiction > Literature 1 Amazon.com [2006-07-25] 478910
|
||||
5015399 Le Maître et Marguerite 1 Boulgakov, Mikhaïl Pocket (1994), Poche 1994 Le Maître et Marguerite by Mikhaïl Boulgakov (1994) Broché 579 p.; 7.09 inches 0.66 pounds 7.09 inches 1.18 inches 4.33 inches 7.09 x 4.33 x 1.18 inches 579 Your library French PG3476 .B78 [2266062328] 2266062328, 9782266062329 Allegories|Bulgakov|Good and evil > Fiction|Humanities|Jerusalem > Fiction|Jesus Christ > Fiction|Literature|Mental illness > Fiction|Moscow (Russia) > Fiction|Novel|Pilate, Pontius, 1st cent. > Fiction|Political fiction|Russia > Fiction|Russian fiction|Russian publications (Form Entry)|Soviet Union > History > 1925-1953 > Fiction|literature 891.7342 1917-1945 > 1917-1991 (USSR) > Literature > Literature of other Indo-European languages > Other Languages > Russian > Russian Fiction 1 Amazon.fr [2006-07-25] 10151
|
||||
|
|
|
|
@ -1,5 +1,4 @@
|
|||
""" testing import """
|
||||
import csv
|
||||
import pathlib
|
||||
from unittest.mock import patch
|
||||
import datetime
|
||||
|
@ -32,7 +31,7 @@ class GoodreadsImport(TestCase):
|
|||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
):
|
||||
self.user = models.User.objects.create_user(
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "password", local=True
|
||||
)
|
||||
|
||||
|
@ -45,12 +44,17 @@ class GoodreadsImport(TestCase):
|
|||
|
||||
def test_create_job(self, *_):
|
||||
"""creates the import job entry and checks csv"""
|
||||
import_job = self.importer.create_job(self.user, self.csv, False, "public")
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "public"
|
||||
)
|
||||
|
||||
import_items = models.ImportItem.objects.filter(job=import_job).all()
|
||||
self.assertEqual(len(import_items), 3)
|
||||
self.assertEqual(import_items[0].index, 0)
|
||||
self.assertEqual(import_items[0].data["Book Id"], "42036538")
|
||||
self.assertEqual(import_items[0].normalized_data["isbn_13"], '="9781250313195"')
|
||||
self.assertEqual(import_items[0].normalized_data["isbn_10"], '="1250313198"')
|
||||
|
||||
self.assertEqual(import_items[1].index, 1)
|
||||
self.assertEqual(import_items[1].data["Book Id"], "52691223")
|
||||
self.assertEqual(import_items[2].index, 2)
|
||||
|
@ -58,12 +62,16 @@ class GoodreadsImport(TestCase):
|
|||
|
||||
def test_create_retry_job(self, *_):
|
||||
"""trying again with items that didn't import"""
|
||||
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "unlisted"
|
||||
)
|
||||
import_items = models.ImportItem.objects.filter(job=import_job).all()[:2]
|
||||
|
||||
retry = self.importer.create_retry_job(self.user, import_job, import_items)
|
||||
retry = self.importer.create_retry_job(
|
||||
self.local_user, import_job, import_items
|
||||
)
|
||||
self.assertNotEqual(import_job, retry)
|
||||
self.assertEqual(retry.user, self.user)
|
||||
self.assertEqual(retry.user, self.local_user)
|
||||
self.assertEqual(retry.include_reviews, False)
|
||||
self.assertEqual(retry.privacy, "unlisted")
|
||||
|
||||
|
@ -76,23 +84,18 @@ class GoodreadsImport(TestCase):
|
|||
|
||||
def test_handle_imported_book(self, *_):
|
||||
"""goodreads import added a book, this adds related connections"""
|
||||
shelf = self.user.shelf_set.filter(identifier="read").first()
|
||||
shelf = self.local_user.shelf_set.filter(identifier="read").first()
|
||||
self.assertIsNone(shelf.books.first())
|
||||
|
||||
import_job = models.ImportJob.objects.create(user=self.user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")
|
||||
csv_file = open(datafile, "r") # pylint: disable=unspecified-encoding
|
||||
for index, entry in enumerate(list(csv.DictReader(csv_file))):
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=index, data=entry, book=self.book
|
||||
)
|
||||
break
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "public"
|
||||
)
|
||||
import_item = import_job.items.first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, False, "public"
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
|
||||
shelf.refresh_from_db()
|
||||
self.assertEqual(shelf.books.first(), self.book)
|
||||
|
@ -100,7 +103,7 @@ class GoodreadsImport(TestCase):
|
|||
shelf.shelfbook_set.first().shelved_date, make_date(2020, 10, 21)
|
||||
)
|
||||
|
||||
readthrough = models.ReadThrough.objects.get(user=self.user)
|
||||
readthrough = models.ReadThrough.objects.get(user=self.local_user)
|
||||
self.assertEqual(readthrough.book, self.book)
|
||||
self.assertEqual(readthrough.start_date, make_date(2020, 10, 21))
|
||||
self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25))
|
||||
|
@ -108,20 +111,17 @@ class GoodreadsImport(TestCase):
|
|||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
def test_handle_imported_book_review(self, *_):
|
||||
"""goodreads review import"""
|
||||
import_job = models.ImportJob.objects.create(user=self.user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")
|
||||
csv_file = open(datafile, "r") # pylint: disable=unspecified-encoding
|
||||
entry = list(csv.DictReader(csv_file))[2]
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=0, data=entry, book=self.book
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, True, "unlisted"
|
||||
)
|
||||
import_item = import_job.items.get(index=2)
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, True, "unlisted"
|
||||
)
|
||||
review = models.Review.objects.get(book=self.book, user=self.user)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
|
||||
review = models.Review.objects.get(book=self.book, user=self.local_user)
|
||||
self.assertEqual(review.content, "mixed feelings")
|
||||
self.assertEqual(review.rating, 2)
|
||||
self.assertEqual(review.published_date, make_date(2019, 7, 8))
|
||||
|
@ -130,23 +130,18 @@ class GoodreadsImport(TestCase):
|
|||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
def test_handle_imported_book_rating(self, *_):
|
||||
"""goodreads rating import"""
|
||||
import_job = models.ImportJob.objects.create(user=self.user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath(
|
||||
"../data/goodreads-rating.csv"
|
||||
)
|
||||
csv_file = open(datafile, "r") # pylint: disable=unspecified-encoding
|
||||
entry = list(csv.DictReader(csv_file))[2]
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=0, data=entry, book=self.book
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, True, "unlisted"
|
||||
)
|
||||
import_item = import_job.items.filter(index=0).first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, True, "unlisted"
|
||||
)
|
||||
review = models.ReviewRating.objects.get(book=self.book, user=self.user)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
|
||||
review = models.ReviewRating.objects.get(book=self.book, user=self.local_user)
|
||||
self.assertIsInstance(review, models.ReviewRating)
|
||||
self.assertEqual(review.rating, 2)
|
||||
self.assertEqual(review.published_date, make_date(2019, 7, 8))
|
||||
self.assertEqual(review.rating, 3)
|
||||
self.assertEqual(review.published_date, make_date(2020, 10, 25))
|
||||
self.assertEqual(review.privacy, "unlisted")
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
""" testing import """
|
||||
from collections import namedtuple
|
||||
import csv
|
||||
import pathlib
|
||||
from unittest.mock import patch
|
||||
import datetime
|
||||
|
@ -11,7 +9,8 @@ import responses
|
|||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.importers import Importer
|
||||
from bookwyrm.importers.importer import import_data, handle_imported_book
|
||||
from bookwyrm.importers.importer import start_import_task, import_item_task
|
||||
from bookwyrm.importers.importer import handle_imported_book
|
||||
|
||||
|
||||
def make_date(*args):
|
||||
|
@ -29,26 +28,7 @@ class GenericImporter(TestCase):
|
|||
def setUp(self):
|
||||
"""use a test csv"""
|
||||
|
||||
class TestImporter(Importer):
|
||||
"""basic importer"""
|
||||
|
||||
mandatory_fields = ["title", "author"]
|
||||
|
||||
def parse_fields(self, entry):
|
||||
return {
|
||||
"id": entry["id"],
|
||||
"Title": entry["title"],
|
||||
"Author": entry["author"],
|
||||
"ISBN13": entry["ISBN"],
|
||||
"Star Rating": entry["rating"],
|
||||
"My Rating": entry["rating"],
|
||||
"My Review": entry["review"],
|
||||
"Exclusive Shelf": entry["shelf"],
|
||||
"Date Added": entry["added"],
|
||||
"Date Read": None,
|
||||
}
|
||||
|
||||
self.importer = TestImporter()
|
||||
self.importer = Importer()
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/generic.csv")
|
||||
self.csv = open(datafile, "r", encoding=self.importer.encoding)
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
|
@ -77,13 +57,24 @@ class GenericImporter(TestCase):
|
|||
import_items = models.ImportItem.objects.filter(job=import_job).all()
|
||||
self.assertEqual(len(import_items), 4)
|
||||
self.assertEqual(import_items[0].index, 0)
|
||||
self.assertEqual(import_items[0].data["id"], "38")
|
||||
self.assertEqual(import_items[0].normalized_data["id"], "38")
|
||||
self.assertEqual(import_items[0].normalized_data["title"], "Gideon the Ninth")
|
||||
self.assertEqual(import_items[0].normalized_data["authors"], "Tamsyn Muir")
|
||||
self.assertEqual(import_items[0].normalized_data["isbn_13"], "9781250313195")
|
||||
self.assertIsNone(import_items[0].normalized_data["isbn_10"])
|
||||
self.assertEqual(import_items[0].normalized_data["shelf"], "read")
|
||||
|
||||
self.assertEqual(import_items[1].index, 1)
|
||||
self.assertEqual(import_items[1].data["id"], "48")
|
||||
self.assertEqual(import_items[1].normalized_data["id"], "48")
|
||||
self.assertEqual(import_items[1].normalized_data["title"], "Harrow the Ninth")
|
||||
|
||||
self.assertEqual(import_items[2].index, 2)
|
||||
self.assertEqual(import_items[2].data["id"], "23")
|
||||
self.assertEqual(import_items[2].normalized_data["id"], "23")
|
||||
self.assertEqual(import_items[2].normalized_data["title"], "Subcutanean")
|
||||
|
||||
self.assertEqual(import_items[3].index, 3)
|
||||
self.assertEqual(import_items[3].data["id"], "10")
|
||||
self.assertEqual(import_items[3].normalized_data["id"], "10")
|
||||
self.assertEqual(import_items[3].normalized_data["title"], "Patisserie at Home")
|
||||
|
||||
def test_create_retry_job(self, *_):
|
||||
"""trying again with items that didn't import"""
|
||||
|
@ -103,67 +94,105 @@ class GenericImporter(TestCase):
|
|||
retry_items = models.ImportItem.objects.filter(job=retry).all()
|
||||
self.assertEqual(len(retry_items), 2)
|
||||
self.assertEqual(retry_items[0].index, 0)
|
||||
self.assertEqual(retry_items[0].data["id"], "38")
|
||||
self.assertEqual(retry_items[0].normalized_data["id"], "38")
|
||||
self.assertEqual(retry_items[1].index, 1)
|
||||
self.assertEqual(retry_items[1].data["id"], "48")
|
||||
self.assertEqual(retry_items[1].normalized_data["id"], "48")
|
||||
|
||||
def test_start_import(self, *_):
|
||||
"""check that a task was created"""
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "unlisted"
|
||||
)
|
||||
MockTask = namedtuple("Task", ("id"))
|
||||
mock_task = MockTask(7)
|
||||
with patch("bookwyrm.importers.importer.import_data.delay") as start:
|
||||
start.return_value = mock_task
|
||||
with patch("bookwyrm.importers.importer.start_import_task.delay") as mock:
|
||||
self.importer.start_import(import_job)
|
||||
import_job.refresh_from_db()
|
||||
self.assertEqual(import_job.task_id, "7")
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
|
||||
@responses.activate
|
||||
def test_import_data(self, *_):
|
||||
def test_start_import_task(self, *_):
|
||||
"""resolve entry"""
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "unlisted"
|
||||
)
|
||||
book = models.Edition.objects.create(title="Test Book")
|
||||
|
||||
with patch("bookwyrm.importers.importer.import_item_task.delay") as mock:
|
||||
start_import_task(import_job.id)
|
||||
|
||||
self.assertEqual(mock.call_count, 4)
|
||||
|
||||
@responses.activate
|
||||
def test_import_item_task(self, *_):
|
||||
"""resolve entry"""
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "unlisted"
|
||||
)
|
||||
|
||||
import_item = models.ImportItem.objects.get(job=import_job, index=0)
|
||||
with patch(
|
||||
"bookwyrm.models.import_job.ImportItem.get_book_from_isbn"
|
||||
) as resolve:
|
||||
resolve.return_value = book
|
||||
with patch("bookwyrm.importers.importer.handle_imported_book"):
|
||||
import_data(self.importer.service, import_job.id)
|
||||
resolve.return_value = self.book
|
||||
|
||||
import_item = models.ImportItem.objects.get(job=import_job, index=0)
|
||||
self.assertEqual(import_item.book.id, book.id)
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
import_item_task(import_item.id)
|
||||
kwargs = mock.call_args.kwargs
|
||||
self.assertEqual(kwargs["queue"], "low_priority")
|
||||
import_item.refresh_from_db()
|
||||
|
||||
def test_complete_job(self, *_):
|
||||
"""test notification"""
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "unlisted"
|
||||
)
|
||||
items = import_job.items.all()
|
||||
for item in items[:3]:
|
||||
item.fail_reason = "hello"
|
||||
item.save()
|
||||
item.update_job()
|
||||
self.assertFalse(
|
||||
models.Notification.objects.filter(
|
||||
user=self.local_user,
|
||||
related_import=import_job,
|
||||
notification_type="IMPORT",
|
||||
).exists()
|
||||
)
|
||||
|
||||
item = items[3]
|
||||
item.fail_reason = "hello"
|
||||
item.save()
|
||||
item.update_job()
|
||||
import_job.refresh_from_db()
|
||||
self.assertTrue(import_job.complete)
|
||||
self.assertTrue(
|
||||
models.Notification.objects.filter(
|
||||
user=self.local_user,
|
||||
related_import=import_job,
|
||||
notification_type="IMPORT",
|
||||
).exists()
|
||||
)
|
||||
|
||||
def test_handle_imported_book(self, *_):
|
||||
"""import added a book, this adds related connections"""
|
||||
shelf = self.local_user.shelf_set.filter(identifier="read").first()
|
||||
self.assertIsNone(shelf.books.first())
|
||||
|
||||
import_job = models.ImportJob.objects.create(user=self.local_user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/generic.csv")
|
||||
csv_file = open(datafile, "r") # pylint: disable=unspecified-encoding
|
||||
for index, entry in enumerate(list(csv.DictReader(csv_file))):
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=index, data=entry, book=self.book
|
||||
)
|
||||
break
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "public"
|
||||
)
|
||||
import_item = import_job.items.first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.local_user, import_item, False, "public"
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
|
||||
shelf.refresh_from_db()
|
||||
self.assertEqual(shelf.books.first(), self.book)
|
||||
|
||||
def test_handle_imported_book_already_shelved(self, *_):
|
||||
"""import added a book, this adds related connections"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
shelf = self.local_user.shelf_set.filter(identifier="to-read").first()
|
||||
models.ShelfBook.objects.create(
|
||||
shelf=shelf,
|
||||
|
@ -172,20 +201,15 @@ class GenericImporter(TestCase):
|
|||
shelved_date=make_date(2020, 2, 2),
|
||||
)
|
||||
|
||||
import_job = models.ImportJob.objects.create(user=self.local_user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/generic.csv")
|
||||
csv_file = open(datafile, "r") # pylint: disable=unspecified-encoding
|
||||
for index, entry in enumerate(list(csv.DictReader(csv_file))):
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=index, data=entry, book=self.book
|
||||
)
|
||||
break
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "public"
|
||||
)
|
||||
import_item = import_job.items.first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.local_user, import_item, False, "public"
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
|
||||
shelf.refresh_from_db()
|
||||
self.assertEqual(shelf.books.first(), self.book)
|
||||
|
@ -199,48 +223,34 @@ class GenericImporter(TestCase):
|
|||
def test_handle_import_twice(self, *_):
|
||||
"""re-importing books"""
|
||||
shelf = self.local_user.shelf_set.filter(identifier="read").first()
|
||||
import_job = models.ImportJob.objects.create(user=self.local_user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/generic.csv")
|
||||
csv_file = open(datafile, "r") # pylint: disable=unspecified-encoding
|
||||
for index, entry in enumerate(list(csv.DictReader(csv_file))):
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=index, data=entry, book=self.book
|
||||
)
|
||||
break
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "public"
|
||||
)
|
||||
import_item = import_job.items.first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.local_user, import_item, False, "public"
|
||||
)
|
||||
handle_imported_book(
|
||||
self.importer.service, self.local_user, import_item, False, "public"
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
handle_imported_book(import_item)
|
||||
|
||||
shelf.refresh_from_db()
|
||||
self.assertEqual(shelf.books.first(), self.book)
|
||||
self.assertEqual(models.ReadThrough.objects.count(), 1)
|
||||
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
def test_handle_imported_book_review(self, *_):
|
||||
"""review import"""
|
||||
import_job = models.ImportJob.objects.create(user=self.local_user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/generic.csv")
|
||||
csv_file = open(datafile, "r") # pylint: disable=unspecified-encoding
|
||||
entry = list(csv.DictReader(csv_file))[3]
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=0, data=entry, book=self.book
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, True, "unlisted"
|
||||
)
|
||||
import_item = import_job.items.filter(index=3).first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
with patch("bookwyrm.models.Status.broadcast") as broadcast_mock:
|
||||
handle_imported_book(
|
||||
self.importer.service,
|
||||
self.local_user,
|
||||
import_item,
|
||||
True,
|
||||
"unlisted",
|
||||
)
|
||||
handle_imported_book(import_item)
|
||||
kwargs = broadcast_mock.call_args.kwargs
|
||||
self.assertEqual(kwargs["software"], "bookwyrm")
|
||||
review = models.Review.objects.get(book=self.book, user=self.local_user)
|
||||
|
@ -248,42 +258,89 @@ class GenericImporter(TestCase):
|
|||
self.assertEqual(review.rating, 2.0)
|
||||
self.assertEqual(review.privacy, "unlisted")
|
||||
|
||||
import_item.refresh_from_db()
|
||||
self.assertEqual(import_item.linked_review, review)
|
||||
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
def test_handle_imported_book_rating(self, *_):
|
||||
"""rating import"""
|
||||
import_job = models.ImportJob.objects.create(user=self.local_user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/generic.csv")
|
||||
csv_file = open(datafile, "r") # pylint: disable=unspecified-encoding
|
||||
entry = list(csv.DictReader(csv_file))[1]
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=0, data=entry, book=self.book
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, True, "unlisted"
|
||||
)
|
||||
import_item = import_job.items.filter(index=1).first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.local_user, import_item, True, "unlisted"
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
review = models.ReviewRating.objects.get(book=self.book, user=self.local_user)
|
||||
self.assertIsInstance(review, models.ReviewRating)
|
||||
self.assertEqual(review.rating, 3.0)
|
||||
self.assertEqual(review.privacy, "unlisted")
|
||||
|
||||
import_item.refresh_from_db()
|
||||
self.assertEqual(import_item.linked_review.id, review.id)
|
||||
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
def test_handle_imported_book_rating_duplicate_with_link(self, *_):
|
||||
"""rating import twice"""
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, True, "unlisted"
|
||||
)
|
||||
import_item = import_job.items.filter(index=1).first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
handle_imported_book(import_item)
|
||||
|
||||
review = models.ReviewRating.objects.get(book=self.book, user=self.local_user)
|
||||
self.assertIsInstance(review, models.ReviewRating)
|
||||
self.assertEqual(review.rating, 3.0)
|
||||
self.assertEqual(review.privacy, "unlisted")
|
||||
|
||||
import_item.refresh_from_db()
|
||||
self.assertEqual(import_item.linked_review.id, review.id)
|
||||
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
def test_handle_imported_book_rating_duplicate_without_link(self, *_):
|
||||
"""rating import twice"""
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, True, "unlisted"
|
||||
)
|
||||
import_item = import_job.items.filter(index=1).first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
import_item.refresh_from_db()
|
||||
import_item.linked_review = None
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
|
||||
review = models.ReviewRating.objects.get(book=self.book, user=self.local_user)
|
||||
self.assertIsInstance(review, models.ReviewRating)
|
||||
self.assertEqual(review.rating, 3.0)
|
||||
self.assertEqual(review.privacy, "unlisted")
|
||||
|
||||
import_item.refresh_from_db()
|
||||
self.assertEqual(import_item.linked_review.id, review.id)
|
||||
|
||||
def test_handle_imported_book_reviews_disabled(self, *_):
|
||||
"""review import"""
|
||||
import_job = models.ImportJob.objects.create(user=self.local_user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/generic.csv")
|
||||
csv_file = open(datafile, "r") # pylint: disable=unspecified-encoding
|
||||
entry = list(csv.DictReader(csv_file))[2]
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=0, data=entry, book=self.book
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "unlisted"
|
||||
)
|
||||
import_item = import_job.items.filter(index=3).first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.local_user, import_item, False, "unlisted"
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
self.assertFalse(
|
||||
models.Review.objects.filter(book=self.book, user=self.local_user).exists()
|
||||
)
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
""" testing import """
|
||||
import csv
|
||||
import pathlib
|
||||
from unittest.mock import patch
|
||||
import datetime
|
||||
import pytz
|
||||
|
||||
from django.test import TestCase
|
||||
import responses
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.importers import LibrarythingImporter
|
||||
from bookwyrm.importers.importer import import_data, handle_imported_book
|
||||
from bookwyrm.importers.importer import handle_imported_book
|
||||
|
||||
|
||||
def make_date(*args):
|
||||
|
@ -35,7 +33,7 @@ class LibrarythingImport(TestCase):
|
|||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
):
|
||||
self.user = models.User.objects.create_user(
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mmai", "mmai@mmai.mmai", "password", local=True
|
||||
)
|
||||
work = models.Work.objects.create(title="Test Work")
|
||||
|
@ -47,8 +45,10 @@ class LibrarythingImport(TestCase):
|
|||
|
||||
def test_create_job(self, *_):
|
||||
"""creates the import job entry and checks csv"""
|
||||
import_job = self.importer.create_job(self.user, self.csv, False, "public")
|
||||
self.assertEqual(import_job.user, self.user)
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "public"
|
||||
)
|
||||
self.assertEqual(import_job.user, self.local_user)
|
||||
self.assertEqual(import_job.include_reviews, False)
|
||||
self.assertEqual(import_job.privacy, "public")
|
||||
|
||||
|
@ -56,6 +56,14 @@ class LibrarythingImport(TestCase):
|
|||
self.assertEqual(len(import_items), 3)
|
||||
self.assertEqual(import_items[0].index, 0)
|
||||
self.assertEqual(import_items[0].data["Book Id"], "5498194")
|
||||
self.assertEqual(import_items[0].normalized_data["isbn_13"], "9782070291342")
|
||||
self.assertEqual(import_items[0].normalized_data["isbn_10"], "2070291340")
|
||||
self.assertEqual(import_items[0].normalized_data["title"], "Marelle")
|
||||
self.assertEqual(import_items[0].normalized_data["authors"], "Cortazar, Julio")
|
||||
self.assertEqual(import_items[0].normalized_data["date_added"], "2006-08-09")
|
||||
self.assertEqual(import_items[0].normalized_data["date_started"], "2007-04-16")
|
||||
self.assertEqual(import_items[0].normalized_data["date_finished"], "2007-05-08")
|
||||
|
||||
self.assertEqual(import_items[1].index, 1)
|
||||
self.assertEqual(import_items[1].data["Book Id"], "5015319")
|
||||
self.assertEqual(import_items[2].index, 2)
|
||||
|
@ -63,12 +71,16 @@ class LibrarythingImport(TestCase):
|
|||
|
||||
def test_create_retry_job(self, *_):
|
||||
"""trying again with items that didn't import"""
|
||||
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "unlisted"
|
||||
)
|
||||
import_items = models.ImportItem.objects.filter(job=import_job).all()[:2]
|
||||
|
||||
retry = self.importer.create_retry_job(self.user, import_job, import_items)
|
||||
retry = self.importer.create_retry_job(
|
||||
self.local_user, import_job, import_items
|
||||
)
|
||||
self.assertNotEqual(import_job, retry)
|
||||
self.assertEqual(retry.user, self.user)
|
||||
self.assertEqual(retry.user, self.local_user)
|
||||
self.assertEqual(retry.include_reviews, False)
|
||||
self.assertEqual(retry.privacy, "unlisted")
|
||||
|
||||
|
@ -79,80 +91,54 @@ class LibrarythingImport(TestCase):
|
|||
self.assertEqual(retry_items[1].index, 1)
|
||||
self.assertEqual(retry_items[1].data["Book Id"], "5015319")
|
||||
|
||||
@responses.activate
|
||||
def test_import_data(self, *_):
|
||||
"""resolve entry"""
|
||||
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
|
||||
book = models.Edition.objects.create(title="Test Book")
|
||||
|
||||
with patch(
|
||||
"bookwyrm.models.import_job.ImportItem.get_book_from_isbn"
|
||||
) as resolve:
|
||||
resolve.return_value = book
|
||||
with patch("bookwyrm.importers.importer.handle_imported_book"):
|
||||
import_data(self.importer.service, import_job.id)
|
||||
|
||||
import_item = models.ImportItem.objects.get(job=import_job, index=0)
|
||||
self.assertEqual(import_item.book.id, book.id)
|
||||
|
||||
def test_handle_imported_book(self, *_):
|
||||
"""librarything import added a book, this adds related connections"""
|
||||
shelf = self.user.shelf_set.filter(identifier="read").first()
|
||||
shelf = self.local_user.shelf_set.filter(identifier="read").first()
|
||||
self.assertIsNone(shelf.books.first())
|
||||
|
||||
import_job = models.ImportJob.objects.create(user=self.user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv")
|
||||
csv_file = open(datafile, "r", encoding=self.importer.encoding)
|
||||
for index, entry in enumerate(
|
||||
list(csv.DictReader(csv_file, delimiter=self.importer.delimiter))
|
||||
):
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=index, data=entry, book=self.book
|
||||
)
|
||||
break
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "public"
|
||||
)
|
||||
import_item = import_job.items.first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, False, "public"
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
|
||||
shelf.refresh_from_db()
|
||||
self.assertEqual(shelf.books.first(), self.book)
|
||||
|
||||
readthrough = models.ReadThrough.objects.get(user=self.user)
|
||||
readthrough = models.ReadThrough.objects.get(user=self.local_user)
|
||||
self.assertEqual(readthrough.book, self.book)
|
||||
self.assertEqual(readthrough.start_date, make_date(2007, 4, 16))
|
||||
self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8))
|
||||
|
||||
def test_handle_imported_book_already_shelved(self, *_):
|
||||
"""librarything import added a book, this adds related connections"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
shelf = self.user.shelf_set.filter(identifier="to-read").first()
|
||||
models.ShelfBook.objects.create(shelf=shelf, user=self.user, book=self.book)
|
||||
|
||||
import_job = models.ImportJob.objects.create(user=self.user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv")
|
||||
csv_file = open(datafile, "r", encoding=self.importer.encoding)
|
||||
for index, entry in enumerate(
|
||||
list(csv.DictReader(csv_file, delimiter=self.importer.delimiter))
|
||||
):
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=index, data=entry, book=self.book
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
shelf = self.local_user.shelf_set.filter(identifier="to-read").first()
|
||||
models.ShelfBook.objects.create(
|
||||
shelf=shelf, user=self.local_user, book=self.book
|
||||
)
|
||||
break
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, False, "public"
|
||||
)
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "public"
|
||||
)
|
||||
import_item = import_job.items.first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
|
||||
shelf.refresh_from_db()
|
||||
self.assertEqual(shelf.books.first(), self.book)
|
||||
self.assertIsNone(self.user.shelf_set.get(identifier="read").books.first())
|
||||
self.assertIsNone(
|
||||
self.local_user.shelf_set.get(identifier="read").books.first()
|
||||
)
|
||||
|
||||
readthrough = models.ReadThrough.objects.get(user=self.user)
|
||||
readthrough = models.ReadThrough.objects.get(user=self.local_user)
|
||||
self.assertEqual(readthrough.book, self.book)
|
||||
self.assertEqual(readthrough.start_date, make_date(2007, 4, 16))
|
||||
self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8))
|
||||
|
@ -160,21 +146,18 @@ class LibrarythingImport(TestCase):
|
|||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
def test_handle_imported_book_review(self, *_):
|
||||
"""librarything review import"""
|
||||
import_job = models.ImportJob.objects.create(user=self.user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv")
|
||||
csv_file = open(datafile, "r", encoding=self.importer.encoding)
|
||||
entry = list(csv.DictReader(csv_file, delimiter=self.importer.delimiter))[0]
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=0, data=entry, book=self.book
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, True, "unlisted"
|
||||
)
|
||||
import_item = import_job.items.filter(index=0).first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, True, "unlisted"
|
||||
)
|
||||
review = models.Review.objects.get(book=self.book, user=self.user)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
|
||||
review = models.Review.objects.get(book=self.book, user=self.local_user)
|
||||
self.assertEqual(review.content, "chef d'oeuvre")
|
||||
self.assertEqual(review.rating, 5)
|
||||
self.assertEqual(review.rating, 4.5)
|
||||
self.assertEqual(review.published_date, make_date(2007, 5, 8))
|
||||
self.assertEqual(review.privacy, "unlisted")
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
""" testing import """
|
||||
import csv
|
||||
import pathlib
|
||||
from unittest.mock import patch
|
||||
import datetime
|
||||
|
@ -32,7 +31,7 @@ class StorygraphImport(TestCase):
|
|||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
):
|
||||
self.user = models.User.objects.create_user(
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "password", local=True
|
||||
)
|
||||
|
||||
|
@ -45,53 +44,34 @@ class StorygraphImport(TestCase):
|
|||
|
||||
def test_create_job(self, *_):
|
||||
"""creates the import job entry and checks csv"""
|
||||
import_job = self.importer.create_job(self.user, self.csv, False, "public")
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "public"
|
||||
)
|
||||
|
||||
import_items = models.ImportItem.objects.filter(job=import_job).all()
|
||||
self.assertEqual(len(import_items), 2)
|
||||
self.assertEqual(import_items[0].index, 0)
|
||||
self.assertEqual(import_items[0].data["Title"], "Always Coming Home")
|
||||
self.assertEqual(import_items[0].normalized_data["title"], "Always Coming Home")
|
||||
self.assertEqual(import_items[1].index, 1)
|
||||
self.assertEqual(import_items[1].data["Title"], "Subprime Attention Crisis")
|
||||
self.assertEqual(import_items[1].data["My Rating"], 5.0)
|
||||
|
||||
def test_create_retry_job(self, *_):
|
||||
"""trying again with items that didn't import"""
|
||||
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
|
||||
import_items = models.ImportItem.objects.filter(job=import_job).all()[:2]
|
||||
|
||||
retry = self.importer.create_retry_job(self.user, import_job, import_items)
|
||||
self.assertNotEqual(import_job, retry)
|
||||
self.assertEqual(retry.user, self.user)
|
||||
self.assertEqual(retry.include_reviews, False)
|
||||
self.assertEqual(retry.privacy, "unlisted")
|
||||
|
||||
retry_items = models.ImportItem.objects.filter(job=retry).all()
|
||||
self.assertEqual(len(retry_items), 2)
|
||||
self.assertEqual(retry_items[0].index, 0)
|
||||
self.assertEqual(retry_items[0].data["Title"], "Always Coming Home")
|
||||
self.assertEqual(retry_items[1].index, 1)
|
||||
self.assertEqual(retry_items[1].data["Title"], "Subprime Attention Crisis")
|
||||
self.assertEqual(
|
||||
import_items[1].normalized_data["title"], "Subprime Attention Crisis"
|
||||
)
|
||||
self.assertEqual(import_items[1].normalized_data["rating"], "5.0")
|
||||
|
||||
def test_handle_imported_book(self, *_):
|
||||
"""storygraph import added a book, this adds related connections"""
|
||||
shelf = self.user.shelf_set.filter(identifier="to-read").first()
|
||||
shelf = self.local_user.shelf_set.filter(identifier="to-read").first()
|
||||
self.assertIsNone(shelf.books.first())
|
||||
|
||||
import_job = models.ImportJob.objects.create(user=self.user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/storygraph.csv")
|
||||
csv_file = open(datafile, "r") # pylint: disable=unspecified-encoding
|
||||
for index, entry in enumerate(list(csv.DictReader(csv_file))):
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=index, data=entry, book=self.book
|
||||
)
|
||||
break
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, False, "public"
|
||||
)
|
||||
import_item = import_job.items.first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, False, "public"
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
|
||||
shelf.refresh_from_db()
|
||||
self.assertEqual(shelf.books.first(), self.book)
|
||||
|
@ -102,20 +82,17 @@ class StorygraphImport(TestCase):
|
|||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
def test_handle_imported_book_rating(self, *_):
|
||||
"""storygraph rating import"""
|
||||
import_job = models.ImportJob.objects.create(user=self.user)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/storygraph.csv")
|
||||
csv_file = open(datafile, "r") # pylint: disable=unspecified-encoding
|
||||
entry = list(csv.DictReader(csv_file))[1]
|
||||
entry = self.importer.parse_fields(entry)
|
||||
import_item = models.ImportItem.objects.create(
|
||||
job_id=import_job.id, index=0, data=entry, book=self.book
|
||||
import_job = self.importer.create_job(
|
||||
self.local_user, self.csv, True, "unlisted"
|
||||
)
|
||||
import_item = import_job.items.filter(index=1).first()
|
||||
import_item.book = self.book
|
||||
import_item.save()
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, True, "unlisted"
|
||||
)
|
||||
review = models.ReviewRating.objects.get(book=self.book, user=self.user)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
handle_imported_book(import_item)
|
||||
|
||||
review = models.ReviewRating.objects.get(book=self.book, user=self.local_user)
|
||||
self.assertIsInstance(review, models.ReviewRating)
|
||||
self.assertEqual(review.rating, 5.0)
|
||||
self.assertEqual(review.published_date, make_date(2021, 5, 10))
|
||||
|
|
|
@ -6,7 +6,7 @@ from bookwyrm import models
|
|||
from bookwyrm.management.commands.populate_streams import populate_streams
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
class Activitystreams(TestCase):
|
||||
"""using redis to build activity streams"""
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ from bookwyrm.settings import PAGE_LENGTH
|
|||
|
||||
# pylint: disable=invalid-name
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
class ActivitypubMixins(TestCase):
|
||||
"""functionality shared across models"""
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.test import TestCase
|
|||
from bookwyrm import models, settings
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
class Group(TestCase):
|
||||
"""some activitypub oddness ahead"""
|
||||
|
||||
|
@ -87,7 +87,7 @@ class Group(TestCase):
|
|||
def test_group_members_can_see_followers_only_lists(self, _):
|
||||
"""follower-only group booklists should not be excluded from group booklist listing for group members who do not follower list owner"""
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
followers_list = models.List.objects.create(
|
||||
name="Followers List",
|
||||
curation="group",
|
||||
|
@ -107,7 +107,7 @@ class Group(TestCase):
|
|||
def test_group_members_can_see_private_lists(self, _):
|
||||
"""private group booklists should not be excluded from group booklist listing for group members"""
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
|
||||
private_list = models.List.objects.create(
|
||||
name="Private List",
|
||||
|
|
|
@ -18,83 +18,68 @@ class ImportJob(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""data is from a goodreads export of The Raven Tower"""
|
||||
read_data = {
|
||||
"Book Id": 39395857,
|
||||
"Title": "The Raven Tower",
|
||||
"Author": "Ann Leckie",
|
||||
"Author l-f": "Leckie, Ann",
|
||||
"Additional Authors": "",
|
||||
"ISBN": '="0356506991"',
|
||||
"ISBN13": '="9780356506999"',
|
||||
"My Rating": 0,
|
||||
"Average Rating": 4.06,
|
||||
"Publisher": "Orbit",
|
||||
"Binding": "Hardcover",
|
||||
"Number of Pages": 416,
|
||||
"Year Published": 2019,
|
||||
"Original Publication Year": 2019,
|
||||
"Date Read": "2019/04/12",
|
||||
"Date Added": "2019/04/09",
|
||||
"Bookshelves": "",
|
||||
"Bookshelves with positions": "",
|
||||
"Exclusive Shelf": "read",
|
||||
"My Review": "",
|
||||
"Spoiler": "",
|
||||
"Private Notes": "",
|
||||
"Read Count": 1,
|
||||
"Recommended For": "",
|
||||
"Recommended By": "",
|
||||
"Owned Copies": 0,
|
||||
"Original Purchase Date": "",
|
||||
"Original Purchase Location": "",
|
||||
"Condition": "",
|
||||
"Condition Description": "",
|
||||
"BCID": "",
|
||||
}
|
||||
currently_reading_data = read_data.copy()
|
||||
currently_reading_data["Exclusive Shelf"] = "currently-reading"
|
||||
currently_reading_data["Date Read"] = ""
|
||||
|
||||
unknown_read_data = currently_reading_data.copy()
|
||||
unknown_read_data["Exclusive Shelf"] = "read"
|
||||
unknown_read_data["Date Read"] = ""
|
||||
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
):
|
||||
user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "password", local=True
|
||||
)
|
||||
job = models.ImportJob.objects.create(user=user)
|
||||
self.item_1 = models.ImportItem.objects.create(
|
||||
job=job, index=1, data=currently_reading_data
|
||||
)
|
||||
self.item_2 = models.ImportItem.objects.create(job=job, index=2, data=read_data)
|
||||
self.item_3 = models.ImportItem.objects.create(
|
||||
job=job, index=3, data=unknown_read_data
|
||||
)
|
||||
self.job = models.ImportJob.objects.create(user=self.local_user, mappings={})
|
||||
|
||||
def test_isbn(self):
|
||||
"""it unquotes the isbn13 field from data"""
|
||||
expected = "9780356506999"
|
||||
item = models.ImportItem.objects.get(index=1)
|
||||
self.assertEqual(item.isbn, expected)
|
||||
item = models.ImportItem.objects.create(
|
||||
index=1,
|
||||
job=self.job,
|
||||
data={},
|
||||
normalized_data={
|
||||
"isbn_13": '="9780356506999"',
|
||||
},
|
||||
)
|
||||
self.assertEqual(item.isbn, "9780356506999")
|
||||
|
||||
def test_shelf(self):
|
||||
"""converts to the local shelf typology"""
|
||||
expected = "reading"
|
||||
self.assertEqual(self.item_1.shelf, expected)
|
||||
item = models.ImportItem.objects.create(
|
||||
index=1,
|
||||
job=self.job,
|
||||
data={},
|
||||
normalized_data={
|
||||
"isbn_13": '="9780356506999"',
|
||||
"shelf": "reading",
|
||||
},
|
||||
)
|
||||
self.assertEqual(item.shelf, "reading")
|
||||
|
||||
def test_date_added(self):
|
||||
"""converts to the local shelf typology"""
|
||||
expected = datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc)
|
||||
item = models.ImportItem.objects.get(index=1)
|
||||
item = models.ImportItem.objects.create(
|
||||
index=1,
|
||||
job=self.job,
|
||||
data={},
|
||||
normalized_data={
|
||||
"isbn_13": '="9780356506999"',
|
||||
"shelf": "reading",
|
||||
"date_added": "2019/04/09",
|
||||
},
|
||||
)
|
||||
self.assertEqual(item.date_added, expected)
|
||||
|
||||
def test_date_read(self):
|
||||
"""converts to the local shelf typology"""
|
||||
expected = datetime.datetime(2019, 4, 12, 0, 0, tzinfo=timezone.utc)
|
||||
item = models.ImportItem.objects.get(index=2)
|
||||
item = models.ImportItem.objects.create(
|
||||
index=1,
|
||||
job=self.job,
|
||||
data={},
|
||||
normalized_data={
|
||||
"isbn_13": '="9780356506999"',
|
||||
"shelf": "reading",
|
||||
"date_added": "2019/04/09",
|
||||
"date_finished": "2019/04/12",
|
||||
},
|
||||
)
|
||||
self.assertEqual(item.date_read, expected)
|
||||
|
||||
def test_currently_reading_reads(self):
|
||||
|
@ -104,31 +89,66 @@ class ImportJob(TestCase):
|
|||
start_date=datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc)
|
||||
)
|
||||
]
|
||||
actual = models.ImportItem.objects.get(index=1)
|
||||
self.assertEqual(actual.reads[0].start_date, expected[0].start_date)
|
||||
self.assertEqual(actual.reads[0].finish_date, expected[0].finish_date)
|
||||
item = models.ImportItem.objects.create(
|
||||
index=1,
|
||||
job=self.job,
|
||||
data={},
|
||||
normalized_data={
|
||||
"isbn_13": '="9780356506999"',
|
||||
"shelf": "reading",
|
||||
"date_added": "2019/04/09",
|
||||
},
|
||||
)
|
||||
self.assertEqual(item.reads[0].start_date, expected[0].start_date)
|
||||
self.assertIsNone(item.reads[0].finish_date)
|
||||
|
||||
def test_read_reads(self):
|
||||
"""infer read dates where available"""
|
||||
actual = self.item_2
|
||||
item = models.ImportItem.objects.create(
|
||||
index=1,
|
||||
job=self.job,
|
||||
data={},
|
||||
normalized_data={
|
||||
"isbn_13": '="9780356506999"',
|
||||
"shelf": "reading",
|
||||
"date_added": "2019/04/09",
|
||||
"date_finished": "2019/04/12",
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
actual.reads[0].start_date,
|
||||
item.reads[0].start_date,
|
||||
datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc),
|
||||
)
|
||||
self.assertEqual(
|
||||
actual.reads[0].finish_date,
|
||||
item.reads[0].finish_date,
|
||||
datetime.datetime(2019, 4, 12, 0, 0, tzinfo=timezone.utc),
|
||||
)
|
||||
|
||||
def test_unread_reads(self):
|
||||
"""handle books with no read dates"""
|
||||
expected = []
|
||||
actual = models.ImportItem.objects.get(index=3)
|
||||
self.assertEqual(actual.reads, expected)
|
||||
item = models.ImportItem.objects.create(
|
||||
index=1,
|
||||
job=self.job,
|
||||
data={},
|
||||
normalized_data={
|
||||
"isbn_13": '="9780356506999"',
|
||||
"shelf": "reading",
|
||||
},
|
||||
)
|
||||
self.assertEqual(item.reads, expected)
|
||||
|
||||
@responses.activate
|
||||
def test_get_book_from_isbn(self):
|
||||
"""search and load books by isbn (9780356506999)"""
|
||||
item = models.ImportItem.objects.create(
|
||||
index=1,
|
||||
job=self.job,
|
||||
data={},
|
||||
normalized_data={
|
||||
"isbn_13": '="9780356506999"',
|
||||
},
|
||||
)
|
||||
connector_info = models.Connector.objects.create(
|
||||
identifier="openlibrary.org",
|
||||
name="OpenLibrary",
|
||||
|
@ -177,6 +197,6 @@ class ImportJob(TestCase):
|
|||
with patch(
|
||||
"bookwyrm.connectors.openlibrary.Connector." "get_authors_from_data"
|
||||
):
|
||||
book = self.item_1.get_book_from_isbn()
|
||||
book = item.get_book_from_isbn()
|
||||
|
||||
self.assertEqual(book.title, "Sabriel")
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.test import TestCase
|
|||
from bookwyrm import models, settings
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
class List(TestCase):
|
||||
"""some activitypub oddness ahead"""
|
||||
|
||||
|
@ -22,7 +22,7 @@ class List(TestCase):
|
|||
|
||||
def test_remote_id(self, _):
|
||||
"""shelves use custom remote ids"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
book_list = models.List.objects.create(
|
||||
name="Test List", user=self.local_user
|
||||
)
|
||||
|
@ -31,7 +31,7 @@ class List(TestCase):
|
|||
|
||||
def test_to_activity(self, _):
|
||||
"""jsonify it"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
book_list = models.List.objects.create(
|
||||
name="Test List", user=self.local_user
|
||||
)
|
||||
|
@ -45,7 +45,7 @@ class List(TestCase):
|
|||
|
||||
def test_list_item(self, _):
|
||||
"""a list entry"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
book_list = models.List.objects.create(
|
||||
name="Test List", user=self.local_user, privacy="unlisted"
|
||||
)
|
||||
|
@ -63,7 +63,7 @@ class List(TestCase):
|
|||
|
||||
def test_list_item_pending(self, _):
|
||||
"""a list entry"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
book_list = models.List.objects.create(
|
||||
name="Test List", user=self.local_user
|
||||
)
|
||||
|
|
|
@ -33,11 +33,13 @@ class Relationship(TestCase):
|
|||
|
||||
def test_user_follows_from_request(self, _):
|
||||
"""convert a follow request into a follow"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
request = models.UserFollowRequest.objects.create(
|
||||
user_subject=self.local_user, user_object=self.remote_user
|
||||
)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Follow")
|
||||
self.assertEqual(
|
||||
request.remote_id, "http://local.com/user/mouse#follows/%d" % request.id
|
||||
|
@ -54,7 +56,7 @@ class Relationship(TestCase):
|
|||
|
||||
def test_user_follows_from_request_custom_remote_id(self, _):
|
||||
"""store a specific remote id for a relationship provided by remote"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
request = models.UserFollowRequest.objects.create(
|
||||
user_subject=self.local_user,
|
||||
user_object=self.remote_user,
|
||||
|
@ -69,19 +71,19 @@ class Relationship(TestCase):
|
|||
self.assertEqual(rel.user_subject, self.local_user)
|
||||
self.assertEqual(rel.user_object, self.remote_user)
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
def test_follow_request_activity(self, broadcast_mock, _):
|
||||
"""accept a request and make it a relationship"""
|
||||
models.UserFollowRequest.objects.create(
|
||||
user_subject=self.local_user,
|
||||
user_object=self.remote_user,
|
||||
)
|
||||
activity = json.loads(broadcast_mock.call_args[0][1])
|
||||
activity = json.loads(broadcast_mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["object"], self.remote_user.remote_id)
|
||||
self.assertEqual(activity["type"], "Follow")
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
def test_follow_request_accept(self, broadcast_mock, _):
|
||||
"""accept a request and make it a relationship"""
|
||||
self.local_user.manually_approves_followers = True
|
||||
|
@ -96,7 +98,7 @@ class Relationship(TestCase):
|
|||
)
|
||||
request.accept()
|
||||
|
||||
activity = json.loads(broadcast_mock.call_args[0][1])
|
||||
activity = json.loads(broadcast_mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Accept")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["object"]["id"], "https://www.hi.com/")
|
||||
|
@ -107,7 +109,7 @@ class Relationship(TestCase):
|
|||
self.assertEqual(rel.user_subject, self.remote_user)
|
||||
self.assertEqual(rel.user_object, self.local_user)
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
def test_follow_request_reject(self, broadcast_mock, _):
|
||||
"""accept a request and make it a relationship"""
|
||||
self.local_user.manually_approves_followers = True
|
||||
|
@ -120,7 +122,7 @@ class Relationship(TestCase):
|
|||
)
|
||||
request.reject()
|
||||
|
||||
activity = json.loads(broadcast_mock.call_args[0][1])
|
||||
activity = json.loads(broadcast_mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Reject")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["object"]["id"], request.remote_id)
|
||||
|
|
|
@ -27,7 +27,7 @@ class Shelf(TestCase):
|
|||
|
||||
def test_remote_id(self, *_):
|
||||
"""shelves use custom remote ids"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
shelf = models.Shelf.objects.create(
|
||||
name="Test Shelf", identifier="test-shelf", user=self.local_user
|
||||
)
|
||||
|
@ -36,7 +36,7 @@ class Shelf(TestCase):
|
|||
|
||||
def test_to_activity(self, *_):
|
||||
"""jsonify it"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
shelf = models.Shelf.objects.create(
|
||||
name="Test Shelf", identifier="test-shelf", user=self.local_user
|
||||
)
|
||||
|
@ -51,19 +51,23 @@ class Shelf(TestCase):
|
|||
def test_create_update_shelf(self, *_):
|
||||
"""create and broadcast shelf creation"""
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
shelf = models.Shelf.objects.create(
|
||||
name="Test Shelf", identifier="test-shelf", user=self.local_user
|
||||
)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Create")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["object"]["name"], "Test Shelf")
|
||||
|
||||
shelf.name = "arthur russel"
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
shelf.save()
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Update")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["object"]["name"], "arthur russel")
|
||||
|
@ -71,27 +75,31 @@ class Shelf(TestCase):
|
|||
|
||||
def test_shelve(self, *_):
|
||||
"""create and broadcast shelf creation"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
shelf = models.Shelf.objects.create(
|
||||
name="Test Shelf", identifier="test-shelf", user=self.local_user
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
shelf_book = models.ShelfBook.objects.create(
|
||||
shelf=shelf, user=self.local_user, book=self.book
|
||||
)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["object"]["id"], shelf_book.remote_id)
|
||||
self.assertEqual(activity["target"], shelf.remote_id)
|
||||
self.assertEqual(shelf.books.first(), self.book)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
shelf_book.delete()
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Remove")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["object"]["id"], shelf_book.remote_id)
|
||||
|
|
|
@ -362,19 +362,15 @@ class Status(TestCase):
|
|||
|
||||
def test_favorite(self, *_):
|
||||
"""fav a status"""
|
||||
real_broadcast = models.Favorite.broadcast
|
||||
|
||||
def fav_broadcast_mock(_, activity, user):
|
||||
"""ok"""
|
||||
self.assertEqual(user.remote_id, self.local_user.remote_id)
|
||||
self.assertEqual(activity["type"], "Like")
|
||||
|
||||
models.Favorite.broadcast = fav_broadcast_mock
|
||||
|
||||
status = models.Status.objects.create(
|
||||
content="test content", user=self.local_user
|
||||
)
|
||||
fav = models.Favorite.objects.create(status=status, user=self.local_user)
|
||||
|
||||
with patch("bookwyrm.models.Favorite.broadcast") as mock:
|
||||
fav = models.Favorite.objects.create(status=status, user=self.local_user)
|
||||
args = mock.call_args[0]
|
||||
self.assertEqual(args[1].remote_id, self.local_user.remote_id)
|
||||
self.assertEqual(args[0]["type"], "Like")
|
||||
|
||||
# can't fav a status twice
|
||||
with self.assertRaises(IntegrityError):
|
||||
|
@ -384,7 +380,6 @@ class Status(TestCase):
|
|||
self.assertEqual(activity["type"], "Like")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["object"], status.remote_id)
|
||||
models.Favorite.broadcast = real_broadcast
|
||||
|
||||
def test_boost(self, *_):
|
||||
"""boosting, this one's a bit fussy"""
|
||||
|
|
|
@ -165,12 +165,12 @@ class User(TestCase):
|
|||
"""deactivate a user"""
|
||||
self.assertTrue(self.user.is_active)
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as broadcast_mock:
|
||||
self.user.delete()
|
||||
|
||||
self.assertEqual(broadcast_mock.call_count, 1)
|
||||
activity = json.loads(broadcast_mock.call_args[0][1])
|
||||
activity = json.loads(broadcast_mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Delete")
|
||||
self.assertEqual(activity["object"], self.user.remote_id)
|
||||
self.assertFalse(self.user.is_active)
|
||||
|
|
|
@ -57,12 +57,24 @@ class BookSearch(TestCase):
|
|||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0], self.second_edition)
|
||||
|
||||
def test_search_identifiers_return_first(self):
|
||||
"""search by unique identifiers"""
|
||||
result = book_search.search_identifiers("hello", return_first=True)
|
||||
self.assertEqual(result, self.second_edition)
|
||||
|
||||
def test_search_title_author(self):
|
||||
"""search by unique identifiers"""
|
||||
results = book_search.search_title_author("Another", min_confidence=0)
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0], self.second_edition)
|
||||
|
||||
def test_search_title_author_return_first(self):
|
||||
"""search by unique identifiers"""
|
||||
results = book_search.search_title_author(
|
||||
"Another", min_confidence=0, return_first=True
|
||||
)
|
||||
self.assertEqual(results, self.second_edition)
|
||||
|
||||
def test_format_search_result(self):
|
||||
"""format a search result"""
|
||||
result = book_search.format_search_result(self.first_edition)
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.test import TestCase
|
|||
from bookwyrm import models
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
class PostgresTriggers(TestCase):
|
||||
"""special migrations, fancy stuff ya know"""
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from bookwyrm import models
|
|||
from bookwyrm.suggested_users import suggested_users, get_annotated_users
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
|
||||
|
@ -168,7 +168,7 @@ class SuggestedUsers(TestCase):
|
|||
remote_id="https://example.com/book/1",
|
||||
parent_work=work,
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
# 1 shared follow
|
||||
self.local_user.following.add(user_2)
|
||||
user_1.followers.add(user_2)
|
||||
|
@ -213,7 +213,7 @@ class SuggestedUsers(TestCase):
|
|||
user.following.add(user_1)
|
||||
user.followers.add(self.local_user)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
for i in range(3):
|
||||
book = models.Edition.objects.create(
|
||||
title=i,
|
||||
|
|
|
@ -44,7 +44,7 @@ class TemplateTags(TestCase):
|
|||
|
||||
def test_get_user_rating(self, *_):
|
||||
"""get a user's most recent rating of a book"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.Review.objects.create(user=self.user, book=self.book, rating=3)
|
||||
self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 3)
|
||||
|
||||
|
@ -63,7 +63,7 @@ class TemplateTags(TestCase):
|
|||
utilities.get_user_identifier(self.remote_user), "rat@example.com"
|
||||
)
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
def test_get_replies(self, *_):
|
||||
"""direct replies to a status"""
|
||||
parent = models.Review.objects.create(
|
||||
|
@ -90,7 +90,7 @@ class TemplateTags(TestCase):
|
|||
|
||||
def test_get_parent(self, *_):
|
||||
"""get the reply parent of a status"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
parent = models.Review.objects.create(
|
||||
user=self.user, book=self.book, content="hi"
|
||||
)
|
||||
|
@ -107,7 +107,7 @@ class TemplateTags(TestCase):
|
|||
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
||||
|
||||
self.assertFalse(interaction.get_user_liked(self.user, status))
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.Favorite.objects.create(user=self.user, status=status)
|
||||
self.assertTrue(interaction.get_user_liked(self.user, status))
|
||||
|
||||
|
@ -116,13 +116,13 @@ class TemplateTags(TestCase):
|
|||
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
||||
|
||||
self.assertFalse(interaction.get_user_boosted(self.user, status))
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.Boost.objects.create(user=self.user, boosted_status=status)
|
||||
self.assertTrue(interaction.get_user_boosted(self.user, status))
|
||||
|
||||
def test_get_boosted(self, *_):
|
||||
"""load a boosted status"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
||||
boost = models.Boost.objects.create(user=self.user, boosted_status=status)
|
||||
boosted = status_display.get_boosted(boost)
|
||||
|
@ -166,7 +166,7 @@ class TemplateTags(TestCase):
|
|||
|
||||
def test_related_status(self, *_):
|
||||
"""gets the subclass model for a notification status"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
status = models.Status.objects.create(content="hi", user=self.user)
|
||||
notification = models.Notification.objects.create(
|
||||
user=self.user, notification_type="MENTION", related_status=status
|
||||
|
|
|
@ -151,10 +151,12 @@ class ReportViews(TestCase):
|
|||
request.user.is_superuser = True
|
||||
|
||||
# de-activate
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.moderator_delete_user(request, self.rat.id)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Delete")
|
||||
|
||||
self.rat.refresh_from_db()
|
||||
|
|
|
@ -67,7 +67,7 @@ class UserAdminViews(TestCase):
|
|||
request.user = self.local_user
|
||||
request.user.is_superuser = True
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
result = view(request, self.local_user.id)
|
||||
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
|
|
|
@ -78,7 +78,7 @@ class BookViews(TestCase):
|
|||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
def test_book_page_statuses(self, *_):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
|
@ -169,7 +169,7 @@ class BookViews(TestCase):
|
|||
request.user = self.local_user
|
||||
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as delay_mock:
|
||||
views.upload_cover(request, self.book.id)
|
||||
self.assertEqual(delay_mock.call_count, 1)
|
||||
|
@ -188,7 +188,7 @@ class BookViews(TestCase):
|
|||
request.user = self.local_user
|
||||
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as delay_mock:
|
||||
views.upload_cover(request, self.book.id)
|
||||
self.assertEqual(delay_mock.call_count, 1)
|
||||
|
@ -202,7 +202,7 @@ class BookViews(TestCase):
|
|||
request = self.factory.post("", {"description": "new description hi"})
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.add_description(request, self.book.id)
|
||||
|
||||
self.book.refresh_from_db()
|
||||
|
|
|
@ -79,7 +79,7 @@ class EditBookViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
view(request, self.book.id)
|
||||
|
||||
self.book.refresh_from_db()
|
||||
|
@ -115,7 +115,7 @@ class EditBookViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
view(request, self.book.id)
|
||||
|
||||
self.book.refresh_from_db()
|
||||
|
@ -136,7 +136,7 @@ class EditBookViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
view(request, self.book.id)
|
||||
self.book.refresh_from_db()
|
||||
self.assertEqual(self.book.title, "New Title")
|
||||
|
@ -207,7 +207,7 @@ class EditBookViews(TestCase):
|
|||
request.user = self.local_user
|
||||
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as delay_mock:
|
||||
views.upload_cover(request, self.book.id)
|
||||
self.assertEqual(delay_mock.call_count, 1)
|
||||
|
|
|
@ -111,7 +111,7 @@ class BookViews(TestCase):
|
|||
work = models.Work.objects.create(title="test work")
|
||||
edition1 = models.Edition.objects.create(title="first ed", parent_work=work)
|
||||
edition2 = models.Edition.objects.create(title="second ed", parent_work=work)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
shelf = models.Shelf.objects.create(name="Test Shelf", user=self.local_user)
|
||||
models.ShelfBook.objects.create(
|
||||
book=edition1,
|
||||
|
@ -124,7 +124,7 @@ class BookViews(TestCase):
|
|||
self.assertEqual(models.ReadThrough.objects.get().book, edition1)
|
||||
request = self.factory.post("", {"edition": edition2.id})
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.switch_edition(request)
|
||||
|
||||
self.assertEqual(models.ShelfBook.objects.get().book, edition2)
|
||||
|
|
1
bookwyrm/tests/views/imports/__init__.py
Normal file
1
bookwyrm/tests/views/imports/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from . import *
|
|
@ -41,7 +41,7 @@ class ImportViews(TestCase):
|
|||
def test_import_status(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.ImportStatus.as_view()
|
||||
import_job = models.ImportJob.objects.create(user=self.local_user)
|
||||
import_job = models.ImportJob.objects.create(user=self.local_user, mappings={})
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.tasks.app.AsyncResult") as async_result:
|
||||
|
@ -55,10 +55,10 @@ class ImportViews(TestCase):
|
|||
"""retry failed items"""
|
||||
view = views.Import.as_view()
|
||||
form = forms.ImportForm()
|
||||
form.data["source"] = "LibraryThing"
|
||||
form.data["source"] = "Goodreads"
|
||||
form.data["privacy"] = "public"
|
||||
form.data["include_reviews"] = False
|
||||
csv_file = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")
|
||||
csv_file = pathlib.Path(__file__).parent.joinpath("../../data/goodreads.csv")
|
||||
form.data["csv_file"] = SimpleUploadedFile(
|
||||
# pylint: disable=consider-using-with
|
||||
csv_file,
|
||||
|
@ -74,22 +74,3 @@ class ImportViews(TestCase):
|
|||
job = models.ImportJob.objects.get()
|
||||
self.assertFalse(job.include_reviews)
|
||||
self.assertEqual(job.privacy, "public")
|
||||
|
||||
def test_retry_import(self):
|
||||
"""retry failed items"""
|
||||
view = views.ImportStatus.as_view()
|
||||
import_job = models.ImportJob.objects.create(
|
||||
user=self.local_user, privacy="unlisted"
|
||||
)
|
||||
request = self.factory.post("")
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.importers.Importer.start_import"):
|
||||
view(request, import_job.id)
|
||||
|
||||
self.assertEqual(models.ImportJob.objects.count(), 2)
|
||||
retry_job = models.ImportJob.objects.last()
|
||||
|
||||
self.assertTrue(retry_job.retry)
|
||||
self.assertEqual(retry_job.user, self.local_user)
|
||||
self.assertEqual(retry_job.privacy, "unlisted")
|
87
bookwyrm/tests/views/imports/test_import_review.py
Normal file
87
bookwyrm/tests/views/imports/test_import_review.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
""" test for app action functionality """
|
||||
from unittest.mock import patch
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from bookwyrm.tests.validate_html import validate_html
|
||||
|
||||
from bookwyrm import models, views
|
||||
|
||||
|
||||
class ImportManualReviewViews(TestCase):
|
||||
"""goodreads import views"""
|
||||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.mouse",
|
||||
"password",
|
||||
local=True,
|
||||
localname="mouse",
|
||||
)
|
||||
models.SiteSettings.objects.create()
|
||||
self.job = models.ImportJob.objects.create(user=self.local_user, mappings={})
|
||||
|
||||
work = models.Work.objects.create(title="Test Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
remote_id="https://example.com/book/1",
|
||||
parent_work=work,
|
||||
)
|
||||
|
||||
def test_import_troubleshoot_get(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.ImportManualReview.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.tasks.app.AsyncResult") as async_result:
|
||||
async_result.return_value = []
|
||||
result = view(request, self.job.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_approve_item(self):
|
||||
"""a guess is correct"""
|
||||
import_item = models.ImportItem.objects.create(
|
||||
index=0,
|
||||
job=self.job,
|
||||
book_guess=self.book,
|
||||
fail_reason="no match",
|
||||
data={},
|
||||
normalized_data={},
|
||||
)
|
||||
request = self.factory.post("")
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.importers.importer.import_item_task.delay") as mock:
|
||||
views.approve_import_item(request, self.job.id, import_item.id)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
import_item.refresh_from_db()
|
||||
self.assertIsNone(import_item.fail_reason)
|
||||
self.assertIsNone(import_item.book_guess)
|
||||
self.assertEqual(import_item.book.id, self.book.id)
|
||||
|
||||
def test_delete_item(self):
|
||||
"""a guess is correct"""
|
||||
import_item = models.ImportItem.objects.create(
|
||||
index=0,
|
||||
job=self.job,
|
||||
book_guess=self.book,
|
||||
fail_reason="no match",
|
||||
data={},
|
||||
normalized_data={},
|
||||
)
|
||||
request = self.factory.post("")
|
||||
request.user = self.local_user
|
||||
|
||||
views.delete_import_item(request, self.job.id, import_item.id)
|
||||
import_item.refresh_from_db()
|
||||
self.assertEqual(import_item.fail_reason, "no match")
|
||||
self.assertIsNone(import_item.book_guess)
|
||||
self.assertIsNone(import_item.book)
|
59
bookwyrm/tests/views/imports/test_import_troubleshoot.py
Normal file
59
bookwyrm/tests/views/imports/test_import_troubleshoot.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
""" test for app action functionality """
|
||||
from unittest.mock import patch
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from bookwyrm.tests.validate_html import validate_html
|
||||
|
||||
from bookwyrm import models, views
|
||||
|
||||
|
||||
class ImportTroubleshootViews(TestCase):
|
||||
"""goodreads import views"""
|
||||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.mouse",
|
||||
"password",
|
||||
local=True,
|
||||
localname="mouse",
|
||||
)
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_import_troubleshoot_get(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.ImportTroubleshoot.as_view()
|
||||
import_job = models.ImportJob.objects.create(user=self.local_user, mappings={})
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.tasks.app.AsyncResult") as async_result:
|
||||
async_result.return_value = []
|
||||
result = view(request, import_job.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_retry_import(self):
|
||||
"""retry failed items"""
|
||||
view = views.ImportTroubleshoot.as_view()
|
||||
import_job = models.ImportJob.objects.create(
|
||||
user=self.local_user, privacy="unlisted", mappings={}
|
||||
)
|
||||
request = self.factory.post("")
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.importers.Importer.start_import"):
|
||||
view(request, import_job.id)
|
||||
|
||||
self.assertEqual(models.ImportJob.objects.count(), 2)
|
||||
retry_job = models.ImportJob.objects.last()
|
||||
|
||||
self.assertTrue(retry_job.retry)
|
||||
self.assertEqual(retry_job.user, self.local_user)
|
||||
self.assertEqual(retry_job.privacy, "unlisted")
|
|
@ -36,7 +36,7 @@ class InboxActivities(TestCase):
|
|||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
with patch("bookwyrm.activitystreams.add_status_task.delay"):
|
||||
self.status = models.Status.objects.create(
|
||||
user=self.local_user,
|
||||
|
|
|
@ -40,7 +40,7 @@ class InboxBlock(TestCase):
|
|||
def test_handle_blocks(self):
|
||||
"""create a "block" database entry from an activity"""
|
||||
self.local_user.followers.add(self.remote_user)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.UserFollowRequest.objects.create(
|
||||
user_subject=self.local_user, user_object=self.remote_user
|
||||
)
|
||||
|
|
|
@ -10,7 +10,7 @@ from bookwyrm.activitypub import ActivitySerializerError
|
|||
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
|
||||
class InboxCreate(TestCase):
|
||||
"""readthrough tests"""
|
||||
|
|
|
@ -49,10 +49,12 @@ class InboxRelationships(TestCase):
|
|||
}
|
||||
|
||||
self.assertFalse(models.UserFollowRequest.objects.exists())
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.inbox.activity_task(activity)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
response_activity = json.loads(mock.call_args[0][1])
|
||||
response_activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(response_activity["type"], "Accept")
|
||||
|
||||
# notification created
|
||||
|
@ -77,17 +79,19 @@ class InboxRelationships(TestCase):
|
|||
"object": "https://example.com/user/mouse",
|
||||
}
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.inbox.activity_task(activity)
|
||||
|
||||
# the follow relationship should exist
|
||||
follow = models.UserFollows.objects.get(user_object=self.local_user)
|
||||
self.assertEqual(follow.user_subject, self.remote_user)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.inbox.activity_task(activity)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
response_activity = json.loads(mock.call_args[0][1])
|
||||
response_activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(response_activity["type"], "Accept")
|
||||
|
||||
# the follow relationship should STILL exist
|
||||
|
@ -109,7 +113,7 @@ class InboxRelationships(TestCase):
|
|||
broadcast=False, update_fields=["manually_approves_followers"]
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.inbox.activity_task(activity)
|
||||
|
||||
# notification created
|
||||
|
@ -132,7 +136,7 @@ class InboxRelationships(TestCase):
|
|||
self.local_user.save(
|
||||
broadcast=False, update_fields=["manually_approves_followers"]
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
request = models.UserFollowRequest.objects.create(
|
||||
user_subject=self.remote_user, user_object=self.local_user
|
||||
)
|
||||
|
@ -160,7 +164,7 @@ class InboxRelationships(TestCase):
|
|||
|
||||
def test_unfollow(self):
|
||||
"""remove a relationship"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
rel = models.UserFollows.objects.create(
|
||||
user_subject=self.remote_user, user_object=self.local_user
|
||||
)
|
||||
|
@ -186,7 +190,7 @@ class InboxRelationships(TestCase):
|
|||
@patch("bookwyrm.activitystreams.add_user_statuses_task.delay")
|
||||
def test_follow_accept(self, _):
|
||||
"""a remote user approved a follow request from local"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
rel = models.UserFollowRequest.objects.create(
|
||||
user_subject=self.local_user, user_object=self.remote_user
|
||||
)
|
||||
|
@ -217,7 +221,7 @@ class InboxRelationships(TestCase):
|
|||
|
||||
def test_follow_reject(self):
|
||||
"""turn down a follow request"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
rel = models.UserFollowRequest.objects.create(
|
||||
user_subject=self.local_user, user_object=self.remote_user
|
||||
)
|
||||
|
|
|
@ -35,7 +35,7 @@ class InboxActivities(TestCase):
|
|||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
with patch("bookwyrm.activitystreams.add_status_task.delay"):
|
||||
self.status = models.Status.objects.create(
|
||||
user=self.local_user,
|
||||
|
|
|
@ -75,7 +75,7 @@ class InboxRemove(TestCase):
|
|||
|
||||
def test_handle_remove_book_from_list(self):
|
||||
"""listing a book"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
booklist = models.List.objects.create(
|
||||
name="test list",
|
||||
user=self.local_user,
|
||||
|
|
|
@ -50,7 +50,7 @@ class InboxUpdate(TestCase):
|
|||
|
||||
def test_update_list(self):
|
||||
"""a new list"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
book_list = models.List.objects.create(
|
||||
name="hi", remote_id="https://example.com/list/22", user=self.local_user
|
||||
)
|
||||
|
@ -171,7 +171,7 @@ class InboxUpdate(TestCase):
|
|||
book = models.Work.objects.get(id=book.id)
|
||||
self.assertEqual(book.title, "Piranesi")
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
def test_update_status(self, *_):
|
||||
"""edit a status"""
|
||||
|
|
|
@ -9,7 +9,7 @@ from bookwyrm import models, views
|
|||
from bookwyrm.tests.validate_html import validate_html
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
class BlockViews(TestCase):
|
||||
"""view user and edit profile"""
|
||||
|
||||
|
|
|
@ -35,9 +35,9 @@ class DeleteUserViews(TestCase):
|
|||
self.book = models.Edition.objects.create(
|
||||
title="test", parent_work=models.Work.objects.create(title="test work")
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.add_book_statuses_task.delay"
|
||||
):
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
), patch("bookwyrm.activitystreams.add_book_statuses_task.delay"):
|
||||
models.ShelfBook.objects.create(
|
||||
book=self.book,
|
||||
user=self.local_user,
|
||||
|
@ -70,11 +70,11 @@ class DeleteUserViews(TestCase):
|
|||
|
||||
self.assertIsNone(self.local_user.name)
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as delay_mock:
|
||||
view(request)
|
||||
self.assertEqual(delay_mock.call_count, 1)
|
||||
activity = json.loads(delay_mock.call_args[0][1])
|
||||
activity = json.loads(delay_mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Delete")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(
|
||||
|
|
|
@ -38,9 +38,9 @@ class EditUserViews(TestCase):
|
|||
self.book = models.Edition.objects.create(
|
||||
title="test", parent_work=models.Work.objects.create(title="test work")
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.add_book_statuses_task.delay"
|
||||
):
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
), patch("bookwyrm.activitystreams.add_book_statuses_task.delay"):
|
||||
models.ShelfBook.objects.create(
|
||||
book=self.book,
|
||||
user=self.local_user,
|
||||
|
@ -74,7 +74,7 @@ class EditUserViews(TestCase):
|
|||
|
||||
self.assertIsNone(self.local_user.name)
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as delay_mock:
|
||||
view(request)
|
||||
self.assertEqual(delay_mock.call_count, 1)
|
||||
|
@ -100,7 +100,7 @@ class EditUserViews(TestCase):
|
|||
request.user = self.local_user
|
||||
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as delay_mock:
|
||||
view(request)
|
||||
self.assertEqual(delay_mock.call_count, 1)
|
||||
|
|
|
@ -11,7 +11,7 @@ from bookwyrm.activitypub import ActivitypubResponse
|
|||
from bookwyrm.tests.validate_html import validate_html
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
|
||||
@patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
|
||||
|
@ -39,7 +39,7 @@ class ShelfViews(TestCase):
|
|||
remote_id="https://example.com/book/1",
|
||||
parent_work=self.work,
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
self.shelf = models.Shelf.objects.create(
|
||||
name="Test Shelf", identifier="test-shelf", user=self.local_user
|
||||
)
|
||||
|
@ -142,7 +142,7 @@ class ShelfViews(TestCase):
|
|||
"", {"privacy": "public", "user": self.local_user.id, "name": "cool name"}
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
view(request, request.user.username, shelf.identifier)
|
||||
shelf.refresh_from_db()
|
||||
|
||||
|
@ -159,7 +159,7 @@ class ShelfViews(TestCase):
|
|||
"", {"privacy": "public", "user": self.local_user.id, "name": "cool name"}
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
view(request, request.user.username, shelf.identifier)
|
||||
|
||||
self.assertEqual(shelf.name, "To Read")
|
||||
|
|
|
@ -9,7 +9,7 @@ from django.test.client import RequestFactory
|
|||
from bookwyrm import forms, models, views
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
|
||||
@patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
|
||||
|
@ -37,7 +37,7 @@ class ShelfActionViews(TestCase):
|
|||
remote_id="https://example.com/book/1",
|
||||
parent_work=self.work,
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
self.shelf = models.Shelf.objects.create(
|
||||
name="Test Shelf", identifier="test-shelf", user=self.local_user
|
||||
)
|
||||
|
@ -49,11 +49,13 @@ class ShelfActionViews(TestCase):
|
|||
"", {"book": self.book.id, "shelf": self.shelf.identifier}
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.shelve(request)
|
||||
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
|
||||
item = models.ShelfBook.objects.get()
|
||||
|
@ -69,7 +71,7 @@ class ShelfActionViews(TestCase):
|
|||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.shelve(request)
|
||||
# make sure the book is on the shelf
|
||||
self.assertEqual(shelf.books.get(), self.book)
|
||||
|
@ -82,7 +84,7 @@ class ShelfActionViews(TestCase):
|
|||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.shelve(request)
|
||||
# make sure the book is on the shelf
|
||||
self.assertEqual(shelf.books.get(), self.book)
|
||||
|
@ -95,7 +97,7 @@ class ShelfActionViews(TestCase):
|
|||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.shelve(request)
|
||||
# make sure the book is on the shelf
|
||||
self.assertEqual(shelf.books.get(), self.book)
|
||||
|
@ -118,7 +120,7 @@ class ShelfActionViews(TestCase):
|
|||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.shelve(request)
|
||||
# make sure the book is on the shelf
|
||||
self.assertEqual(shelf.books.get(), self.book)
|
||||
|
@ -126,7 +128,7 @@ class ShelfActionViews(TestCase):
|
|||
|
||||
def test_unshelve(self, *_):
|
||||
"""remove a book from a shelf"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ShelfBook.objects.create(
|
||||
book=self.book, user=self.local_user, shelf=self.shelf
|
||||
)
|
||||
|
@ -136,9 +138,11 @@ class ShelfActionViews(TestCase):
|
|||
self.assertEqual(self.shelf.books.count(), 1)
|
||||
request = self.factory.post("", {"book": self.book.id, "shelf": self.shelf.id})
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.unshelve(request)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Remove")
|
||||
self.assertEqual(activity["object"]["id"], item.remote_id)
|
||||
self.assertEqual(self.shelf.books.count(), 0)
|
||||
|
@ -192,7 +196,7 @@ class ShelfActionViews(TestCase):
|
|||
|
||||
def test_delete_shelf_has_book(self, *_):
|
||||
"""delete a brand new custom shelf"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ShelfBook.objects.create(
|
||||
book=self.book, user=self.local_user, shelf=self.shelf
|
||||
)
|
||||
|
|
|
@ -111,7 +111,7 @@ class AuthorViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
view(request, author.id)
|
||||
author.refresh_from_db()
|
||||
self.assertEqual(author.name, "New Name")
|
||||
|
|
|
@ -41,7 +41,7 @@ class DiscoverViews(TestCase):
|
|||
self.assertEqual(result.status_code, 200)
|
||||
result.render()
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
def test_discover_page(self, *_):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
|
|
|
@ -57,7 +57,7 @@ class FeedViews(TestCase):
|
|||
def test_status_page(self, *_):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.Status.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
status = models.Status.objects.create(content="hi", user=self.local_user)
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
@ -95,7 +95,7 @@ class FeedViews(TestCase):
|
|||
local=True,
|
||||
localname="rat",
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
status = models.Status.objects.create(content="hi", user=another_user)
|
||||
|
||||
request = self.factory.get("")
|
||||
|
@ -115,7 +115,7 @@ class FeedViews(TestCase):
|
|||
image = Image.open(image_file)
|
||||
output = BytesIO()
|
||||
image.save(output, format=image.format)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
status = models.Review.objects.create(
|
||||
content="hi",
|
||||
user=self.local_user,
|
||||
|
@ -144,7 +144,7 @@ class FeedViews(TestCase):
|
|||
def test_replies_page(self, *_):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.Replies.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
status = models.Status.objects.create(content="hi", user=self.local_user)
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
@ -171,7 +171,7 @@ class FeedViews(TestCase):
|
|||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
|
||||
def test_get_suggested_book(self, *_):
|
||||
"""gets books the ~*~ algorithm ~*~ thinks you want to post about"""
|
||||
|
|
|
@ -59,7 +59,7 @@ class FollowViews(TestCase):
|
|||
request.user = self.local_user
|
||||
self.assertEqual(models.UserFollowRequest.objects.count(), 0)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.follow(request)
|
||||
|
||||
rel = models.UserFollowRequest.objects.get()
|
||||
|
@ -86,7 +86,7 @@ class FollowViews(TestCase):
|
|||
request.user = self.local_user
|
||||
self.assertEqual(models.UserFollowRequest.objects.count(), 0)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.follow(request)
|
||||
rel = models.UserFollowRequest.objects.get()
|
||||
|
||||
|
@ -111,7 +111,7 @@ class FollowViews(TestCase):
|
|||
request.user = self.local_user
|
||||
self.assertEqual(models.UserFollowRequest.objects.count(), 0)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.follow(request)
|
||||
|
||||
rel = models.UserFollows.objects.get()
|
||||
|
@ -127,10 +127,12 @@ class FollowViews(TestCase):
|
|||
request.user = self.local_user
|
||||
self.remote_user.followers.add(self.local_user)
|
||||
self.assertEqual(self.remote_user.followers.count(), 1)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.unfollow(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args_list[0][0][1])
|
||||
activity = json.loads(mock.call_args_list[0][1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Undo")
|
||||
|
||||
self.assertEqual(self.remote_user.followers.count(), 0)
|
||||
|
@ -147,7 +149,7 @@ class FollowViews(TestCase):
|
|||
user_subject=self.remote_user, user_object=self.local_user
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.accept_follow_request(request)
|
||||
# request should be deleted
|
||||
self.assertEqual(models.UserFollowRequest.objects.filter(id=rel.id).count(), 0)
|
||||
|
@ -166,7 +168,7 @@ class FollowViews(TestCase):
|
|||
user_subject=self.remote_user, user_object=self.local_user
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.delete_follow_request(request)
|
||||
# request should be deleted
|
||||
self.assertEqual(models.UserFollowRequest.objects.filter(id=rel.id).count(), 0)
|
||||
|
|
|
@ -56,7 +56,7 @@ class GetStartedViews(TestCase):
|
|||
|
||||
self.assertIsNone(self.local_user.name)
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as delay_mock:
|
||||
view(request)
|
||||
self.assertEqual(delay_mock.call_count, 1)
|
||||
|
@ -98,7 +98,7 @@ class GetStartedViews(TestCase):
|
|||
|
||||
self.assertFalse(self.local_user.shelfbook_set.exists())
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as delay_mock:
|
||||
view(request)
|
||||
self.assertEqual(delay_mock.call_count, 1)
|
||||
|
|
|
@ -123,7 +123,7 @@ class GoalViews(TestCase):
|
|||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
view(request, self.local_user.localname, self.year)
|
||||
|
||||
goal = models.AnnualGoal.objects.get()
|
||||
|
|
|
@ -10,7 +10,7 @@ from bookwyrm import models, views, forms
|
|||
from bookwyrm.tests.validate_html import validate_html
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
class GroupViews(TestCase):
|
||||
"""view group and edit details"""
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ class ViewsHelpers(TestCase):
|
|||
datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json")
|
||||
self.userdata = json.loads(datafile.read_bytes())
|
||||
del self.userdata["icon"]
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
self.shelf = models.Shelf.objects.create(
|
||||
name="Test Shelf", identifier="test-shelf", user=self.local_user
|
||||
)
|
||||
|
@ -166,7 +166,7 @@ class ViewsHelpers(TestCase):
|
|||
def test_handle_reading_status_to_read(self, *_):
|
||||
"""posts shelve activities"""
|
||||
shelf = self.local_user.shelf_set.get(identifier="to-read")
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.helpers.handle_reading_status(
|
||||
self.local_user, shelf, self.book, "public"
|
||||
)
|
||||
|
@ -178,7 +178,7 @@ class ViewsHelpers(TestCase):
|
|||
def test_handle_reading_status_reading(self, *_):
|
||||
"""posts shelve activities"""
|
||||
shelf = self.local_user.shelf_set.get(identifier="reading")
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.helpers.handle_reading_status(
|
||||
self.local_user, shelf, self.book, "public"
|
||||
)
|
||||
|
@ -190,7 +190,7 @@ class ViewsHelpers(TestCase):
|
|||
def test_handle_reading_status_read(self, *_):
|
||||
"""posts shelve activities"""
|
||||
shelf = self.local_user.shelf_set.get(identifier="read")
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.helpers.handle_reading_status(
|
||||
self.local_user, shelf, self.book, "public"
|
||||
)
|
||||
|
@ -201,7 +201,7 @@ class ViewsHelpers(TestCase):
|
|||
|
||||
def test_handle_reading_status_other(self, *_):
|
||||
"""posts shelve activities"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.helpers.handle_reading_status(
|
||||
self.local_user, self.shelf, self.book, "public"
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.test.client import RequestFactory
|
|||
from bookwyrm import models, views
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.remove_status_task.delay")
|
||||
class InteractionViews(TestCase):
|
||||
"""viewing and creating statuses"""
|
||||
|
@ -74,7 +74,7 @@ class InteractionViews(TestCase):
|
|||
self.assertEqual(models.Favorite.objects.count(), 1)
|
||||
self.assertEqual(models.Notification.objects.count(), 1)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
view(request, status.id)
|
||||
self.assertEqual(models.Favorite.objects.count(), 0)
|
||||
self.assertEqual(models.Notification.objects.count(), 0)
|
||||
|
@ -110,12 +110,12 @@ class InteractionViews(TestCase):
|
|||
status = models.Status.objects.create(user=self.local_user, content="hi")
|
||||
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as broadcast_mock:
|
||||
view(request, status.id)
|
||||
|
||||
self.assertEqual(broadcast_mock.call_count, 1)
|
||||
activity = json.loads(broadcast_mock.call_args[0][1])
|
||||
activity = json.loads(broadcast_mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Announce")
|
||||
|
||||
boost = models.Boost.objects.get()
|
||||
|
|
|
@ -61,7 +61,7 @@ class ListViews(TestCase):
|
|||
parent_work=work_four,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
self.list = models.List.objects.create(
|
||||
name="Test List", user=self.local_user
|
||||
)
|
||||
|
@ -73,7 +73,7 @@ class ListViews(TestCase):
|
|||
def test_lists_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.Lists.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.List.objects.create(name="Public list", user=self.local_user)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
|
@ -96,7 +96,7 @@ class ListViews(TestCase):
|
|||
def test_saved_lists_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.SavedLists.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
booklist = models.List.objects.create(
|
||||
name="Public list", user=self.local_user
|
||||
)
|
||||
|
@ -116,7 +116,7 @@ class ListViews(TestCase):
|
|||
def test_saved_lists_page_empty(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.SavedLists.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.List.objects.create(name="Public list", user=self.local_user)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
|
@ -153,11 +153,13 @@ class ListViews(TestCase):
|
|||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
result = view(request)
|
||||
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Create")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
|
||||
|
@ -172,7 +174,7 @@ class ListViews(TestCase):
|
|||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
|
@ -191,7 +193,7 @@ class ListViews(TestCase):
|
|||
def test_list_page_sorted(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
for (i, book) in enumerate([self.book, self.book_two, self.book_three]):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
|
@ -253,7 +255,7 @@ class ListViews(TestCase):
|
|||
def test_list_page_logged_out(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
|
@ -276,7 +278,7 @@ class ListViews(TestCase):
|
|||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
|
@ -320,11 +322,13 @@ class ListViews(TestCase):
|
|||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
result = view(request, self.list.id)
|
||||
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Update")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["object"]["id"], self.list.remote_id)
|
||||
|
@ -340,7 +344,7 @@ class ListViews(TestCase):
|
|||
def test_curate_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.Curate.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.List.objects.create(name="Public list", user=self.local_user)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
|
@ -360,7 +364,7 @@ class ListViews(TestCase):
|
|||
def test_user_lists_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.UserLists.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.List.objects.create(name="Public list", user=self.local_user)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
|
|
|
@ -61,7 +61,7 @@ class ListActionViews(TestCase):
|
|||
parent_work=work_four,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
self.list = models.List.objects.create(
|
||||
name="Test List", user=self.local_user
|
||||
)
|
||||
|
@ -71,7 +71,7 @@ class ListActionViews(TestCase):
|
|||
|
||||
def test_delete_list(self):
|
||||
"""delete an entire list"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
|
@ -88,9 +88,11 @@ class ListActionViews(TestCase):
|
|||
)
|
||||
request = self.factory.post("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.delete_list(request, self.list.id)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Delete")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["object"]["id"], self.list.remote_id)
|
||||
|
@ -110,7 +112,7 @@ class ListActionViews(TestCase):
|
|||
def test_curate_approve(self):
|
||||
"""approve a pending item"""
|
||||
view = views.Curate.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
pending = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
|
@ -128,11 +130,13 @@ class ListActionViews(TestCase):
|
|||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
view(request, self.list.id)
|
||||
|
||||
self.assertEqual(mock.call_count, 2)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
@ -145,7 +149,7 @@ class ListActionViews(TestCase):
|
|||
def test_curate_reject(self):
|
||||
"""approve a pending item"""
|
||||
view = views.Curate.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
pending = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
|
@ -179,10 +183,12 @@ class ListActionViews(TestCase):
|
|||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.list.add_book(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
@ -214,7 +220,7 @@ class ListActionViews(TestCase):
|
|||
},
|
||||
)
|
||||
request_two.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.list.add_book(request_one)
|
||||
views.list.add_book(request_two)
|
||||
|
||||
|
@ -256,7 +262,7 @@ class ListActionViews(TestCase):
|
|||
)
|
||||
request_three.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.list.add_book(request_one)
|
||||
views.list.add_book(request_two)
|
||||
views.list.add_book(request_three)
|
||||
|
@ -271,7 +277,7 @@ class ListActionViews(TestCase):
|
|||
|
||||
remove_request = self.factory.post("", {"item": items[1].id})
|
||||
remove_request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.list.remove_book(remove_request, self.list.id)
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
|
@ -293,7 +299,7 @@ class ListActionViews(TestCase):
|
|||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
|
@ -330,7 +336,7 @@ class ListActionViews(TestCase):
|
|||
its order should be at the end of the approved books and before the
|
||||
remaining pending books.
|
||||
"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
|
@ -370,7 +376,7 @@ class ListActionViews(TestCase):
|
|||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
view(request, self.list.id)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
|
@ -422,7 +428,7 @@ class ListActionViews(TestCase):
|
|||
)
|
||||
request_three.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.list.add_book(request_one)
|
||||
views.list.add_book(request_two)
|
||||
views.list.add_book(request_three)
|
||||
|
@ -437,7 +443,7 @@ class ListActionViews(TestCase):
|
|||
|
||||
set_position_request = self.factory.post("", {"position": 1})
|
||||
set_position_request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.list.set_book_position(set_position_request, items[2].id)
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book_three)
|
||||
|
@ -460,10 +466,12 @@ class ListActionViews(TestCase):
|
|||
)
|
||||
request.user = self.rat
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.list.add_book(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.rat.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
@ -486,11 +494,13 @@ class ListActionViews(TestCase):
|
|||
)
|
||||
request.user = self.rat
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.list.add_book(request)
|
||||
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.rat.remote_id)
|
||||
|
@ -516,10 +526,12 @@ class ListActionViews(TestCase):
|
|||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as mock:
|
||||
views.list.add_book(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
activity = json.loads(mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
@ -532,7 +544,7 @@ class ListActionViews(TestCase):
|
|||
def test_remove_book(self):
|
||||
"""take an item off a list"""
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
item = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
|
@ -549,13 +561,13 @@ class ListActionViews(TestCase):
|
|||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.list.remove_book(request, self.list.id)
|
||||
self.assertFalse(self.list.listitem_set.exists())
|
||||
|
||||
def test_remove_book_unauthorized(self):
|
||||
"""take an item off a list"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
item = models.ListItem.objects.create(
|
||||
book_list=self.list, user=self.local_user, book=self.book, order=1
|
||||
)
|
||||
|
|
|
@ -25,7 +25,7 @@ class NotificationViews(TestCase):
|
|||
local=True,
|
||||
localname="mouse",
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
self.status = models.Status.objects.create(
|
||||
content="hi",
|
||||
user=self.local_user,
|
||||
|
|
|
@ -11,7 +11,7 @@ from bookwyrm.settings import USER_AGENT
|
|||
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
class OutboxView(TestCase):
|
||||
"""sends out activities"""
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ class ReadingViews(TestCase):
|
|||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.ReadingStatus.as_view()(request, "start", self.book.id)
|
||||
|
||||
self.assertEqual(shelf.books.get(), self.book)
|
||||
|
@ -100,7 +100,7 @@ class ReadingViews(TestCase):
|
|||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.ReadingStatus.as_view()(request, "start", self.book.id)
|
||||
|
||||
self.assertEqual(shelf.books.get(), self.book)
|
||||
|
@ -124,7 +124,7 @@ class ReadingViews(TestCase):
|
|||
def test_start_reading_reshelve(self, *_):
|
||||
"""begin a book"""
|
||||
to_read_shelf = self.local_user.shelf_set.get(identifier=models.Shelf.TO_READ)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
models.ShelfBook.objects.create(
|
||||
shelf=to_read_shelf, book=self.book, user=self.local_user
|
||||
)
|
||||
|
@ -135,7 +135,7 @@ class ReadingViews(TestCase):
|
|||
|
||||
request = self.factory.post("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.ReadingStatus.as_view()(request, "start", self.book.id)
|
||||
|
||||
self.assertFalse(to_read_shelf.books.exists())
|
||||
|
@ -162,7 +162,7 @@ class ReadingViews(TestCase):
|
|||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.ReadingStatus.as_view()(request, "finish", self.book.id)
|
||||
|
||||
self.assertEqual(shelf.books.get(), self.book)
|
||||
|
@ -267,7 +267,7 @@ class ReadingViews(TestCase):
|
|||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
views.update_progress(request, self.book.id)
|
||||
|
||||
status = models.Comment.objects.get()
|
||||
|
|
|
@ -9,7 +9,7 @@ from bookwyrm import models
|
|||
|
||||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
|
||||
@patch("bookwyrm.activitystreams.remove_book_statuses_task.delay")
|
||||
class ReadThrough(TestCase):
|
||||
|
@ -32,7 +32,7 @@ class ReadThrough(TestCase):
|
|||
"cinco", "cinco@example.com", "seissiete", local=True, localname="cinco"
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
self.client.force_login(self.user)
|
||||
|
||||
@patch("bookwyrm.activitystreams.remove_user_statuses_task.delay")
|
||||
|
|
|
@ -6,7 +6,7 @@ from bookwyrm import models
|
|||
from bookwyrm.views import rss_feed
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream")
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
class RssFeedView(TestCase):
|
||||
|
|
|
@ -139,7 +139,7 @@ class Views(TestCase):
|
|||
|
||||
def test_search_lists(self):
|
||||
"""searches remote connectors"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
|
||||
booklist = models.List.objects.create(
|
||||
user=self.local_user, name="test list"
|
||||
)
|
||||
|
|
|
@ -14,7 +14,7 @@ from bookwyrm.tests.validate_html import validate_html
|
|||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
|
||||
@patch("bookwyrm.activitystreams.remove_status_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
class StatusViews(TestCase):
|
||||
"""viewing and creating statuses"""
|
||||
|
||||
|
@ -310,7 +310,7 @@ http://www.fish.com/"""
|
|||
with patch("bookwyrm.activitystreams.remove_status_task.delay") as redis_mock:
|
||||
view(request, status.id)
|
||||
self.assertTrue(redis_mock.called)
|
||||
activity = json.loads(mock.call_args_list[1][0][1])
|
||||
activity = json.loads(mock.call_args_list[1][1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Delete")
|
||||
self.assertEqual(activity["object"]["type"], "Tombstone")
|
||||
status.refresh_from_db()
|
||||
|
@ -344,7 +344,7 @@ http://www.fish.com/"""
|
|||
with patch("bookwyrm.activitystreams.remove_status_task.delay") as redis_mock:
|
||||
view(request, status.id)
|
||||
self.assertTrue(redis_mock.called)
|
||||
activity = json.loads(mock.call_args_list[1][0][1])
|
||||
activity = json.loads(mock.call_args_list[1][1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Delete")
|
||||
self.assertEqual(activity["object"]["type"], "Tombstone")
|
||||
status.refresh_from_db()
|
||||
|
@ -396,7 +396,7 @@ http://www.fish.com/"""
|
|||
request.user = self.local_user
|
||||
|
||||
view(request, "comment", existing_status_id=status.id)
|
||||
activity = json.loads(mock.call_args_list[1][0][1])
|
||||
activity = json.loads(mock.call_args_list[1][1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Update")
|
||||
self.assertEqual(activity["object"]["id"], status.remote_id)
|
||||
|
||||
|
|
|
@ -34,9 +34,11 @@ class UserViews(TestCase):
|
|||
self.book = models.Edition.objects.create(
|
||||
title="test", parent_work=models.Work.objects.create(title="test work")
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"), patch(
|
||||
"bookwyrm.suggested_users.rerank_suggestions_task.delay"
|
||||
), patch("bookwyrm.activitystreams.add_book_statuses_task.delay"):
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
), patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.add_book_statuses_task.delay"
|
||||
):
|
||||
models.ShelfBook.objects.create(
|
||||
book=self.book,
|
||||
user=self.local_user,
|
||||
|
|
|
@ -237,7 +237,41 @@ urlpatterns = [
|
|||
re_path(r"^search/?$", views.Search.as_view(), name="search"),
|
||||
# imports
|
||||
re_path(r"^import/?$", views.Import.as_view(), name="import"),
|
||||
re_path(r"^import/(\d+)/?$", views.ImportStatus.as_view(), name="import-status"),
|
||||
re_path(
|
||||
r"^import/(?P<job_id>\d+)/?$",
|
||||
views.ImportStatus.as_view(),
|
||||
name="import-status",
|
||||
),
|
||||
re_path(
|
||||
r"^import/(?P<job_id>\d+)/retry/(?P<item_id>\d+)/?$",
|
||||
views.retry_item,
|
||||
name="import-item-retry",
|
||||
),
|
||||
re_path(
|
||||
r"^import/(?P<job_id>\d+)/failed/?$",
|
||||
views.ImportTroubleshoot.as_view(),
|
||||
name="import-troubleshoot",
|
||||
),
|
||||
re_path(
|
||||
r"^import/(?P<job_id>\d+)/review/?$",
|
||||
views.ImportManualReview.as_view(),
|
||||
name="import-review",
|
||||
),
|
||||
re_path(
|
||||
r"^import/(?P<job_id>\d+)/review/?$",
|
||||
views.ImportManualReview.as_view(),
|
||||
name="import-review",
|
||||
),
|
||||
re_path(
|
||||
r"^import/(?P<job_id>\d+)/review/(?P<item_id>\d+)/approve/?$",
|
||||
views.approve_import_item,
|
||||
name="import-approve",
|
||||
),
|
||||
re_path(
|
||||
r"^import/(?P<job_id>\d+)/review/(?P<item_id>\d+)/delete/?$",
|
||||
views.delete_import_item,
|
||||
name="import-delete",
|
||||
),
|
||||
# users
|
||||
re_path(rf"{USER_PATH}\.json$", views.User.as_view()),
|
||||
re_path(rf"{USER_PATH}/?$", views.User.as_view(), name="user-feed"),
|
||||
|
|
|
@ -43,6 +43,16 @@ from .shelf.shelf import Shelf
|
|||
from .shelf.shelf_actions import create_shelf, delete_shelf
|
||||
from .shelf.shelf_actions import shelve, unshelve
|
||||
|
||||
# csv import
|
||||
from .imports.import_data import Import
|
||||
from .imports.import_status import ImportStatus, retry_item
|
||||
from .imports.troubleshoot import ImportTroubleshoot
|
||||
from .imports.manually_review import (
|
||||
ImportManualReview,
|
||||
approve_import_item,
|
||||
delete_import_item,
|
||||
)
|
||||
|
||||
# misc views
|
||||
from .author import Author, EditAuthor
|
||||
from .directory import Directory
|
||||
|
@ -62,7 +72,6 @@ from .group import (
|
|||
accept_membership,
|
||||
reject_membership,
|
||||
)
|
||||
from .import_data import Import, ImportStatus
|
||||
from .inbox import Inbox
|
||||
from .interaction import Favorite, Unfavorite, Boost, Unboost
|
||||
from .isbn import Isbn
|
||||
|
|
0
bookwyrm/views/imports/__init__.py
Normal file
0
bookwyrm/views/imports/__init__.py
Normal file
|
@ -2,9 +2,8 @@
|
|||
from io import TextIOWrapper
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.shortcuts import redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
@ -12,12 +11,10 @@ from django.views import View
|
|||
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm.importers import (
|
||||
Importer,
|
||||
LibrarythingImporter,
|
||||
GoodreadsImporter,
|
||||
StorygraphImporter,
|
||||
)
|
||||
from bookwyrm.tasks import app
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
|
@ -70,46 +67,3 @@ class Import(View):
|
|||
|
||||
return redirect(f"/import/{job.id}")
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class ImportStatus(View):
|
||||
"""status of an existing import"""
|
||||
|
||||
def get(self, request, job_id):
|
||||
"""status of an import job"""
|
||||
job = get_object_or_404(models.ImportJob, id=job_id)
|
||||
if job.user != request.user:
|
||||
raise PermissionDenied()
|
||||
|
||||
try:
|
||||
task = app.AsyncResult(job.task_id)
|
||||
# triggers attribute error if the task won't load
|
||||
task.status # pylint: disable=pointless-statement
|
||||
except (ValueError, AttributeError):
|
||||
task = None
|
||||
|
||||
items = job.items.order_by("index").all()
|
||||
failed_items = [i for i in items if i.fail_reason]
|
||||
items = [i for i in items if not i.fail_reason]
|
||||
return TemplateResponse(
|
||||
request,
|
||||
"import/import_status.html",
|
||||
{"job": job, "items": items, "failed_items": failed_items, "task": task},
|
||||
)
|
||||
|
||||
def post(self, request, job_id):
|
||||
"""retry lines from an import"""
|
||||
job = get_object_or_404(models.ImportJob, id=job_id)
|
||||
items = []
|
||||
for item in request.POST.getlist("import_item"):
|
||||
items.append(get_object_or_404(models.ImportItem, id=item))
|
||||
|
||||
importer = Importer()
|
||||
job = importer.create_retry_job(
|
||||
request.user,
|
||||
job,
|
||||
items,
|
||||
)
|
||||
importer.start_import(job)
|
||||
return redirect(f"/import/{job.id}")
|
79
bookwyrm/views/imports/import_status.py
Normal file
79
bookwyrm/views/imports/import_status.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
""" import books from another app """
|
||||
import math
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.paginator import Paginator
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.importers import GoodreadsImporter
|
||||
from bookwyrm.importers.importer import import_item_task
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class ImportStatus(View):
|
||||
"""status of an existing import"""
|
||||
|
||||
def get(self, request, job_id):
|
||||
"""status of an import job"""
|
||||
job = get_object_or_404(models.ImportJob, id=job_id)
|
||||
if job.user != request.user:
|
||||
raise PermissionDenied()
|
||||
|
||||
items = job.items.order_by("index")
|
||||
item_count = items.count() or 1
|
||||
|
||||
paginated = Paginator(items, PAGE_LENGTH)
|
||||
page = paginated.get_page(request.GET.get("page"))
|
||||
manual_review_count = items.filter(
|
||||
fail_reason__isnull=False, book_guess__isnull=False, book__isnull=True
|
||||
).count()
|
||||
fail_count = items.filter(
|
||||
fail_reason__isnull=False, book_guess__isnull=True
|
||||
).count()
|
||||
pending_item_count = job.pending_items.count()
|
||||
data = {
|
||||
"job": job,
|
||||
"items": page,
|
||||
"manual_review_count": manual_review_count,
|
||||
"fail_count": fail_count,
|
||||
"page_range": paginated.get_elided_page_range(
|
||||
page.number, on_each_side=2, on_ends=1
|
||||
),
|
||||
"item_count": item_count,
|
||||
"complete_count": item_count - pending_item_count,
|
||||
"percent": math.floor( # pylint: disable=c-extension-no-member
|
||||
(item_count - pending_item_count) / item_count * 100
|
||||
),
|
||||
# hours since last import item update
|
||||
"inactive_time": (job.updated_date - timezone.now()).seconds / 60 / 60,
|
||||
"legacy": not job.mappings,
|
||||
}
|
||||
|
||||
return TemplateResponse(request, "import/import_status.html", data)
|
||||
|
||||
def post(self, request, job_id):
|
||||
"""bring a legacy import into the latest format"""
|
||||
job = get_object_or_404(models.ImportJob, id=job_id)
|
||||
if job.user != request.user:
|
||||
raise PermissionDenied()
|
||||
GoodreadsImporter().update_legacy_job(job)
|
||||
return redirect("import-status", job_id)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def retry_item(request, job_id, item_id):
|
||||
"""retry an item"""
|
||||
item = get_object_or_404(
|
||||
models.ImportItem, id=item_id, job__id=job_id, job__user=request.user
|
||||
)
|
||||
import_item_task.delay(item.id)
|
||||
return redirect("import-status", job_id)
|
72
bookwyrm/views/imports/manually_review.py
Normal file
72
bookwyrm/views/imports/manually_review.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
""" verify books we're unsure about """
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.paginator import Paginator
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.importers.importer import import_item_task
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class ImportManualReview(View):
|
||||
"""problems items in an existing import"""
|
||||
|
||||
def get(self, request, job_id):
|
||||
"""status of an import job"""
|
||||
job = get_object_or_404(models.ImportJob, id=job_id)
|
||||
if job.user != request.user:
|
||||
raise PermissionDenied()
|
||||
|
||||
items = job.items.order_by("index").filter(
|
||||
book__isnull=True, book_guess__isnull=False
|
||||
)
|
||||
|
||||
paginated = Paginator(items, PAGE_LENGTH)
|
||||
page = paginated.get_page(request.GET.get("page"))
|
||||
data = {
|
||||
"job": job,
|
||||
"items": page,
|
||||
"page_range": paginated.get_elided_page_range(
|
||||
page.number, on_each_side=2, on_ends=1
|
||||
),
|
||||
"complete": True,
|
||||
}
|
||||
|
||||
return TemplateResponse(request, "import/manual_review.html", data)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
# pylint: disable=unused-argument
|
||||
def approve_import_item(request, job_id, item_id):
|
||||
"""we guessed right"""
|
||||
item = get_object_or_404(
|
||||
models.ImportItem, id=item_id, job__id=job_id, book_guess__isnull=False
|
||||
)
|
||||
item.fail_reason = None
|
||||
item.book = item.book_guess
|
||||
item.book_guess = None
|
||||
item.save()
|
||||
|
||||
# the good stuff - actually import the data
|
||||
import_item_task.delay(item.id)
|
||||
return redirect("import-review", job_id)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
# pylint: disable=unused-argument
|
||||
def delete_import_item(request, job_id, item_id):
|
||||
"""we guessed right"""
|
||||
item = get_object_or_404(
|
||||
models.ImportItem, id=item_id, job__id=job_id, book_guess__isnull=False
|
||||
)
|
||||
item.book_guess = None
|
||||
item.save()
|
||||
return redirect("import-review", job_id)
|
54
bookwyrm/views/imports/troubleshoot.py
Normal file
54
bookwyrm/views/imports/troubleshoot.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
""" import books from another app """
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.paginator import Paginator
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.importers import Importer
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class ImportTroubleshoot(View):
|
||||
"""problems items in an existing import"""
|
||||
|
||||
def get(self, request, job_id):
|
||||
"""status of an import job"""
|
||||
job = get_object_or_404(models.ImportJob, id=job_id)
|
||||
if job.user != request.user:
|
||||
raise PermissionDenied()
|
||||
|
||||
items = job.items.order_by("index").filter(
|
||||
fail_reason__isnull=False, book_guess__isnull=True
|
||||
)
|
||||
|
||||
paginated = Paginator(items, PAGE_LENGTH)
|
||||
page = paginated.get_page(request.GET.get("page"))
|
||||
data = {
|
||||
"job": job,
|
||||
"items": page,
|
||||
"page_range": paginated.get_elided_page_range(
|
||||
page.number, on_each_side=2, on_ends=1
|
||||
),
|
||||
"complete": True,
|
||||
}
|
||||
|
||||
return TemplateResponse(request, "import/troubleshoot.html", data)
|
||||
|
||||
def post(self, request, job_id):
|
||||
"""retry lines from an import"""
|
||||
job = get_object_or_404(models.ImportJob, id=job_id)
|
||||
items = job.items.filter(fail_reason__isnull=False)
|
||||
|
||||
importer = Importer()
|
||||
job = importer.create_retry_job(
|
||||
request.user,
|
||||
job,
|
||||
items,
|
||||
)
|
||||
importer.start_import(job)
|
||||
return redirect(f"/import/{job.id}")
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-10-24 14:09+0000\n"
|
||||
"POT-Creation-Date: 2021-11-14 15:08+0000\n"
|
||||
"PO-Revision-Date: 2021-02-28 17:19-0800\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: English <LL@li.org>\n"
|
||||
|
@ -73,15 +73,16 @@ msgstr ""
|
|||
msgid "Descending"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/importers/importer.py:75
|
||||
#: bookwyrm/importers/importer.py:127
|
||||
msgid "Error loading book"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/importers/importer.py:88
|
||||
#: bookwyrm/importers/importer.py:135
|
||||
msgid "Could not find a match for book"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/models/base_model.py:17
|
||||
#: bookwyrm/templates/import/import_status.html:171
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
|
@ -101,23 +102,23 @@ msgstr ""
|
|||
msgid "Domain block"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/models/book.py:232
|
||||
#: bookwyrm/models/book.py:233
|
||||
msgid "Audiobook"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/models/book.py:233
|
||||
#: bookwyrm/models/book.py:234
|
||||
msgid "eBook"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/models/book.py:234
|
||||
#: bookwyrm/models/book.py:235
|
||||
msgid "Graphic novel"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/models/book.py:235
|
||||
#: bookwyrm/models/book.py:236
|
||||
msgid "Hardcover"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/models/book.py:236
|
||||
#: bookwyrm/models/book.py:237
|
||||
msgid "Paperback"
|
||||
msgstr ""
|
||||
|
||||
|
@ -134,21 +135,21 @@ msgstr ""
|
|||
msgid "Blocked"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/models/fields.py:27
|
||||
#: bookwyrm/models/fields.py:29
|
||||
#, python-format
|
||||
msgid "%(value)s is not a valid remote_id"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/models/fields.py:36 bookwyrm/models/fields.py:45
|
||||
#: bookwyrm/models/fields.py:38 bookwyrm/models/fields.py:47
|
||||
#, python-format
|
||||
msgid "%(value)s is not a valid username"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/models/fields.py:181 bookwyrm/templates/layout.html:171
|
||||
#: bookwyrm/models/fields.py:183 bookwyrm/templates/layout.html:171
|
||||
msgid "username"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/models/fields.py:186
|
||||
#: bookwyrm/models/fields.py:188
|
||||
msgid "A user with that username already exists."
|
||||
msgstr ""
|
||||
|
||||
|
@ -893,22 +894,37 @@ msgstr ""
|
|||
msgid "All known users"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/discover/card-header.html:9
|
||||
#: bookwyrm/templates/discover/card-header.html:8
|
||||
#, python-format
|
||||
msgid "<a href=\"%(user_path)s\">%(username)s</a> rated <a href=\"%(book_path)s\">%(book_title)s</a>"
|
||||
msgid "<a href=\"%(user_path)s\">%(username)s</a> wants to read <a href=\"%(book_path)s\">%(book_title)s</a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/discover/card-header.html:13
|
||||
#, python-format
|
||||
msgid "<a href=\"%(user_path)s\">%(username)s</a> finished reading <a href=\"%(book_path)s\">%(book_title)s</a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/discover/card-header.html:18
|
||||
#, python-format
|
||||
msgid "<a href=\"%(user_path)s\">%(username)s</a> started reading <a href=\"%(book_path)s\">%(book_title)s</a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/discover/card-header.html:23
|
||||
#, python-format
|
||||
msgid "<a href=\"%(user_path)s\">%(username)s</a> rated <a href=\"%(book_path)s\">%(book_title)s</a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/discover/card-header.html:27
|
||||
#, python-format
|
||||
msgid "<a href=\"%(user_path)s\">%(username)s</a> reviewed <a href=\"%(book_path)s\">%(book_title)s</a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/discover/card-header.html:17
|
||||
#: bookwyrm/templates/discover/card-header.html:31
|
||||
#, python-format
|
||||
msgid "<a href=\"%(user_path)s\">%(username)s</a> commented on <a href=\"%(book_path)s\">%(book_title)s</a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/discover/card-header.html:21
|
||||
#: bookwyrm/templates/discover/card-header.html:35
|
||||
#, python-format
|
||||
msgid "<a href=\"%(user_path)s\">%(username)s</a> quoted <a href=\"%(book_path)s\">%(book_title)s</a>"
|
||||
msgstr ""
|
||||
|
@ -1059,9 +1075,8 @@ msgstr ""
|
|||
msgid "Updates"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/feed/layout.html:12
|
||||
#: bookwyrm/templates/user/books_header.html:3
|
||||
msgid "Your books"
|
||||
#: bookwyrm/templates/feed/layout.html:12 bookwyrm/templates/layout.html:106
|
||||
msgid "Your Books"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/feed/layout.html:14
|
||||
|
@ -1070,11 +1085,13 @@ msgstr ""
|
|||
|
||||
#: bookwyrm/templates/feed/layout.html:25
|
||||
#: bookwyrm/templates/shelf/shelf.html:38
|
||||
#: bookwyrm/templates/user/books_header.html:4
|
||||
msgid "To Read"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/feed/layout.html:26
|
||||
#: bookwyrm/templates/shelf/shelf.html:40
|
||||
#: bookwyrm/templates/user/books_header.html:6
|
||||
msgid "Currently Reading"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1082,6 +1099,7 @@ msgstr ""
|
|||
#: bookwyrm/templates/shelf/shelf.html:42
|
||||
#: bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown_options.html:23
|
||||
#: bookwyrm/templates/snippets/shelve_button/shelve_button_options.html:12
|
||||
#: bookwyrm/templates/user/books_header.html:8
|
||||
msgid "Read"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1367,88 +1385,161 @@ msgid "No recent imports"
|
|||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:6
|
||||
#: bookwyrm/templates/import/import_status.html:10
|
||||
#: bookwyrm/templates/import/import_status.html:15
|
||||
#: bookwyrm/templates/import/import_status.html:29
|
||||
msgid "Import Status"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:11
|
||||
msgid "Back to imports"
|
||||
#: bookwyrm/templates/import/import_status.html:13
|
||||
#: bookwyrm/templates/import/import_status.html:27
|
||||
msgid "Retry Status"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:15
|
||||
#: bookwyrm/templates/import/import_status.html:22
|
||||
msgid "Imports"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:39
|
||||
msgid "Import started:"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:20
|
||||
msgid "Import completed:"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:24
|
||||
msgid "TASK FAILED"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:32
|
||||
msgid "Import still in progress."
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:34
|
||||
msgid "(Hit reload to update!)"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:41
|
||||
msgid "Failed to load"
|
||||
#: bookwyrm/templates/import/import_status.html:48
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:50
|
||||
#, python-format
|
||||
msgid "Jump to the bottom of the list to select the %(failed_count)s items which failed to import."
|
||||
msgid "Refresh"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:62
|
||||
#, python-format
|
||||
msgid "Line %(index)s: <strong>%(title)s</strong> by %(author)s"
|
||||
msgid "%(display_counter)s item needs manual approval."
|
||||
msgid_plural "%(display_counter)s items need manual approval."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:67
|
||||
#: bookwyrm/templates/import/manual_review.html:8
|
||||
msgid "Review items"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:82
|
||||
msgid "Select all"
|
||||
#: bookwyrm/templates/import/import_status.html:73
|
||||
#, python-format
|
||||
msgid "%(display_counter)s item failed to import."
|
||||
msgid_plural "%(display_counter)s items failed to import."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:79
|
||||
msgid "View and troubleshoot failed items"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:85
|
||||
msgid "Retry items"
|
||||
#: bookwyrm/templates/import/import_status.html:91
|
||||
msgid "Row"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:112
|
||||
msgid "Successfully imported"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:114
|
||||
msgid "Import Progress"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:119
|
||||
msgid "Book"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:122
|
||||
#: bookwyrm/templates/import/import_status.html:94
|
||||
#: bookwyrm/templates/shelf/shelf.html:141
|
||||
#: bookwyrm/templates/shelf/shelf.html:163
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:125
|
||||
#: bookwyrm/templates/import/import_status.html:97
|
||||
msgid "ISBN"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:100
|
||||
#: bookwyrm/templates/shelf/shelf.html:142
|
||||
#: bookwyrm/templates/shelf/shelf.html:166
|
||||
msgid "Author"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:148
|
||||
#: bookwyrm/templates/import/import_status.html:103
|
||||
msgid "Shelf"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:106
|
||||
#: bookwyrm/templates/import/manual_review.html:13
|
||||
#: bookwyrm/templates/snippets/create_status.html:17
|
||||
msgid "Review"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:110
|
||||
msgid "Book"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:113
|
||||
#: bookwyrm/templates/settings/announcements/announcements.html:38
|
||||
#: bookwyrm/templates/settings/federation/instance_list.html:46
|
||||
#: bookwyrm/templates/settings/invites/manage_invite_requests.html:44
|
||||
#: bookwyrm/templates/settings/invites/status_filter.html:5
|
||||
#: bookwyrm/templates/settings/users/user_admin.html:34
|
||||
#: bookwyrm/templates/settings/users/user_info.html:20
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:144
|
||||
msgid "View imported review"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:158
|
||||
msgid "Imported"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/import_status.html:164
|
||||
msgid "Needs manual review"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/manual_review.html:5
|
||||
#: bookwyrm/templates/import/troubleshoot.html:4
|
||||
msgid "Import Troubleshooting"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/manual_review.html:21
|
||||
msgid "Approving a suggestion will permanently add the suggested book to your shelves and associate your reading dates, reviews, and ratings with that book."
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/manual_review.html:56
|
||||
#: bookwyrm/templates/lists/curate.html:57
|
||||
msgid "Approve"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/manual_review.html:64
|
||||
msgid "Reject"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/tooltip.html:6
|
||||
msgid "You can download your Goodreads data from the <a href=\"https://www.goodreads.com/review/import\" target=\"_blank\" rel=\"noopener\">Import/Export page</a> of your Goodreads account."
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/troubleshoot.html:7
|
||||
msgid "Failed items"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/troubleshoot.html:12
|
||||
msgid "Troubleshooting"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/troubleshoot.html:20
|
||||
msgid "Re-trying an import can fix missing items in cases such as:"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/troubleshoot.html:23
|
||||
msgid "The book has been added to the instance since this import"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/troubleshoot.html:24
|
||||
msgid "A transient error or timeout caused the external data source to be unavailable."
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/troubleshoot.html:25
|
||||
msgid "BookWyrm has been updated since this import with a bug fix"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/import/troubleshoot.html:28
|
||||
msgid "Contact your admin or <a href='https://github.com/bookwyrm-social/bookwyrm/issues'>open an issue</a> if you are seeing unexpected failed items."
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/landing/about.html:7 bookwyrm/templates/layout.html:230
|
||||
#, python-format
|
||||
msgid "About %(site_name)s"
|
||||
|
@ -1580,10 +1671,6 @@ msgstr ""
|
|||
msgid "Feed"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/layout.html:106
|
||||
msgid "Your Books"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/layout.html:116
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
@ -1683,10 +1770,6 @@ msgstr ""
|
|||
msgid "Suggested by"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/lists/curate.html:57
|
||||
msgid "Approve"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/lists/curate.html:63
|
||||
msgid "Discard"
|
||||
msgstr ""
|
||||
|
@ -2239,15 +2322,6 @@ msgstr ""
|
|||
msgid "End date"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/settings/announcements/announcements.html:38
|
||||
#: bookwyrm/templates/settings/federation/instance_list.html:46
|
||||
#: bookwyrm/templates/settings/invites/manage_invite_requests.html:44
|
||||
#: bookwyrm/templates/settings/invites/status_filter.html:5
|
||||
#: bookwyrm/templates/settings/users/user_admin.html:34
|
||||
#: bookwyrm/templates/settings/users/user_info.html:20
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/settings/announcements/announcements.html:48
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
@ -3096,10 +3170,6 @@ msgstr ""
|
|||
msgid "Un-boost"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/snippets/create_status.html:17
|
||||
msgid "Review"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/snippets/create_status.html:39
|
||||
msgid "Quote"
|
||||
msgstr ""
|
||||
|
@ -3526,7 +3596,7 @@ msgstr ""
|
|||
msgid "commented on <a href=\"%(book_path)s\">%(book)s</a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/snippets/status/headers/note.html:15
|
||||
#: bookwyrm/templates/snippets/status/headers/note.html:8
|
||||
#, python-format
|
||||
msgid "replied to <a href=\"%(user_path)s\">%(username)s</a>'s <a href=\"%(status_path)s\">status</a>"
|
||||
msgstr ""
|
||||
|
@ -3605,7 +3675,11 @@ msgstr ""
|
|||
msgid "Show less"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/user/books_header.html:5
|
||||
#: bookwyrm/templates/user/books_header.html:10
|
||||
msgid "Your books"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/user/books_header.html:15
|
||||
#, python-format
|
||||
msgid "%(username)s's books"
|
||||
msgstr ""
|
||||
|
@ -3749,7 +3823,7 @@ msgstr ""
|
|||
msgid "%(title)s: %(subtitle)s"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/views/import_data.py:67
|
||||
#: bookwyrm/views/imports/import_data.py:64
|
||||
msgid "Not a valid csv file"
|
||||
msgstr ""
|
||||
|
||||
|
|
Loading…
Reference in a new issue