Allow users to set privacy on imported reviews

or not import them at all. Fixes #252
This commit is contained in:
Mouse Reeve 2020-10-30 11:21:02 -07:00
parent 45f39fab48
commit 0b0de12968
8 changed files with 77 additions and 18 deletions

View file

@ -11,9 +11,13 @@ from bookwyrm.status import create_notification
MAX_ENTRIES = 500 MAX_ENTRIES = 500
def create_job(user, csv_file): def create_job(user, csv_file, include_reviews, privacy):
''' check over a csv and creates a database entry for the job''' ''' check over a csv and creates a database entry for the job'''
job = ImportJob.objects.create(user=user) job = ImportJob.objects.create(
user=user,
include_reviews=include_reviews,
privacy=privacy
)
for index, entry in enumerate(list(csv.DictReader(csv_file))[:MAX_ENTRIES]): for index, entry in enumerate(list(csv.DictReader(csv_file))[:MAX_ENTRIES]):
if not all(x in entry for x in ('ISBN13', 'Title', 'Author')): if not all(x in entry for x in ('ISBN13', 'Title', 'Author')):
raise ValueError("Author, title, and isbn must be in data.") raise ValueError("Author, title, and isbn must be in data.")
@ -42,8 +46,10 @@ def import_data(job_id):
if item.book: if item.book:
item.save() item.save()
results.append(item) results.append(item)
# shelves book and handles reviews
outgoing.handle_imported_book(job.user, item) if job.include_reviews:
# shelves book and handles reviews
outgoing.handle_imported_book(job.user, item, job.privacy)
else: else:
item.fail_reason = "Could not find a match for book" item.fail_reason = "Could not find a match for book"
item.save() item.save()

View file

@ -0,0 +1,23 @@
# Generated by Django 3.0.7 on 2020-10-30 17:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bookwyrm', '0058_remove_importjob_import_status'),
]
operations = [
migrations.AddField(
model_name='importjob',
name='include_reviews',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='importjob',
name='privacy',
field=models.CharField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255),
),
]

View file

@ -14,6 +14,14 @@ from django.dispatch import receiver
from bookwyrm import activitypub from bookwyrm import activitypub
from bookwyrm.settings import DOMAIN from bookwyrm.settings import DOMAIN
PrivacyLevels = models.TextChoices('Privacy', [
'public',
'unlisted',
'followers',
'direct'
])
class BookWyrmModel(models.Model): class BookWyrmModel(models.Model):
''' shared fields ''' ''' shared fields '''
created_date = models.DateTimeField(auto_now_add=True) created_date = models.DateTimeField(auto_now_add=True)

View file

@ -9,6 +9,8 @@ from bookwyrm import books_manager
from bookwyrm.connectors import ConnectorException from bookwyrm.connectors import ConnectorException
from bookwyrm.models import ReadThrough, User, Book from bookwyrm.models import ReadThrough, User, Book
from bookwyrm.utils.fields import JSONField from bookwyrm.utils.fields import JSONField
from .base_model import PrivacyLevels
# Mapping goodreads -> bookwyrm shelf titles. # Mapping goodreads -> bookwyrm shelf titles.
GOODREADS_SHELVES = { GOODREADS_SHELVES = {
@ -40,6 +42,12 @@ class ImportJob(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
created_date = models.DateTimeField(default=timezone.now) created_date = models.DateTimeField(default=timezone.now)
task_id = models.CharField(max_length=100, null=True) task_id = models.CharField(max_length=100, null=True)
include_reviews = models.BooleanField(default=True)
privacy = models.CharField(
max_length=255,
default='public',
choices=PrivacyLevels.choices
)
class ImportItem(models.Model): class ImportItem(models.Model):

View file

@ -7,16 +7,9 @@ from model_utils.managers import InheritanceManager
from bookwyrm import activitypub from bookwyrm import activitypub
from .base_model import ActivitypubMixin, OrderedCollectionPageMixin from .base_model import ActivitypubMixin, OrderedCollectionPageMixin
from .base_model import ActivityMapping, BookWyrmModel from .base_model import ActivityMapping, BookWyrmModel, PrivacyLevels
PrivacyLevels = models.TextChoices('Privacy', [
'public',
'unlisted',
'followers',
'direct'
])
class Status(OrderedCollectionPageMixin, BookWyrmModel): class Status(OrderedCollectionPageMixin, BookWyrmModel):
''' any post, like a reply to a review, etc ''' ''' any post, like a reply to a review, etc '''
user = models.ForeignKey('User', on_delete=models.PROTECT) user = models.ForeignKey('User', on_delete=models.PROTECT)

View file

@ -155,7 +155,7 @@ def handle_unshelve(user, book, shelf):
broadcast(user, activity) broadcast(user, activity)
def handle_imported_book(user, item): def handle_imported_book(user, item, privacy):
''' process a goodreads csv and then post about it ''' ''' process a goodreads csv and then post about it '''
if isinstance(item.book, models.Work): if isinstance(item.book, models.Work):
item.book = item.book.default_edition item.book = item.book.default_edition
@ -171,7 +171,7 @@ def handle_imported_book(user, item):
shelf_book, created = models.ShelfBook.objects.get_or_create( shelf_book, created = models.ShelfBook.objects.get_or_create(
book=item.book, shelf=desired_shelf, added_by=user) book=item.book, shelf=desired_shelf, added_by=user)
if created: if created:
broadcast(user, shelf_book.to_add_activity(user)) broadcast(user, shelf_book.to_add_activity(user), privacy=privacy)
# only add new read-throughs if the item isn't already shelved # only add new read-throughs if the item isn't already shelved
for read in item.reads: for read in item.reads:
@ -194,10 +194,11 @@ def handle_imported_book(user, item):
content=item.review, content=item.review,
rating=item.rating, rating=item.rating,
published_date=published_date_guess, published_date=published_date_guess,
privacy=privacy,
) )
# we don't need to send out pure activities because non-bookwyrm # we don't need to send out pure activities because non-bookwyrm
# instances don't need this data # instances don't need this data
broadcast(user, review.to_create_activity(user)) broadcast(user, review.to_create_activity(user), privacy=privacy)
def handle_delete_status(user, status): def handle_delete_status(user, status):

View file

@ -5,8 +5,24 @@
<h2 class="title">Import Books from GoodReads</h2> <h2 class="title">Import Books from GoodReads</h2>
<form name="import" action="/import_data/" method="post" enctype="multipart/form-data"> <form name="import" action="/import_data/" method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{{ import_form.as_p }} <div class="field">
<button class="button" type="submit">Import</button> {{ import_form.as_p }}
</div>
<div class="field">
<label class="label" for="include_reviews"><input type="checkbox" name="include_reviews" checked> Include reviews</label>
</div>
<div class="field">
<label class="label" for="privacy">Privacy setting for imported reviews</label>
<div class="select">
<select name="privacy">
<option value="public" selected>Public</option>
<option value="unlisted">Unlisted</option>
<option value="followers">Followers only</option>
<option value="direct">Private</option>
</select>
</div>
</div>
<button class="button is-primary" type="submit">Import</button>
</form> </form>
<p> <p>
Imports are limited in size, and only the first {{ limit }} items will be imported. Imports are limited in size, and only the first {{ limit }} items will be imported.

View file

@ -491,12 +491,16 @@ def import_data(request):
''' ingest a goodreads csv ''' ''' ingest a goodreads csv '''
form = forms.ImportForm(request.POST, request.FILES) form = forms.ImportForm(request.POST, request.FILES)
if form.is_valid(): if form.is_valid():
include_reviews = request.POST.get('include_reviews') == 'on'
privacy = request.POST.get('privacy')
try: try:
job = goodreads_import.create_job( job = goodreads_import.create_job(
request.user, request.user,
TextIOWrapper( TextIOWrapper(
request.FILES['csv_file'], request.FILES['csv_file'],
encoding=request.encoding) encoding=request.encoding),
include_reviews,
privacy,
) )
except (UnicodeDecodeError, ValueError): except (UnicodeDecodeError, ValueError):
return HttpResponseBadRequest('Not a valid csv file') return HttpResponseBadRequest('Not a valid csv file')